From d86a6429f7291a4b5a915f9fbd9206ea51284214 Mon Sep 17 00:00:00 2001 From: bartool Date: Sat, 27 Sep 2025 19:14:42 +0200 Subject: [PATCH] feat: add split view functionality with image rotation and flipping controls --- ui/icons/flip-horizontal-svgrepo-com.svg | 4 + ui/icons/flip-vertical-svgrepo-com.svg | 5 + ui/icons/horizontal-stacks-svgrepo-com.svg | 40 +++++ ui/icons/rotate-ccw-svgrepo-com.svg | 4 + ui/icons/rotate-cw-svgrepo-com.svg | 4 + ui/icons/vertical-stacks-svgrepo-com.svg | 2 + ui/widgets/split_view_widget.py | 161 ++++++++++++++++++--- 7 files changed, 202 insertions(+), 18 deletions(-) create mode 100644 ui/icons/flip-horizontal-svgrepo-com.svg create mode 100644 ui/icons/flip-vertical-svgrepo-com.svg create mode 100644 ui/icons/horizontal-stacks-svgrepo-com.svg create mode 100644 ui/icons/rotate-ccw-svgrepo-com.svg create mode 100644 ui/icons/rotate-cw-svgrepo-com.svg create mode 100644 ui/icons/vertical-stacks-svgrepo-com.svg diff --git a/ui/icons/flip-horizontal-svgrepo-com.svg b/ui/icons/flip-horizontal-svgrepo-com.svg new file mode 100644 index 0000000..8754423 --- /dev/null +++ b/ui/icons/flip-horizontal-svgrepo-com.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/ui/icons/flip-vertical-svgrepo-com.svg b/ui/icons/flip-vertical-svgrepo-com.svg new file mode 100644 index 0000000..a66ef65 --- /dev/null +++ b/ui/icons/flip-vertical-svgrepo-com.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/ui/icons/horizontal-stacks-svgrepo-com.svg b/ui/icons/horizontal-stacks-svgrepo-com.svg new file mode 100644 index 0000000..f7a1e59 --- /dev/null +++ b/ui/icons/horizontal-stacks-svgrepo-com.svg @@ -0,0 +1,40 @@ + + + + + + + + diff --git a/ui/icons/rotate-ccw-svgrepo-com.svg b/ui/icons/rotate-ccw-svgrepo-com.svg new file mode 100644 index 0000000..f42cc46 --- /dev/null +++ b/ui/icons/rotate-ccw-svgrepo-com.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/ui/icons/rotate-cw-svgrepo-com.svg b/ui/icons/rotate-cw-svgrepo-com.svg new file mode 100644 index 0000000..a4496a6 --- /dev/null +++ b/ui/icons/rotate-cw-svgrepo-com.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/ui/icons/vertical-stacks-svgrepo-com.svg b/ui/icons/vertical-stacks-svgrepo-com.svg new file mode 100644 index 0000000..872e321 --- /dev/null +++ b/ui/icons/vertical-stacks-svgrepo-com.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ui/widgets/split_view_widget.py b/ui/widgets/split_view_widget.py index d5f9a87..1a18486 100644 --- a/ui/widgets/split_view_widget.py +++ b/ui/widgets/split_view_widget.py @@ -1,9 +1,10 @@ -from PySide6.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsPixmapItem, QApplication, QMainWindow, QWidget, QVBoxLayout, QSplitter, QStackedWidget, QPushButton, QLabel -from PySide6.QtGui import QPixmap, QWheelEvent, QPainter, QBrush, QColor -from PySide6.QtCore import Qt +from PySide6.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsPixmapItem, QApplication, QMainWindow, QWidget, QVBoxLayout, QSplitter, QStackedWidget, QPushButton, QLabel, QToolButton +from PySide6.QtGui import QEnterEvent, QPixmap, QWheelEvent, QPainter, QBrush, QColor, QIcon +from PySide6.QtCore import Qt, QSize, Signal, QEvent import sys from ui.widgets.placeholder_widget import PlaceholderWidget + class ZoomableImageView(QGraphicsView): def __init__(self, parent=None): super().__init__(parent) @@ -11,18 +12,21 @@ class ZoomableImageView(QGraphicsView): # Scena i element obrazu self._scene = QGraphicsScene(self) self.setScene(self._scene) - self._scene.setBackgroundBrush(QBrush(QColor(20, 20, 20))) # ciemne tło + self._scene.setBackgroundBrush( + QBrush(QColor(20, 20, 20))) # ciemne tło self._pixmap_item = QGraphicsPixmapItem() self._scene.addItem(self._pixmap_item) # Ustawienia widoku - self.setDragMode(QGraphicsView.DragMode.ScrollHandDrag) # przesuwanie myszą + # przesuwanie myszą + self.setDragMode(QGraphicsView.DragMode.ScrollHandDrag) self.setRenderHint(QPainter.RenderHint.Antialiasing) self.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform) # Wyłączenie suwaków - self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + self.setHorizontalScrollBarPolicy( + Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) # Parametry zoomu self._zoom_factor = 1.25 @@ -53,6 +57,7 @@ class ZoomableImageView(QGraphicsView): return super().wheelEvent(event) # normalne przewijanie + class CameraPlaceholder(QWidget): def __init__(self, parent=None): super().__init__(parent) @@ -91,11 +96,13 @@ class CameraPlaceholder(QWidget): self.camera_start_btn.setStyleSheet(style_sheet) self.info_label = QLabel("Kliknij, aby uruchomić kamerę") - self.info_label.setStyleSheet("background-color: transparent; color: #CECECE; font-size: 18px;") + self.info_label.setStyleSheet( + "background-color: transparent; color: #CECECE; font-size: 18px;") self.info_label.setAlignment(Qt.AlignmentFlag.AlignCenter) layout.addStretch() - layout.addWidget(self.camera_start_btn, alignment=Qt.AlignmentFlag.AlignCenter) + layout.addWidget(self.camera_start_btn, + alignment=Qt.AlignmentFlag.AlignCenter) layout.addWidget(self.info_label) layout.addStretch() self.setLayout(layout) @@ -103,6 +110,112 @@ class CameraPlaceholder(QWidget): def set_info_text(self, text: str): self.info_label.setText(text) + +class ViewWithOverlay(QWidget): + toggleOrientation = Signal() + swapViews = Signal() + rotateCW = Signal() + rotateCCW = Signal() + + def __init__(self): + super().__init__() + layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + + self.viewer = ZoomableImageView() + layout.addWidget(self.viewer) + + icon_size = QSize(32, 32) + btn_size = (48, 48) + btn_style = """ + background-color: rgba(255, 255, 255, 0.5); + border-radius: 8px; + border: 2px solid #1f1f1f; + """ + + + self.cw_btn = QToolButton(self) + self.cw_btn.setIcon(QIcon("ui/icons/rotate-cw-svgrepo-com.svg")) + self.cw_btn.setIconSize(icon_size) + self.cw_btn.setStyleSheet(btn_style) + self.cw_btn.setFixedSize(*btn_size) + move_x = self.cw_btn.width() + 10 + self.cw_btn.move(self.width() - move_x, 10) + self.cw_btn.clicked.connect(self.rotateCW) + + self.ccw_btn = QToolButton(self) + self.ccw_btn.setIcon(QIcon("ui/icons/rotate-ccw-svgrepo-com.svg")) + self.ccw_btn.setIconSize(icon_size) + self.ccw_btn.setStyleSheet(btn_style) + self.ccw_btn.setFixedSize(*btn_size) + move_x += self.ccw_btn.width() + 10 + self.ccw_btn.move(self.width() - move_x, 10) + self.ccw_btn.clicked.connect(self.rotateCCW) + + self.flip_btn = QToolButton(self) + # self.flip_btn.setIcon(QIcon("ui/icons/flip-vertical-svgrepo-com.svg")) + self.flip_btn.setIconSize(icon_size) + self.flip_btn.setStyleSheet(btn_style) + self.flip_btn.setFixedSize(*btn_size) + move_x += self.flip_btn.width() + 10 + self.flip_btn.move(self.width() - move_x, 10) + self.flip_btn.clicked.connect(self.swapViews) + + self.orient_btn = QToolButton(self) + # self.orient_btn.setIcon(QIcon("ui/icons/horizontal-stacks-svgrepo-com.svg")) + self.orient_btn.setIconSize(icon_size) + self.orient_btn.setStyleSheet(btn_style) + self.orient_btn.setFixedSize(*btn_size) + move_x += self.orient_btn.width() + 10 + self.orient_btn.move(self.width() - move_x, 10) + self.orient_btn.clicked.connect(self.toggleOrientation) + + + self.cw_btn.raise_() + self.ccw_btn.raise_() + self.flip_btn.raise_() + self.orient_btn.raise_() + + self.toggle_orientation(Qt.Orientation.Vertical) + + def set_image(self, pixmap: QPixmap): + self.viewer.set_image(pixmap) + + def resizeEvent(self, event): + super().resizeEvent(event) + # Aktualizacja pozycji przycisku przy zmianie rozmiaru + move_x = self.cw_btn.width() + 10 + self.cw_btn.move(self.width() - move_x, 10) + move_x += self.ccw_btn.width() + 10 + self.ccw_btn.move(self.width() - move_x, 10) + move_x += self.flip_btn.width() + 10 + self.flip_btn.move(self.width() - move_x, 10) + move_x += self.orient_btn.width() + 10 + self.orient_btn.move(self.width() - move_x, 10) + + def toggle_orientation(self, orientation): + if orientation == Qt.Orientation.Vertical: + self.flip_btn.setIcon(QIcon("ui/icons/flip-vertical-svgrepo-com.svg")) + self.orient_btn.setIcon(QIcon("ui/icons/horizontal-stacks-svgrepo-com.svg")) + else: + self.flip_btn.setIcon(QIcon("ui/icons/flip-horizontal-svgrepo-com.svg")) + self.orient_btn.setIcon(QIcon("ui/icons/vertical-stacks-svgrepo-com.svg")) + + def enterEvent(self, event: QEnterEvent) -> None: + self.orient_btn.show() + self.flip_btn.show() + self.ccw_btn.show() + self.cw_btn.show() + return super().enterEvent(event) + + def leaveEvent(self, event: QEvent) -> None: + self.orient_btn.hide() + self.flip_btn.hide() + self.ccw_btn.hide() + self.cw_btn.hide() + return super().leaveEvent(event) + + class SplitView(QSplitter): def __init__(self, parent=None): super().__init__(parent) @@ -110,9 +223,11 @@ class SplitView(QSplitter): self.setOrientation(Qt.Orientation.Vertical) self.widget_start = CameraPlaceholder() - self.widget_live = ZoomableImageView() + # self.widget_live = ZoomableImageView() + self.widget_live = ViewWithOverlay() # self.widget_live = PlaceholderWidget("Camera View", "#750466") - self.widget_ref = ZoomableImageView() + # self.widget_ref = ZoomableImageView() + self.widget_ref = ViewWithOverlay() # self.widget_ref = PlaceholderWidget("Image View", "#007981") self.stack = QStackedWidget() @@ -129,18 +244,31 @@ class SplitView(QSplitter): # pixmap.fill(Qt.GlobalColor.lightGray) self.widget_live.set_image(pixmap) + self.widget_live.toggleOrientation.connect(self.toggle_orientation) + self.widget_ref.toggleOrientation.connect(self.toggle_orientation) + self.widget_live.swapViews.connect(self.swap_views) + self.widget_ref.swapViews.connect(self.swap_views) + def toggle_orientation(self): if self.orientation() == Qt.Orientation.Vertical: self.setOrientation(Qt.Orientation.Horizontal) self.setSizes([self.width()//2, self.width()//2]) + self.widget_live.toggle_orientation(Qt.Orientation.Horizontal) + self.widget_ref.toggle_orientation(Qt.Orientation.Horizontal) else: self.setOrientation(Qt.Orientation.Vertical) self.setSizes([self.height()//2, self.height()//2]) - - # def set_live_image(self, path_image: str): - # """Ustawienie obrazu na żywo""" - # pixmap = QPixmap(path_image) - # self.widget_live.set_image(pixmap) + self.widget_live.toggle_orientation(Qt.Orientation.Vertical) + self.widget_ref.toggle_orientation(Qt.Orientation.Vertical) + + def swap_views(self): + """Zamiana widoków miejscami""" + index_live = self.indexOf(self.stack) + index_ref = self.indexOf(self.widget_ref) + sizes = self.sizes() + self.insertWidget(index_live, self.widget_ref) + self.insertWidget(index_ref, self.stack) + self.setSizes(sizes) def set_live_image(self, pixmap: QPixmap): """Ustawienie obrazu na żywo""" @@ -159,6 +287,3 @@ class SplitView(QSplitter): self.stack.setCurrentWidget(self.widget_live) else: self.stack.setCurrentWidget(self.widget_start) - - -