init
This commit is contained in:
121 files changed
+17641
No files matched your search
@@ -0,0 +1,36 @@
|
||||
// An inline warning strip — used for "the backend is gone" and "the helper is
|
||||
// not installed", the two states where the app cannot do its job.
|
||||
import QtQuick
|
||||
import QtQuick.Controls.Material
|
||||
import QtQuick.Layouts
|
||||
|
||||
Rectangle {
|
||||
id: banner
|
||||
property string text: ""
|
||||
property string actionText: ""
|
||||
signal actionClicked()
|
||||
|
||||
implicitHeight: visible ? row.implicitHeight + 20 : 0
|
||||
color: "#e34948"
|
||||
opacity: 0.92
|
||||
|
||||
RowLayout {
|
||||
id: row
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
spacing: 12
|
||||
Label {
|
||||
text: banner.text
|
||||
color: "white"
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Button {
|
||||
visible: banner.actionText !== ""
|
||||
text: banner.actionText
|
||||
flat: true
|
||||
Material.foreground: "white"
|
||||
onClicked: banner.actionClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// A tiny bar chart: magnitude over time, one hue, baseline at zero.
|
||||
//
|
||||
// The ramp step says "did this day clear the goal" without adding a second
|
||||
// colour, and every bar carries its own tooltip, so no bar needs a label.
|
||||
import QtQuick
|
||||
import QtQuick.Controls.Material
|
||||
import QtQuick.Layouts
|
||||
|
||||
Item {
|
||||
id: chart
|
||||
property var days: [] // [{date, pomodoros, focus_minutes, goal_met}]
|
||||
property int goal: 8
|
||||
property int barHeight: 110
|
||||
|
||||
implicitHeight: barHeight + 18
|
||||
|
||||
readonly property int maxValue: {
|
||||
var top = goal
|
||||
for (var i = 0; i < days.length; i++) top = Math.max(top, days[i].pomodoros)
|
||||
return Math.max(top, 1)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
y: chart.barHeight - (chart.goal / chart.maxValue) * chart.barHeight
|
||||
height: 1
|
||||
color: Material.hintTextColor
|
||||
opacity: 0.45
|
||||
visible: chart.goal > 0
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: chart.barHeight
|
||||
spacing: 2
|
||||
|
||||
Repeater {
|
||||
model: chart.days
|
||||
delegate: Item {
|
||||
required property var modelData
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
width: parent.width
|
||||
height: Math.max(modelData.pomodoros > 0
|
||||
? (modelData.pomodoros / chart.maxValue) * chart.barHeight : 2, 2)
|
||||
radius: 4
|
||||
color: modelData.pomodoros === 0 ? Material.dividerColor
|
||||
: (modelData.goal_met ? "#256abf" : "#6da7ec")
|
||||
|
||||
ToolTip.visible: hover.hovered
|
||||
ToolTip.text: modelData.date + ": " + modelData.pomodoros
|
||||
+ " pomodoros · " + modelData.focus_minutes + " min"
|
||||
HoverHandler { id: hover }
|
||||
Behavior on height { NumberAnimation { duration: 250 } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
Label {
|
||||
text: chart.days.length ? chart.days[0].date.substring(5) : ""
|
||||
font.pixelSize: 10; opacity: 0.6
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
Label {
|
||||
text: chart.days.length ? chart.days[chart.days.length - 1].date.substring(5) : ""
|
||||
font.pixelSize: 10; opacity: 0.6
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// A Material surface with a title. Used everywhere instead of bare Panes so the
|
||||
// pages share one radius, one padding and one elevation.
|
||||
import QtQuick
|
||||
import QtQuick.Controls.Material
|
||||
import QtQuick.Layouts
|
||||
|
||||
Pane {
|
||||
id: card
|
||||
property string title: ""
|
||||
property string subtitle: ""
|
||||
default property alias content: inner.data
|
||||
|
||||
Material.elevation: 1
|
||||
Material.roundedScale: Material.LargeScale
|
||||
padding: 18
|
||||
Layout.fillWidth: true
|
||||
|
||||
background: Rectangle {
|
||||
radius: 18
|
||||
color: Material.dialogColor
|
||||
border.width: 1
|
||||
border.color: Material.dividerColor
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 10
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
visible: card.title !== ""
|
||||
Layout.fillWidth: true
|
||||
Label { text: card.title; font.pixelSize: 16; font.weight: Font.DemiBold }
|
||||
Label {
|
||||
text: card.subtitle
|
||||
visible: card.subtitle !== ""
|
||||
font.pixelSize: 12
|
||||
opacity: 0.7
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: inner
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
spacing: 10
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// The filled Material button, painted explicitly.
|
||||
//
|
||||
// Relying on `highlighted: true` leaves the fill to the style, and under some
|
||||
// renderers that fill never appears — white text on a white surface. The
|
||||
// primary action on a page is exactly the thing that must never be invisible,
|
||||
// so it draws its own pill.
|
||||
import QtQuick
|
||||
import QtQuick.Controls.Material
|
||||
|
||||
Button {
|
||||
id: control
|
||||
|
||||
Material.foreground: "white"
|
||||
font.weight: Font.Medium
|
||||
leftPadding: 20
|
||||
rightPadding: 20
|
||||
implicitHeight: 40
|
||||
|
||||
background: Rectangle {
|
||||
radius: height / 2
|
||||
color: control.enabled ? Material.accent : Material.buttonDisabledColor
|
||||
opacity: control.pressed ? 0.82 : (control.hovered ? 0.92 : 1)
|
||||
Behavior on opacity { NumberAnimation { duration: 120 } }
|
||||
}
|
||||
|
||||
contentItem: Label {
|
||||
text: control.text
|
||||
font: control.font
|
||||
color: control.enabled ? "white" : Material.hintTextColor
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Transient messages. Material's own popup, anchored to the bottom.
|
||||
import QtQuick
|
||||
import QtQuick.Controls.Material
|
||||
|
||||
Popup {
|
||||
id: snackbar
|
||||
property string message: ""
|
||||
|
||||
function show(text) {
|
||||
message = text
|
||||
open()
|
||||
timer.restart()
|
||||
}
|
||||
|
||||
x: Math.round((parent.width - width) / 2)
|
||||
y: parent.height - height - 72
|
||||
width: Math.min(parent.width - 48, Math.max(260, label.implicitWidth + 48))
|
||||
padding: 16
|
||||
modal: false
|
||||
focus: false
|
||||
closePolicy: Popup.NoAutoClose
|
||||
Material.elevation: 6
|
||||
Material.roundedScale: Material.SmallScale
|
||||
|
||||
Label {
|
||||
id: label
|
||||
anchors.fill: parent
|
||||
text: snackbar.message
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Timer { id: timer; interval: 5000; onTriggered: snackbar.close() }
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// One number, read at a glance. Not a chart.
|
||||
import QtQuick
|
||||
import QtQuick.Controls.Material
|
||||
import QtQuick.Layouts
|
||||
|
||||
Pane {
|
||||
property string label: ""
|
||||
property string value: "—"
|
||||
property string caption: ""
|
||||
property color accent: Material.foreground
|
||||
|
||||
Material.elevation: 1
|
||||
Material.roundedScale: Material.LargeScale
|
||||
padding: 16
|
||||
Layout.fillWidth: true
|
||||
|
||||
background: Rectangle {
|
||||
radius: 16
|
||||
color: Material.dialogColor
|
||||
border.width: 1
|
||||
border.color: Material.dividerColor
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 2
|
||||
Label { text: label.toUpperCase(); font.pixelSize: 10; font.letterSpacing: 1; opacity: 0.6 }
|
||||
Label { text: value; font.pixelSize: 26; font.weight: Font.Light; color: accent }
|
||||
Label {
|
||||
text: caption
|
||||
visible: caption !== ""
|
||||
font.pixelSize: 11
|
||||
opacity: 0.7
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// The little "what is the timer doing" chip, shown in the rail and the toolbar.
|
||||
import QtQuick
|
||||
import QtQuick.Controls.Material
|
||||
|
||||
Rectangle {
|
||||
property bool running: false
|
||||
property bool paused: false
|
||||
property bool connected: true
|
||||
property string text: ""
|
||||
property color accent: "#2a78d6"
|
||||
|
||||
implicitWidth: Math.max(78, label.implicitWidth + 28)
|
||||
implicitHeight: 32
|
||||
radius: 16
|
||||
color: !connected ? "#e34948" : (running ? accent : "transparent")
|
||||
opacity: paused ? 0.6 : 1
|
||||
border.width: running || !connected ? 0 : 1
|
||||
border.color: Material.hintTextColor
|
||||
|
||||
Label {
|
||||
id: label
|
||||
anchors.centerIn: parent
|
||||
text: parent.text
|
||||
font.pixelSize: 13
|
||||
font.weight: Font.DemiBold
|
||||
font.family: "Roboto Mono, monospace"
|
||||
color: (parent.running || !parent.connected) ? "white" : Material.foreground
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// The countdown ring, drawn with Canvas.
|
||||
//
|
||||
// A hero number inside a progress arc: the number is what you read, the arc is
|
||||
// the glance. The arc colour is the only thing that says focus vs break.
|
||||
import QtQuick
|
||||
import QtQuick.Controls.Material
|
||||
|
||||
Item {
|
||||
id: dial
|
||||
property real progress: 0 // 0..1 elapsed
|
||||
property string clockText: "00:00"
|
||||
property string label: ""
|
||||
property string sublabel: ""
|
||||
property color accent: "#2a78d6"
|
||||
property bool paused: false
|
||||
|
||||
implicitWidth: 260
|
||||
implicitHeight: 260
|
||||
|
||||
onProgressChanged: canvas.requestPaint()
|
||||
onAccentChanged: canvas.requestPaint()
|
||||
onPausedChanged: canvas.requestPaint()
|
||||
|
||||
Canvas {
|
||||
id: canvas
|
||||
anchors.fill: parent
|
||||
antialiasing: true
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d")
|
||||
ctx.reset()
|
||||
var centre = Math.min(width, height) / 2
|
||||
var stroke = Math.max(10, centre * 0.11)
|
||||
var radius = centre - stroke
|
||||
|
||||
ctx.lineCap = "round"
|
||||
ctx.lineWidth = stroke
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = Qt.rgba(0.5, 0.5, 0.5, 0.18)
|
||||
ctx.arc(centre, centre, radius, 0, Math.PI * 2)
|
||||
ctx.stroke()
|
||||
|
||||
var remaining = 1 - Math.max(0, Math.min(1, dial.progress))
|
||||
if (remaining > 0) {
|
||||
ctx.beginPath()
|
||||
ctx.globalAlpha = dial.paused ? 0.45 : 1
|
||||
ctx.strokeStyle = dial.accent
|
||||
ctx.arc(centre, centre, radius, -Math.PI / 2, -Math.PI / 2 + remaining * Math.PI * 2)
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: 4
|
||||
width: parent.width * 0.72
|
||||
|
||||
Label {
|
||||
width: parent.width
|
||||
text: dial.label.toUpperCase()
|
||||
font.pixelSize: 11
|
||||
font.letterSpacing: 1.5
|
||||
opacity: 0.65
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
Label {
|
||||
width: parent.width
|
||||
text: dial.clockText
|
||||
font.pixelSize: Math.round(dial.width * 0.2)
|
||||
font.weight: Font.Light
|
||||
font.family: "Roboto Mono, monospace"
|
||||
opacity: dial.paused ? 0.6 : 1
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
Label {
|
||||
width: parent.width
|
||||
text: dial.sublabel
|
||||
visible: dial.sublabel !== ""
|
||||
font.pixelSize: 13
|
||||
opacity: 0.75
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user