diff --git a/res/design.ui b/res/design.ui index 62eb6630..f77b6ec7 100644 --- a/res/design.ui +++ b/res/design.ui @@ -7,20 +7,14 @@ 0 0 - 786 - 598 + 772 + 580 - 786 - 450 - - - - - 786 - 598 + 772 + 432 @@ -39,902 +33,1122 @@ QMainWindow::separator { height: 0px; width: 0px; } - - - 0 - 404 - - - - - 16777215 - 404 - - - - - - 11 - 145 - 49 - 20 - - - - X - - - Qt::AlignmentFlag::AlignCenter - - - - - - 10 - 67 - 107 - 24 - - - - Qt::FocusPolicy::NoFocus - - - Select Region - - - - - - 657 - 369 - 121 - 27 - - - - Qt::FocusPolicy::NoFocus - - - Start Auto Splitter - - - - - false - - - - 657 - 339 - 121 - 27 - - - - Qt::FocusPolicy::NoFocus - - - Reset - - - - - false - - - - 657 - 310 - 59 - 27 - - - - Qt::FocusPolicy::NoFocus - - - Undo - - - - - false - - - - 719 - 310 - 59 - 27 - - - - Qt::FocusPolicy::NoFocus - - - Skip - - - - - - 10 - 270 - 53 - 24 - - - - Qt::FocusPolicy::NoFocus - - - calculate the average max FPS of the set capture region - - - Max FPS - - - - - - 92 - 272 - 21 - 20 - - - - FPS - - - - - - 127 - 67 - 320 - 240 - - - - QFrame::Shape::Box - - - QFrame::Shadow::Plain - - - 1 - - - 0 - - - - - - Qt::AlignmentFlag::AlignCenter - - - - - - 456 - 67 - 320 - 240 - - - - QFrame::Shape::Box - - - - - - Qt::AlignmentFlag::AlignCenter - - - - - - 456 - 31 - 318 - 20 - - - - Current Image - - - Qt::AlignmentFlag::AlignCenter - - - - - - 11 - 190 - 49 - 20 - - - - Width - - - Qt::AlignmentFlag::AlignCenter - - - - - - 66 - 190 - 49 - 20 - - - - Height - - - Qt::AlignmentFlag::AlignCenter - - - - - - 65 - 272 - 26 - 20 - - - - - - - - - - 11 - 210 - 51 - 24 - - - - QAbstractSpinBox::CorrectionMode::CorrectToNearestValue - - - 1 - - - 10000 - - - 640 - - - - - - 66 - 210 - 51 - 24 - - - - QAbstractSpinBox::CorrectionMode::CorrectToNearestValue - - - 1 - - - 10000 - - - 480 - - - - - - 127 - 31 - 318 - 20 - - - - Capture Region - - - Qt::AlignmentFlag::AlignCenter - - - - - - 484 - 49 - 264 - 20 - - - - - - - - Qt::AlignmentFlag::AlignCenter - - - - - - 10 - 240 - 107 - 24 - - - - Qt::FocusPolicy::NoFocus - - - Take Screenshot - - - - - - 11 - 165 - 51 - 24 - - - - QAbstractSpinBox::ButtonSymbols::UpDownArrows - - - QAbstractSpinBox::CorrectionMode::CorrectToNearestValue - - - 0 - - - 999999999 - - - 1 - - - 0 - - - - - - 66 - 165 - 51 - 24 - - - - QAbstractSpinBox::CorrectionMode::CorrectToNearestValue - - - 0 - - - 999999999 - - - 0 - - - - - - 66 - 145 - 49 - 20 - - - - Y - - - Qt::AlignmentFlag::AlignCenter - - - - - - 10 - 119 - 107 - 24 - - - - Qt::FocusPolicy::NoFocus - - - Align the capture region using a reference image - - - Align Region - - - - - - 10 - 93 - 107 - 24 - - - - Qt::FocusPolicy::NoFocus - - - Select Window - - - - - - 696 - 5 - 81 - 24 - - - - Qt::FocusPolicy::NoFocus - - - Browse... - - - - - - 10 - 9 - 111 - 16 - - - - Split Image Folder: - - - - - - 127 - 6 - 561 - 22 - - - - No Folder Selected - - - true - - - - - - 127 - 49 - 318 - 20 - - - - - - - - Qt::AlignmentFlag::AlignCenter - - - - - - 458 - 313 - 81 - 20 - - - - Image Loop: - - - - - - 127 - 312 - 321 - 84 - - - - Similarity Viewer - - - Qt::AlignmentFlag::AlignCenter - - - - - 133 - 22 - 57 - 16 - - - - Live - - - Qt::AlignmentFlag::AlignCenter - - - - - - 197 - 22 - 57 - 16 - - - - Highest - - - Qt::AlignmentFlag::AlignCenter - - - - - - 261 - 22 - 56 - 16 - - - - Threshold - - - Qt::AlignmentFlag::AlignCenter - - - - - - 0 - 39 - 322 - 3 - - - - Qt::Orientation::Horizontal - - - - - - 10 - 42 - 111 - 16 - - - - Current Image - - - - - - 10 - 64 - 111 - 16 - - - - Reset Image - - - - - - 0 - 61 - 322 - 3 - - - - Qt::Orientation::Horizontal - - - - - - 129 - 20 - 3 - 62 - - - - Qt::Orientation::Vertical - - - - - - 193 - 20 - 3 - 95 - - - - Qt::Orientation::Vertical - - - - - - 257 - 20 - 3 - 95 - - - - Qt::Orientation::Vertical - - - - - - 133 - 43 - 57 - 16 - - - - - - - - Qt::AlignmentFlag::AlignCenter - - - - - - 197 - 43 - 57 - 16 - - - - - - - - Qt::AlignmentFlag::AlignCenter - - - - - - 260 - 43 - 57 - 16 - - - - - - - - Qt::AlignmentFlag::AlignCenter - - - - - - 133 - 64 - 57 - 16 - - - - - - - - Qt::AlignmentFlag::AlignCenter - - - - - - 197 - 64 - 57 - 16 - - - - - - - - Qt::AlignmentFlag::AlignCenter - - - - - - 260 - 64 - 57 - 16 - - - - - - - - Qt::AlignmentFlag::AlignCenter - - - - - - - 457 - 369 - 131 - 27 - - - - Qt::FocusPolicy::NoFocus - - - Reload Start Image - - - - - - 458 - 344 - 121 - 20 - - - - Start Image Status: - - - - - - 577 - 344 - 81 - 20 - - - - status - - - - - - 537 - 313 - 121 - 20 - - - - N/A - - - - - false - - - - 455 - 49 - 27 - 18 - - - - Qt::FocusPolicy::NoFocus - - - Previous image - - - < - - - - - false - - - - 750 - 49 - 27 - 18 - - - - Qt::FocusPolicy::NoFocus - - - Next image - - - > - - + + + 4 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + 0 + 0 + + + + + 4 + + + 2 + + + 0 + + + 0 + + + 0 + + + + + + 111 + 0 + + + + + 111 + 16777215 + + + + Split Image Folder: + + + + + + + No Folder Selected + + + true + + + + + + + Qt::FocusPolicy::NoFocus + + + Browse... + + + + + + + + + + QSplitter::handle { background: transparent; } + + + Qt::Orientation::Horizontal + + + + + 112 + 16777215 + + + + + 2 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::Fixed + + + + 0 + 41 + + + + + + + + Qt::FocusPolicy::NoFocus + + + Select Region + + + + + + + Qt::FocusPolicy::NoFocus + + + Select Window + + + + + + + Qt::FocusPolicy::NoFocus + + + Align the capture region using a reference image + + + Align Region + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 2 + + + + + Width + + + Qt::AlignmentFlag::AlignCenter + + + + + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + 1 + + + 9999 + + + 480 + + + + + + + QAbstractSpinBox::ButtonSymbols::UpDownArrows + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + 9999 + + + 0 + + + + + + + Height + + + Qt::AlignmentFlag::AlignCenter + + + + + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + 9999 + + + 0 + + + + + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + 1 + + + 9999 + + + 640 + + + + + + + X + + + Qt::AlignmentFlag::AlignCenter + + + + + + + Y + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + + + Qt::FocusPolicy::NoFocus + + + Take Screenshot + + + + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 54 + 16777215 + + + + Qt::FocusPolicy::NoFocus + + + calculate the average max FPS of the set capture region + + + Max FPS + + + + + + + + 0 + 0 + + + + + + + + + + + FPS + + + + + + + + + + Qt::Orientation::Vertical + + + + 0 + 0 + + + + + + + + + + 322 + 16777215 + + + + + 2 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + 0 + 0 + + + + Capture Region + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + 0 + 20 + + + + - + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + 320 + 240 + + + + + 320 + 240 + + + + QFrame::Shape::Box + + + QFrame::Shadow::Plain + + + 1 + + + 0 + + + + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + 0 + 0 + + + + + 0 + 84 + + + + + 16777215 + 84 + + + + Similarity Viewer + + + Qt::AlignmentFlag::AlignCenter + + + + + 129 + 22 + 56 + 16 + + + + Live + + + Qt::AlignmentFlag::AlignCenter + + + + + + 194 + 22 + 56 + 16 + + + + Highest + + + Qt::AlignmentFlag::AlignCenter + + + + + + 258 + 22 + 56 + 16 + + + + Threshold + + + Qt::AlignmentFlag::AlignCenter + + + + + + 1 + 39 + 318 + 3 + + + + Qt::Orientation::Horizontal + + + + + + 10 + 42 + 111 + 16 + + + + Current Image + + + + + + 10 + 64 + 111 + 16 + + + + Reset Image + + + + + + 1 + 61 + 318 + 3 + + + + Qt::Orientation::Horizontal + + + + + + 124 + 20 + 3 + 64 + + + + Qt::Orientation::Vertical + + + + + + 189 + 20 + 3 + 64 + + + + Qt::Orientation::Vertical + + + + + + 254 + 20 + 3 + 64 + + + + Qt::Orientation::Vertical + + + + + + 129 + 43 + 56 + 16 + + + + - + + + Qt::AlignmentFlag::AlignCenter + + + + + + 194 + 43 + 56 + 16 + + + + - + + + Qt::AlignmentFlag::AlignCenter + + + + + + 258 + 43 + 56 + 16 + + + + - + + + Qt::AlignmentFlag::AlignCenter + + + + + + 129 + 64 + 56 + 16 + + + + - + + + Qt::AlignmentFlag::AlignCenter + + + + + + 194 + 64 + 56 + 16 + + + + - + + + Qt::AlignmentFlag::AlignCenter + + + + + + 258 + 64 + 56 + 16 + + + + - + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + Qt::Orientation::Vertical + + + + 0 + 0 + + + + + + + + + + 322 + 16777215 + + + + + 2 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + 0 + 0 + + + + Current Image + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + 4 + + + 1 + + + 0 + + + 0 + + + 0 + + + + + false + + + + 0 + 0 + + + + + 32 + 20 + + + + Qt::FocusPolicy::NoFocus + + + Previous image + + + < + + + + + + + - + + + Qt::AlignmentFlag::AlignCenter + + + + + + + false + + + + 0 + 0 + + + + + 32 + 20 + + + + Qt::FocusPolicy::NoFocus + + + Next image + + + > + + + + + + + + + + + 320 + 240 + + + + + 320 + 240 + + + + QFrame::Shape::Box + + + + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + 2 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Image Loop: + + + + + + + N/A + + + + + + + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Start Image Status: + + + + + + + status + + + + + + + + + + Qt::FocusPolicy::NoFocus + + + Reload Start Image + + + + + + + + 0 + 0 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + false + + + + 56 + 16777215 + + + + Qt::FocusPolicy::NoFocus + + + Undo + + + + + + + false + + + + 56 + 16777215 + + + + Qt::FocusPolicy::NoFocus + + + Skip + + + + + + + + + + false + + + Qt::FocusPolicy::NoFocus + + + Reset + + + + + + + + 0 + 0 + + + + Qt::FocusPolicy::NoFocus + + + Start Auto Splitter + + + + + + + Qt::Orientation::Vertical + + + + 0 + 0 + + + + + + + + + + + + 0 0 - 786 + 772 22 - - - Help - - - - - - - - - File @@ -949,8 +1163,21 @@ View + + + + Help + + + + + + + + + @@ -974,7 +1201,7 @@ ClickableLabel:hover { background-color: palette(midlight); } - + 1 0 @@ -1017,6 +1244,18 @@ ClickableLabel:hover { background-color: palette(midlight); } + + + 0 + 148 + + + + + 16777215 + 148 + + monospace @@ -1142,12 +1381,23 @@ ClickableLabel:hover { background-color: palette(midlight); } QAction::MenuRole::AboutQtRole + + + Toggle Layout to Portrait + + + Ctrl+L + + + Qt::ShortcutContext::ApplicationShortcut + + Toggle Logs - Ctrl+L + Ctrl+J Qt::ShortcutContext::ApplicationShortcut diff --git a/src/AutoSplit.py b/src/AutoSplit.py index 0a6b6b24..0ef53066 100755 --- a/src/AutoSplit.py +++ b/src/AutoSplit.py @@ -51,7 +51,16 @@ def do_nothing(*_): ... import cv2 from PySide6 import QtCore, QtGui from PySide6.QtTest import QTest -from PySide6.QtWidgets import QApplication, QFileDialog, QLabel, QMainWindow, QMessageBox, QWidget +from PySide6.QtWidgets import ( + QApplication, + QBoxLayout, + QFileDialog, + QLabel, + QMainWindow, + QMessageBox, + QVBoxLayout, + QWidget, +) import error_messages import user_profile @@ -246,6 +255,15 @@ def _show_error_signal_slot(error_message_box: Callable[..., object]): "check_for_updates_on_open", self.action_check_for_updates_on_open.isChecked() ) ) + self.action_toggle_layout.triggered.connect(self.toggle_layout) + + # left panel stays at sizeHint; center and right expand + self.main_splitter.setStretchFactor(0, 0) + self.main_splitter.setStretchFactor(1, 1) + self.main_splitter.setStretchFactor(2, 1) + self._layout_is_portrait = False + if user_profile.QT_SETTINGS.value("layout_is_portrait", False, type=bool): + self.toggle_layout() # update x, y, width, and height when changing the value of these spinbox's are changed self.x_spinbox.valueChanged.connect(self.__update_x) @@ -278,6 +296,8 @@ def _update_checker_widget_signal_slot(latest_version: str, check_on_open: bool) self.timer_start_image.timeout.connect(self.__compare_capture_for_auto_start) self.show() + # Lock the window to a fixed, content-fitting size (non-resizable). + QtCore.QTimer.singleShot(0, self._shrink_window_to_fit) # https://pyinstaller.org/en/stable/advanced-topics.html#module-pyi_splash # doc implies calling `pyi_splash.is_alive()` should return false should be enough, but in @@ -303,20 +323,8 @@ def _update_checker_widget_signal_slot(latest_version: str, check_on_open: bool) _last_log_footer_entry: log_capture.LogLine | None = None """Last (timestamp, text, is_stderr) shown in the footer, kept so it can be re-rendered.""" - _collapsed_height = 0 - """ - Window height with the log panel collapsed; captured from the .ui to fix the height per state. - """ - - _log_panel_height = 0 - """Window growth when the panel opens; derived from the .ui (expanded - collapsed height).""" - def _setup_log_footer(self): """Wire up the clickable log footer and the expandable log history panel.""" - # Both come from the .ui: collapsed = minimumSize height, panel = designed (expanded) - # geometry height minus that. Captured before setFixedHeight() overwrites minimumHeight(). - self._collapsed_height = self.minimumHeight() - self._log_panel_height = self.height() - self._collapsed_height # The status bar doesn't add() its .ui child, and stretch isn't expressible there. self.status_bar.addWidget(self.log_footer_label, 1) self.status_bar.setContentsMargins(0, 0, 0, 0) # Let the label's padding define the insets. @@ -348,8 +356,10 @@ def _setup_log_footer(self): def _set_log_panel_visible(self, show: bool): # noqa: FBT001 # boolean value setter, not an arbitrary flag self.log_dock.setVisible(show) - # Fix the height per state so it can't be dragged to over-expand or hide content. - self.setFixedHeight(self._collapsed_height + (self._log_panel_height if show else 0)) + # The dock lives outside the central widget (a bottom dock area), so showing it grows the + # window downward without shifting content. Refit to the new content in whatever orientation + # the window currently has, sharing the layout-toggle's sizing path (fixed, non-resizable). + self._shrink_window_to_fit() self._refresh_log_footer() # Flip the chevron to match the new state. def _toggle_log_panel(self): @@ -1101,6 +1111,75 @@ def __update_split_image(self, specific_image: AutoSplitImage | None = None): loop_tuple = self.split_images_and_loop_number[self.split_image_number] self.image_loop_value_label.setText(f"{loop_tuple[1]}/{loop_tuple[0].loops}") + def toggle_layout(self) -> None: + self._layout_is_portrait = not self._layout_is_portrait + + if self._layout_is_portrait: # Landscape --> portrait + right_layout = cast("QVBoxLayout", self.right_panel.layout()) + right_layout.insertWidget(0, self.capture_region_label) + right_layout.insertWidget(1, self.capture_region_window_label) + right_layout.insertWidget(2, self.live_image) + right_layout.insertWidget(3, self.similarity_viewer_groupbox) + cast("QVBoxLayout", self.left_panel.layout()).addWidget(self.split_controls_panel) + else: # Portrait --> landscape + center_layout = cast("QVBoxLayout", self.center_panel.layout()) + center_layout.insertWidget(0, self.capture_region_label) + center_layout.insertWidget(1, self.capture_region_window_label) + center_layout.insertWidget(2, self.live_image) + center_layout.insertWidget(3, self.similarity_viewer_groupbox) + cast("QVBoxLayout", self.right_panel.layout()).addWidget(self.split_controls_panel) + self.center_panel.setVisible(not self._layout_is_portrait) + self._apply_split_controls_layout() + self.action_toggle_layout.setText( + "Toggle Layout to Landscape" + if self._layout_is_portrait + else "Toggle Layout to Portrait" + ) + user_profile.QT_SETTINGS.setValue("layout_is_portrait", self._layout_is_portrait) + # The window keeps its old geometry after re-layout, leaving blank space. + # Shrink it back to fit; deferred because the panel/splitter size hints + # only settle over the next couple of layout passes. + QtCore.QTimer.singleShot(0, self._shrink_window_to_fit) + + def _shrink_window_to_fit(self) -> None: + # setFixedSize both fits the window to its content and removes the + # user resize handles. Two passes: the panel/splitter size hints only + # settle over consecutive layout passes. + self.setFixedSize(self.sizeHint()) + QtCore.QTimer.singleShot(0, lambda: self.setFixedSize(self.sizeHint())) + + def _apply_split_controls_layout(self) -> None: + """Two columns in landscape; single stacked column in portrait (narrow panel).""" + self.start_image_status_layout.setDirection( + QBoxLayout.Direction.TopToBottom + if self._layout_is_portrait + else QBoxLayout.Direction.LeftToRight + ) + layout = self.split_controls_layout + layout.removeItem(self.split_controls_spacer) + widgets = ( + self.image_loop_row, + self.start_image_status_row, + self.reload_start_image_button, + self.action_row, + self.reset_button, + self.start_auto_splitter_button, + ) + for widget in widgets: + layout.removeWidget(widget) + if self._layout_is_portrait: + for row, widget in enumerate(widgets): + layout.addWidget(widget, row, 0) + layout.addItem(self.split_controls_spacer, len(widgets), 0, 1, 1) + else: + layout.addWidget(self.image_loop_row, 0, 0) + layout.addWidget(self.start_image_status_row, 1, 0) + layout.addWidget(self.reload_start_image_button, 2, 0) + layout.addWidget(self.action_row, 0, 1) + layout.addWidget(self.reset_button, 1, 1) + layout.addWidget(self.start_auto_splitter_button, 2, 1) + layout.addItem(self.split_controls_spacer, 3, 0, 1, 2) + @override def closeEvent(self, event: QtGui.QCloseEvent | None = None): """Exit safely when closing the window."""