refactor: enhance TimerPage layout and functionality; update theme and tokens for improved design consistency
- Refactored TimerPage component to improve layout and user experience. - Updated theme configuration to utilize design tokens for better maintainability. - Introduced new tokens for colors and typography to align with design principles. - Simplified state management and improved timer functionality. - Enhanced accessibility and responsiveness of UI elements.
This commit is contained in:
84 files changed
+4501
-2682
No files matched your search
@@ -20,7 +20,7 @@ os.environ.setdefault("QT_QUICK_CONTROLS_MATERIAL_VARIANT", "Normal")
|
||||
|
||||
from PyQt6.QtCore import QObject, QTimer, QUrl
|
||||
from PyQt6.QtGui import QIcon
|
||||
from PyQt6.QtQml import QQmlApplicationEngine
|
||||
from PyQt6.QtQml import QQmlApplicationEngine, QQmlComponent
|
||||
from PyQt6.QtQuick import QQuickWindow
|
||||
from PyQt6.QtWidgets import QApplication, QSystemTrayIcon
|
||||
|
||||
@@ -28,6 +28,7 @@ from kadence_desktop import APP_ID, __version__
|
||||
from kadence_desktop.config import DesktopConfig
|
||||
from kadence_desktop.controller import Controller
|
||||
from kadence_desktop.integrations import dbus_service
|
||||
from kadence_desktop.integrations.placement import Rect, point_for
|
||||
from kadence_desktop.tray import Tray, render_icon
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -36,7 +37,9 @@ QML_DIR = Path(__file__).resolve().parent / "qml"
|
||||
|
||||
|
||||
class KadenceApp(QObject):
|
||||
def __init__(self, argv: list[str], *, minimized: bool = False) -> None:
|
||||
def __init__(
|
||||
self, argv: list[str], *, minimized: bool = False, view_only: bool = False
|
||||
) -> None:
|
||||
super().__init__()
|
||||
# QApplication (not QGuiApplication): the tray icon and its menu are
|
||||
# widgets, and they are what makes the app usable with no window open.
|
||||
@@ -52,6 +55,8 @@ class KadenceApp(QObject):
|
||||
self._app.setQuitOnLastWindowClosed(False)
|
||||
|
||||
self._config = DesktopConfig.load()
|
||||
if view_only:
|
||||
self._config.view_only = True
|
||||
self._controller = Controller(self._config)
|
||||
|
||||
self._engine = QQmlApplicationEngine()
|
||||
@@ -63,6 +68,8 @@ class KadenceApp(QObject):
|
||||
raise SystemExit("Could not load the QML interface.")
|
||||
self._window: QQuickWindow = self._engine.rootObjects()[0]
|
||||
|
||||
self._mini = self._create_mini_window()
|
||||
|
||||
self._tray: Tray | None = None
|
||||
if self._config.tray_icon and QSystemTrayIcon.isSystemTrayAvailable():
|
||||
self._tray = Tray(self._controller, self)
|
||||
@@ -73,6 +80,15 @@ class KadenceApp(QObject):
|
||||
self._controller.showWindowRequested.connect(self.show_window)
|
||||
self._app.aboutToQuit.connect(self._controller.shutdown)
|
||||
|
||||
# Anything that can change "should the mini timer be up?" lands here.
|
||||
self._controller.miniChanged.connect(self._update_mini)
|
||||
self._controller.miniPlacementChanged.connect(self._place_mini)
|
||||
self._controller.stateChanged.connect(self._update_mini)
|
||||
self._controller.configChanged.connect(self._update_mini)
|
||||
self._window.visibleChanged.connect(self._update_mini)
|
||||
self._update_mini()
|
||||
self._place_mini()
|
||||
|
||||
if self._config.dbus_service:
|
||||
self._register_dbus()
|
||||
|
||||
@@ -88,6 +104,73 @@ class KadenceApp(QObject):
|
||||
self._wakeup.start(500)
|
||||
self._wakeup.timeout.connect(lambda: None)
|
||||
|
||||
# ---- the floating mini timer --------------------------------------------
|
||||
|
||||
def _create_mini_window(self) -> QObject | None:
|
||||
"""Build MiniTimer.qml as a top-level window of its own."""
|
||||
component = QQmlComponent(self._engine, QUrl.fromLocalFile(str(QML_DIR / "MiniTimer.qml")))
|
||||
if component.isError(): # pragma: no cover - a QML typo, caught in dev
|
||||
for error in component.errors():
|
||||
log.error("mini timer: %s", error.toString())
|
||||
return None
|
||||
# No parent on purpose: a parented QML Window becomes transient and
|
||||
# cannot outlive — or out-show — its parent. The Python reference on
|
||||
# `self._mini` is what keeps it alive.
|
||||
window = component.create()
|
||||
if window is not None:
|
||||
window.setProperty("objectName", "miniTimer")
|
||||
return window
|
||||
|
||||
def _update_mini(self) -> None:
|
||||
"""Show it when pinned, or when an interval runs with the window away."""
|
||||
if self._mini is None:
|
||||
return
|
||||
config = self._config
|
||||
wanted = bool(config.mini_timer) and (
|
||||
self._controller.miniVisible
|
||||
or (config.mini_timer_auto and self._controller.running and not self._window.isVisible())
|
||||
)
|
||||
if bool(self._mini.property("visible")) != wanted:
|
||||
if wanted:
|
||||
# Each time it comes up it lands where you asked for, even if it
|
||||
# was dragged elsewhere during the last interval.
|
||||
self._place_mini()
|
||||
self._mini.setProperty("visible", wanted)
|
||||
|
||||
def _mini_screen_rect(self) -> Rect:
|
||||
"""The usable area of the screen the app is on — panels excluded."""
|
||||
screen = self._window.screen() or self._app.primaryScreen()
|
||||
area = screen.availableGeometry()
|
||||
return Rect(area.x(), area.y(), area.width(), area.height())
|
||||
|
||||
def _place_mini(self) -> None:
|
||||
"""Put the mini timer in the configured corner.
|
||||
|
||||
Setting `x`/`y` is what works on X11; on Wayland the client may not
|
||||
place itself, so the same point goes into the KWin rule, which is
|
||||
honoured when the window is mapped.
|
||||
"""
|
||||
if self._mini is None:
|
||||
return
|
||||
config = self._config
|
||||
if config.mini_timer_corner == "remember":
|
||||
self._controller.ensure_keep_above(None)
|
||||
if config.mini_timer_x >= 0 and config.mini_timer_y >= 0:
|
||||
self._mini.setProperty("x", config.mini_timer_x)
|
||||
self._mini.setProperty("y", config.mini_timer_y)
|
||||
return
|
||||
|
||||
size = (
|
||||
int(self._mini.property("width") or 252),
|
||||
int(self._mini.property("height") or 152),
|
||||
)
|
||||
point = point_for(
|
||||
config.mini_timer_corner, self._mini_screen_rect(), size, config.mini_timer_margin
|
||||
)
|
||||
self._mini.setProperty("x", point[0])
|
||||
self._mini.setProperty("y", point[1])
|
||||
self._controller.ensure_keep_above(point)
|
||||
|
||||
# ---- window ------------------------------------------------------------
|
||||
|
||||
def show_window(self) -> None:
|
||||
@@ -117,6 +200,7 @@ class KadenceApp(QObject):
|
||||
"interrupt": lambda: (controller.interrupt(), "noted")[1],
|
||||
"status": controller.statusLine,
|
||||
"send_report": lambda: (controller.sendReport(), "sending")[1],
|
||||
"toggle_mini": controller.toggleMini,
|
||||
"raise_window": lambda: (self.show_window(), "shown")[1],
|
||||
}
|
||||
self._dbus = dbus_service.TimerDBusService(actions, self)
|
||||
@@ -126,6 +210,8 @@ class KadenceApp(QObject):
|
||||
# ---- lifecycle ---------------------------------------------------------
|
||||
|
||||
def quit(self) -> None:
|
||||
if self._mini is not None:
|
||||
self._mini.setProperty("visible", False)
|
||||
self._controller.shutdown()
|
||||
if self._tray:
|
||||
self._tray.hide()
|
||||
|
||||
Reference in new issue
Block a user