ICRA/app/qt/main_window.py

224 lines
7.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Main PySide6 window with custom title bar."""
from __future__ import annotations
from pathlib import Path
from PySide6 import QtCore, QtGui, QtWidgets
class TitleBar(QtWidgets.QWidget):
"""Custom title bar mimicking modern Windows applications."""
HEIGHT = 40
def __init__(self, window: "MainWindow") -> None:
super().__init__(window)
self.window = window
self.setFixedHeight(self.HEIGHT)
self.setCursor(QtCore.Qt.ArrowCursor)
self.setAutoFillBackground(True)
palette = self.palette()
palette.setColor(QtGui.QPalette.Window, QtGui.QColor("#16171d"))
self.setPalette(palette)
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(12, 8, 12, 8)
layout.setSpacing(8)
logo_path = Path(__file__).resolve().parents[1] / "assets" / "logo.png"
if logo_path.exists():
pixmap = QtGui.QPixmap(str(logo_path))
logo_label = QtWidgets.QLabel()
logo_label.setPixmap(pixmap.scaled(24, 24, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation))
layout.addWidget(logo_label)
title_label = QtWidgets.QLabel("Interactive Color Range Analyzer")
title_label.setStyleSheet("color: #f7f7fb; font-weight: 600;")
layout.addWidget(title_label)
layout.addStretch(1)
self.min_btn = self._create_button("", "Minimise window")
self.min_btn.clicked.connect(window.showMinimized)
layout.addWidget(self.min_btn)
self.max_btn = self._create_button("", "Maximise / Restore")
self.max_btn.clicked.connect(window.toggle_maximise)
layout.addWidget(self.max_btn)
close_btn = self._create_button("", "Close")
close_btn.clicked.connect(window.close)
close_btn.setStyleSheet(
"""
QPushButton { background-color: transparent; color: #f7f7fb; border: none; padding: 4px 10px; }
QPushButton:hover { background-color: #d0342c; }
"""
)
layout.addWidget(close_btn)
def _create_button(self, text: str, tooltip: str) -> QtWidgets.QPushButton:
btn = QtWidgets.QPushButton(text)
btn.setToolTip(tooltip)
btn.setFixedSize(36, 24)
btn.setCursor(QtCore.Qt.ArrowCursor)
btn.setStyleSheet(
"""
QPushButton {
background-color: transparent;
color: #f7f7fb;
border: none;
padding: 4px 10px;
}
QPushButton:hover {
background-color: rgba(255, 255, 255, 0.12);
}
"""
)
return btn
def mouseDoubleClickEvent(self, event: QtGui.QMouseEvent) -> None:
if event.button() == QtCore.Qt.LeftButton:
self.window.toggle_maximise()
event.accept()
def mousePressEvent(self, event: QtGui.QMouseEvent) -> None:
if event.button() == QtCore.Qt.LeftButton:
self.window.start_system_move(event.globalPosition())
event.accept()
super().mousePressEvent(event)
class ImageView(QtWidgets.QLabel):
"""Simple image display widget that keeps aspect ratio."""
def __init__(self) -> None:
super().__init__()
self.setAlignment(QtCore.Qt.AlignCenter)
self._pixmap: QtGui.QPixmap | None = None
def set_image(self, pixmap: QtGui.QPixmap | None) -> None:
self._pixmap = pixmap
self._rescale()
def resizeEvent(self, event: QtGui.QResizeEvent) -> None:
super().resizeEvent(event)
self._rescale()
def _rescale(self) -> None:
if self._pixmap is None:
self.clear()
self.setText("<No image loaded>")
self.setStyleSheet("color: rgba(255,255,255,0.5); font-size: 14px;")
return
target = self._pixmap.scaled(
self.size(),
QtCore.Qt.KeepAspectRatio,
QtCore.Qt.SmoothTransformation,
)
self.setPixmap(target)
self.setStyleSheet("")
class MainWindow(QtWidgets.QMainWindow):
"""Main application window containing custom chrome and content area."""
def __init__(self) -> None:
super().__init__()
self.setWindowFlag(QtCore.Qt.FramelessWindowHint)
self.setWindowFlag(QtCore.Qt.Window)
self.setAttribute(QtCore.Qt.WA_TranslucentBackground, False)
self.setMinimumSize(900, 600)
container = QtWidgets.QWidget()
container_layout = QtWidgets.QVBoxLayout(container)
container_layout.setContentsMargins(0, 0, 0, 0)
container_layout.setSpacing(0)
self.title_bar = TitleBar(self)
container_layout.addWidget(self.title_bar)
self.content = QtWidgets.QWidget()
self.content.setStyleSheet("background-color: #111216;")
content_layout = QtWidgets.QVBoxLayout(self.content)
content_layout.setContentsMargins(24, 24, 24, 24)
content_layout.setSpacing(18)
toolbar = QtWidgets.QHBoxLayout()
toolbar.setSpacing(12)
self.open_button = QtWidgets.QPushButton("Open Image…")
self.open_button.setCursor(QtCore.Qt.PointingHandCursor)
self.open_button.setStyleSheet(
"""
QPushButton {
background: linear-gradient(135deg, #5168ff, #9a4dff);
border: none;
color: #ffffff;
font-weight: 600;
padding: 10px 16px;
border-radius: 10px;
}
QPushButton:hover {
filter: brightness(1.1);
}
"""
)
self.open_button.clicked.connect(self.open_image)
toolbar.addWidget(self.open_button)
toolbar.addStretch(1)
self.status_label = QtWidgets.QLabel("No image loaded")
self.status_label.setStyleSheet("color: rgba(255,255,255,0.7); font-weight: 500;")
toolbar.addWidget(self.status_label)
content_layout.addLayout(toolbar)
self.image_view = ImageView()
self.image_view.setStyleSheet("border: 1px solid rgba(255,255,255,0.08); border-radius: 12px;")
content_layout.addWidget(self.image_view, 1)
container_layout.addWidget(self.content, 1)
self.setCentralWidget(container)
self._is_maximised = False
self._current_image_path: Path | None = None
# Window control helpers -------------------------------------------------
def toggle_maximise(self) -> None:
handle = self.windowHandle()
if handle is None:
return
if self._is_maximised:
self.showNormal()
self._is_maximised = False
self.title_bar.max_btn.setText("")
else:
self.showMaximized()
self._is_maximised = True
self.title_bar.max_btn.setText("")
def start_system_move(self, _global_position: QtCore.QPointF) -> None:
handle = self.windowHandle()
if handle:
handle.startSystemMove()
# Image handling ---------------------------------------------------------
def open_image(self) -> None:
filters = "Images (*.png *.jpg *.jpeg *.bmp *.webp)"
path_str, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select image", "", filters)
if not path_str:
return
path = Path(path_str)
pixmap = QtGui.QPixmap(str(path))
if pixmap.isNull():
QtWidgets.QMessageBox.warning(self, "ICRA", "Unable to open the selected image.")
return
self.image_view.set_image(pixmap)
self._current_image_path = path
self.status_label.setText(f"{path.name} · {pixmap.width()}×{pixmap.height()}")