// The window. // // A quiet left column — wordmark, navigation as words, and the two things worth // knowing from anywhere (what the timer is doing, whether the blocker is on) — // with the pages on the right. Qt Quick Controls supply the behaviour; the // chrome comes from `Theme`, so the desktop app and the web app are the same // product rather than two takes on it. import QtQuick import QtQuick.Controls import QtQuick.Controls.Material import QtQuick.Layouts import "." import "components" ApplicationWindow { id: window width: 1180 height: 820 minimumWidth: 460 minimumHeight: 600 visible: false color: Theme.paper title: controller.running ? controller.clockText + " · " + controller.kindLabel + " — Kadence" : "Kadence" Material.theme: Theme.dark ? Material.Dark : Material.Light Material.primary: Theme.ember Material.accent: Theme.ember Material.background: Theme.paper Material.foreground: Theme.ink readonly property bool wide: width >= 820 property int page: 0 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() } } Shortcut { sequence: "Space"; onActivated: if (controller.timerEnabled) controller.toggle() } Shortcut { sequence: "Ctrl+Return"; onActivated: if (controller.timerEnabled) controller.complete() } Shortcut { sequence: "Ctrl+E"; onActivated: if (controller.timerEnabled) 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() } Shortcut { sequence: "Ctrl+M"; onActivated: controller.toggleMini() } RowLayout { anchors.fill: parent spacing: 0 // ---- the rail -------------------------------------------------------- Item { visible: window.wide Layout.fillHeight: true Layout.preferredWidth: 212 Rectangle { anchors.right: parent.right width: 1 height: parent.height color: Theme.rule } ColumnLayout { anchors.fill: parent anchors.margins: 22 spacing: 2 ColumnLayout { Layout.bottomMargin: 26 spacing: 0 Label { text: "Kadence" color: Theme.ink font.pixelSize: 23 font.family: Theme.displayFamily font.letterSpacing: -0.5 } Label { text: "find your rhythm" color: Theme.muted font.pixelSize: 11 font.family: Theme.sansFamily } } Repeater { model: ["Timer", "Tasks", "Blocker", "Report", "Settings"] delegate: Item { required property int index required property string modelData Layout.fillWidth: true implicitHeight: 34 Rectangle { anchors.verticalCenter: parent.verticalCenter width: 2 height: 16 radius: 1 color: Theme.ember visible: window.page === index } Label { anchors.left: parent.left anchors.leftMargin: 14 anchors.verticalCenter: parent.verticalCenter text: modelData color: window.page === index ? Theme.ink : (mouse.containsMouse ? Theme.ink : Theme.muted) font.pixelSize: 14 font.weight: window.page === index ? Font.DemiBold : Font.Normal font.family: Theme.sansFamily } MouseArea { id: mouse anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: window.page = index } } } Item { Layout.fillHeight: true } Rectangle { Layout.fillWidth: true; height: 1; color: Theme.rule; Layout.bottomMargin: 10 } RowLayout { Layout.fillWidth: true spacing: 8 Rectangle { width: 7; height: 7; radius: 3.5 color: controller.running ? Theme.kindColor(controller.kind) : Theme.faint SequentialAnimation on opacity { running: controller.running && controller.status === "running" loops: Animation.Infinite NumberAnimation { to: 0.35; duration: 900 } NumberAnimation { to: 1.0; duration: 900 } } } Label { text: controller.connected ? (controller.timerEnabled ? (controller.running ? controller.clockText : "Idle") : "View mode") : "Offline" color: Theme.ink font.pixelSize: 13 font.weight: Font.Medium font.family: Theme.monoFamily } Item { Layout.fillWidth: true } Label { text: controller.completedToday + "/" + controller.goal color: Theme.muted font.pixelSize: 12 font.family: Theme.sansFamily } } Label { visible: controller.blockerActive text: "● BLOCKING" color: Theme.clay font.pixelSize: 10 font.letterSpacing: 1.1 font.weight: Font.DemiBold font.family: Theme.sansFamily } } } // ---- pages ----------------------------------------------------------- ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true spacing: 0 ToolBar { visible: !window.wide Layout.fillWidth: true background: Rectangle { color: Theme.paper Rectangle { anchors.bottom: parent.bottom; width: parent.width; height: 1; color: Theme.rule } } RowLayout { anchors.fill: parent anchors.leftMargin: 16 anchors.rightMargin: 16 Label { text: "Kadence" color: Theme.ink font.pixelSize: 18 font.family: Theme.displayFamily Layout.fillWidth: true } Label { text: controller.connected ? (controller.timerEnabled ? (controller.running ? controller.clockText : "Idle") : "View mode") : "Offline" color: Theme.ink font.pixelSize: 13 font.family: Theme.monoFamily } } } StackLayout { Layout.fillWidth: true Layout.fillHeight: true currentIndex: window.page TimerPage {} TasksPage {} BlockerPage {} ReportPage {} SettingsPage {} } TabBar { visible: !window.wide Layout.fillWidth: true currentIndex: window.page onCurrentIndexChanged: window.page = currentIndex background: Rectangle { color: Theme.paper Rectangle { width: parent.width; height: 1; color: Theme.rule } } TabButton { text: "Timer" } TabButton { text: "Tasks" } TabButton { text: "Blocker" } TabButton { text: "Report" } TabButton { text: "Settings" } } } } Snackbar { id: snackbar } // The floating mini timer is created by app.py as its own top-level window: // nested inside this one it would be transient, and a transient window // cannot be shown while its parent is hidden — which is precisely when the // mini timer earns its keep. }