feat: add split view functionality with image rotation and flipping controls
This commit is contained in:
4
ui/icons/flip-horizontal-svgrepo-com.svg
Normal file
4
ui/icons/flip-horizontal-svgrepo-com.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.079 3.46209C15.3762 3.17355 15.851 3.18054 16.1396 3.47771L19.538 6.9777C19.8205 7.26871 19.8205 7.73162 19.538 8.02263L16.1396 11.5226C15.851 11.8198 15.3762 11.8268 15.079 11.5382C14.7819 11.2497 14.7749 10.7749 15.0634 10.4777L17.2263 8.25015L4.99989 8.25015C4.58567 8.25015 4.24989 7.91437 4.24989 7.50015C4.24989 7.08594 4.58567 6.75015 4.99989 6.75015L17.2263 6.75015L15.0634 4.52264C14.7749 4.22546 14.7819 3.75064 15.079 3.46209ZM8.92071 12.4618C9.21788 12.7504 9.22488 13.2252 8.93633 13.5224L6.77327 15.7501L18.9999 15.7501C19.4141 15.7501 19.7499 16.0859 19.7499 16.5001C19.7499 16.9143 19.4141 17.2501 18.9999 17.2501L6.77366 17.2501L8.93633 19.4774C9.22488 19.7746 9.21788 20.2494 8.92071 20.538C8.62353 20.8265 8.14871 20.8195 7.86016 20.5224L4.46177 17.0224C4.17922 16.7314 4.17922 16.2685 4.46177 15.9774L7.86016 12.4775C8.14871 12.1803 8.62353 12.1733 8.92071 12.4618Z" fill="#000000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
5
ui/icons/flip-vertical-svgrepo-com.svg
Normal file
5
ui/icons/flip-vertical-svgrepo-com.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.49976 4.25001C7.91398 4.25001 8.24976 4.5858 8.24976 5.00001L8.24976 17.2266L10.4775 15.0636C10.7747 14.775 11.2495 14.782 11.538 15.0792C11.8266 15.3763 11.8196 15.8512 11.5224 16.1397L8.02243 19.5381C7.73142 19.8207 7.26851 19.8207 6.9775 19.5381L3.47751 16.1397C3.18034 15.8512 3.17335 15.3763 3.4619 15.0792C3.75044 14.782 4.22527 14.775 4.52244 15.0636L6.74976 17.2262L6.74976 5.00001C6.74976 4.5858 7.08555 4.25001 7.49976 4.25001Z" fill="#000000"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.9773 4.4619C16.2683 4.17934 16.7312 4.17934 17.0222 4.4619L20.5222 7.86029C20.8193 8.14884 20.8263 8.62366 20.5378 8.92083C20.2492 9.21801 19.7744 9.225 19.4772 8.93645L17.2497 6.7736L17.2497 19C17.2497 19.4142 16.9139 19.75 16.4997 19.75C16.0855 19.75 15.7497 19.4142 15.7497 19L15.7497 6.77358L13.5222 8.93645C13.225 9.225 12.7502 9.21801 12.4616 8.92083C12.1731 8.62366 12.1801 8.14884 12.4773 7.86029L15.9773 4.4619Z" fill="#000000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
40
ui/icons/horizontal-stacks-svgrepo-com.svg
Normal file
40
ui/icons/horizontal-stacks-svgrepo-com.svg
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
|
||||
<svg
|
||||
fill="#000000"
|
||||
width="800px"
|
||||
height="800px"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="horizontal-stacks-svgrepo-com.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="0.98875"
|
||||
inkscape:cx="579.5196"
|
||||
inkscape:cy="342.35145"
|
||||
inkscape:window-width="3440"
|
||||
inkscape:window-height="1369"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
d="M 13.5,14.82 V 1.18 C 13.5,0.53 12.97,0 12.32,0 H 9.8 C 9.15,0 8.62,0.53 8.62,1.18 V 14.82 C 8.62,15.47 9.15,16 9.8,16 h 2.52 c 0.65,0 1.18,-0.53 1.18,-1.18 z M 9.88,14.75 V 1.25 h 2.37 v 13.5 z m -2.5,0.07 V 1.18 C 7.38,0.53 6.85,0 6.2,0 H 3.68 C 3.03,0 2.5,0.53 2.5,1.18 V 14.82 C 2.5,15.47 3.03,16 3.68,16 H 6.2 c 0.65,0 1.18,-0.53 1.18,-1.18 z M 3.75,14.75 V 1.25 h 2.38 v 13.5 z"
|
||||
id="path1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
4
ui/icons/rotate-ccw-svgrepo-com.svg
Normal file
4
ui/icons/rotate-ccw-svgrepo-com.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.5 20.5C17.1944 20.5 21 16.6944 21 12C21 7.30558 17.1944 3.5 12.5 3.5C7.80558 3.5 4 7.30558 4 12C4 13.5433 4.41128 14.9905 5.13022 16.238M1.5 15L5.13022 16.238M6.82531 12.3832L5.47107 16.3542L5.13022 16.238" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 533 B |
4
ui/icons/rotate-cw-svgrepo-com.svg
Normal file
4
ui/icons/rotate-cw-svgrepo-com.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.5 20.5C6.80558 20.5 3 16.6944 3 12C3 7.30558 6.80558 3.5 11.5 3.5C16.1944 3.5 20 7.30558 20 12C20 13.5433 19.5887 14.9905 18.8698 16.238M22.5 15L18.8698 16.238M17.1747 12.3832L18.5289 16.3542L18.8698 16.238" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 534 B |
2
ui/icons/vertical-stacks-svgrepo-com.svg
Normal file
2
ui/icons/vertical-stacks-svgrepo-com.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.82 2.5H1.18C.53 2.5 0 3.03 0 3.68V6.2c0 .65.53 1.18 1.18 1.18h13.64c.65 0 1.18-.53 1.18-1.18V3.68c0-.65-.53-1.18-1.18-1.18zm-.07 3.62H1.25V3.75h13.5v2.37zm.07 2.5H1.18C.53 8.62 0 9.15 0 9.8v2.52c0 .65.53 1.18 1.18 1.18h13.64c.65 0 1.18-.53 1.18-1.18V9.8c0-.65-.53-1.18-1.18-1.18zm-.07 3.63H1.25V9.87h13.5v2.38z"/></svg>
|
||||
|
After Width: | Height: | Size: 556 B |
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user