// The window. // // Qt Quick Controls' Material style, following the session's own light/dark // preference, with a navigation rail on the left and the pages stacked on the // right. Everything it shows comes from `controller`, the Python object bound // into the QML context โ€” there is no second copy of the state here. import QtQuick import QtQuick.Controls.Material import QtQuick.Layouts import QtQuick.Window import "components" ApplicationWindow { id: window width: 1100 height: 760 minimumWidth: 420 minimumHeight: 560 visible: false title: controller.running ? controller.clockText + " ยท " + controller.kindLabel + " โ€” Kadence" : "Kadence" Material.theme: Material.System Material.primary: "#6750A4" Material.accent: "#6750A4" readonly property color workColor: "#2a78d6" readonly property color shortBreakColor: "#eb6834" readonly property color longBreakColor: "#1baf7a" readonly property bool wide: width >= 820 property int page: 0 function kindColor(kind) { if (kind === "short_break") return shortBreakColor if (kind === "long_break") return longBreakColor return workColor } // Closing the window keeps the app alive in the tray โ€” the timer is still // running, and that is the whole point of a tray icon. onClosing: function (close) { if (controller.config.close_to_tray && controller.config.tray_icon) { close.accepted = false window.hide() } } Connections { target: controller function onToast(message) { snackbar.show(message) } function onShowWindowRequested() { window.show(); window.raise(); window.requestActivate() } } // ---- shortcuts --------------------------------------------------------- Shortcut { sequence: "Space"; onActivated: controller.toggle() } Shortcut { sequence: "Ctrl+Return"; onActivated: controller.complete() } Shortcut { sequence: "Ctrl+E"; onActivated: controller.extend(5) } Shortcut { sequence: "Ctrl+R"; onActivated: controller.refreshSources() } Shortcut { sequence: "Ctrl+1"; onActivated: window.page = 0 } Shortcut { sequence: "Ctrl+2"; onActivated: window.page = 1 } Shortcut { sequence: "Ctrl+3"; onActivated: window.page = 2 } Shortcut { sequence: "Ctrl+4"; onActivated: window.page = 3 } Shortcut { sequence: "Ctrl+5"; onActivated: window.page = 4 } Shortcut { sequence: "Ctrl+W"; onActivated: window.hide() } header: ToolBar { visible: !window.wide Material.background: Material.backgroundColor RowLayout { anchors.fill: parent anchors.leftMargin: 12 anchors.rightMargin: 12 Label { text: ["Timer", "Tasks", "Blocker", "Report", "Settings"][window.page] font.pixelSize: 18 font.weight: Font.Medium Layout.fillWidth: true } StatusPill { running: controller.running paused: controller.status === "paused" connected: controller.connected text: controller.connected ? controller.clockText : "offline" accent: window.kindColor(controller.kind) } } } RowLayout { anchors.fill: parent spacing: 0 // ---- navigation rail ------------------------------------------------ Pane { visible: window.wide Layout.fillHeight: true Layout.preferredWidth: 212 Material.elevation: 0 padding: 12 ColumnLayout { anchors.fill: parent spacing: 4 RowLayout { Layout.bottomMargin: 16 Layout.leftMargin: 8 spacing: 10 Rectangle { width: 36; height: 36; radius: 18 color: Material.accent Label { anchors.centerIn: parent; text: "๐Ÿ…"; font.pixelSize: 18 } } ColumnLayout { spacing: 0 Label { text: "Kadence"; font.pixelSize: 17; font.weight: Font.DemiBold } Label { text: controller.connected ? "connected" : "offline" font.pixelSize: 11 opacity: 0.6 } } } Repeater { model: [ { label: "Timer", icon: "โฑ" }, { label: "Tasks", icon: "โœ“" }, { label: "Blocker", icon: "๐Ÿ›ก" }, { label: "Report", icon: "๐Ÿ“Š" }, { label: "Settings", icon: "โš™" } ] delegate: ItemDelegate { required property int index required property var modelData Layout.fillWidth: true height: 44 highlighted: window.page === index onClicked: window.page = index background: Rectangle { radius: 22 color: parent.highlighted ? Material.accent : (parent.hovered ? Material.listHighlightColor : "transparent") opacity: parent.highlighted ? 0.18 : 1 } contentItem: RowLayout { spacing: 12 Label { text: modelData.icon; font.pixelSize: 16 } Label { text: modelData.label font.weight: parent.parent.highlighted ? Font.DemiBold : Font.Normal Layout.fillWidth: true } } } } Item { Layout.fillHeight: true } StatusPill { Layout.fillWidth: true running: controller.running paused: controller.status === "paused" connected: controller.connected text: controller.connected ? (controller.running ? controller.clockText : "idle") : "offline" accent: window.kindColor(controller.kind) } Label { Layout.fillWidth: true Layout.topMargin: 6 text: controller.completedToday + "/" + controller.goal + " today" font.pixelSize: 12 opacity: 0.7 horizontalAlignment: Text.AlignHCenter } Label { Layout.fillWidth: true visible: controller.blockerActive text: "๐Ÿ›ก blocking is on" font.pixelSize: 12 color: "#e34948" horizontalAlignment: Text.AlignHCenter } } } // ---- pages ---------------------------------------------------------- ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true spacing: 0 Banner { Layout.fillWidth: true visible: !controller.connected text: controller.connectionError !== "" ? controller.connectionError : "Cannot reach the backend. Start it with `docker compose up`." actionText: "Settings" onActionClicked: window.page = 4 } StackLayout { Layout.fillWidth: true Layout.fillHeight: true currentIndex: window.page TimerPage {} TasksPage {} BlockerPage {} ReportPage {} SettingsPage {} } } } // ---- footer: bottom navigation on a narrow window ---------------------- footer: TabBar { visible: !window.wide currentIndex: window.page onCurrentIndexChanged: window.page = currentIndex TabButton { text: "โฑ" } TabButton { text: "โœ“" } TabButton { text: "๐Ÿ›ก" } TabButton { text: "๐Ÿ“Š" } TabButton { text: "โš™" } } Snackbar { id: snackbar } }