mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
d4de39f3ea
NClock: allow style overwrite
443 lines
12 KiB
QML
443 lines
12 KiB
QML
import QtQuick
|
|
import QtQuick.Layouts
|
|
import Quickshell
|
|
import "../Helpers/ColorsConvert.js" as ColorsConvert
|
|
import qs.Commons
|
|
import qs.Widgets
|
|
|
|
Item {
|
|
id: root
|
|
|
|
property var now: Time.now
|
|
|
|
// Style: "analog" or "digital"
|
|
property string clockStyle: "analog"
|
|
|
|
// Show seconds progress ring (digital only)
|
|
property bool showProgress: true
|
|
|
|
// Color properties
|
|
property color backgroundColor: Color.mPrimary
|
|
property color clockColor: Color.mOnPrimary
|
|
|
|
property color secondHandColor: {
|
|
var defaultColor = Color.mError;
|
|
var bestContrast = 1.0; // 1.0 is "no contrast"
|
|
var bestColor = defaultColor;
|
|
var candidates = [Color.mSecondary, Color.mTertiary, Color.mError];
|
|
|
|
const minContrast = 1.149;
|
|
|
|
for (var i = 0; i < candidates.length; i++) {
|
|
var candidate = candidates[i];
|
|
var contrastClock = ColorsConvert.getContrastRatio(candidate.toString(), clockColor.toString());
|
|
if (contrastClock < minContrast) {
|
|
continue;
|
|
}
|
|
var contrastBg = ColorsConvert.getContrastRatio(candidate.toString(), backgroundColor.toString());
|
|
if (contrastBg < minContrast) {
|
|
continue;
|
|
}
|
|
|
|
var currentWorstContrast = Math.min(contrastBg, contrastClock);
|
|
|
|
if (currentWorstContrast > bestContrast) {
|
|
bestContrast = currentWorstContrast;
|
|
bestColor = candidate;
|
|
}
|
|
}
|
|
|
|
return bestColor;
|
|
}
|
|
|
|
property color progressColor: root.secondHandColor
|
|
|
|
// Font size properties for digital clock
|
|
property real hoursFontSize: Style.fontSizeXS
|
|
property real minutesFontSize: Style.fontSizeXXS
|
|
property int hoursFontWeight: Style.fontWeightBold
|
|
property int minutesFontWeight: Style.fontWeightBold
|
|
|
|
// Scale ratio for canvas line widths (used by desktop widget scaling)
|
|
property real scaleRatio: Style.uiScaleRatio
|
|
|
|
height: Math.round((Style.fontSizeXXXL * 1.9) / 2 * Style.uiScaleRatio) * 2
|
|
width: root.height
|
|
|
|
Loader {
|
|
id: clockLoader
|
|
anchors.fill: parent
|
|
|
|
sourceComponent: {
|
|
if (root.clockStyle === "analog")
|
|
return analogClockComponent;
|
|
if (root.clockStyle === "binary")
|
|
return binaryClockComponent;
|
|
return digitalClockComponent;
|
|
}
|
|
|
|
onLoaded: {
|
|
item.now = Qt.binding(function () {
|
|
return root.now;
|
|
});
|
|
item.backgroundColor = Qt.binding(function () {
|
|
return root.backgroundColor;
|
|
});
|
|
item.clockColor = Qt.binding(function () {
|
|
return root.clockColor;
|
|
});
|
|
if (item.hasOwnProperty("secondHandColor")) {
|
|
item.secondHandColor = Qt.binding(function () {
|
|
return root.secondHandColor;
|
|
});
|
|
}
|
|
if (item.hasOwnProperty("progressColor")) {
|
|
item.progressColor = Qt.binding(function () {
|
|
return root.progressColor;
|
|
});
|
|
}
|
|
if (item.hasOwnProperty("hoursFontSize")) {
|
|
item.hoursFontSize = Qt.binding(function () {
|
|
return root.hoursFontSize;
|
|
});
|
|
}
|
|
if (item.hasOwnProperty("minutesFontSize")) {
|
|
item.minutesFontSize = Qt.binding(function () {
|
|
return root.minutesFontSize;
|
|
});
|
|
}
|
|
if ("hoursFontWeight" in item) {
|
|
item.hoursFontWeight = Qt.binding(function () {
|
|
return root.hoursFontWeight;
|
|
});
|
|
}
|
|
if ("minutesFontWeight" in item) {
|
|
item.minutesFontWeight = Qt.binding(function () {
|
|
return root.minutesFontWeight;
|
|
});
|
|
}
|
|
if (item.hasOwnProperty("scaleRatio")) {
|
|
item.scaleRatio = Qt.binding(function () {
|
|
return root.scaleRatio;
|
|
});
|
|
}
|
|
if ("showProgress" in item) {
|
|
item.showProgress = Qt.binding(function () {
|
|
return root.showProgress;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Analog Clock Component
|
|
component NClockAnalog: Item {
|
|
property var now
|
|
property color backgroundColor: Color.mPrimary
|
|
property color clockColor: Color.mOnPrimary
|
|
property color secondHandColor: Color.mError
|
|
property real scaleRatio: Style.uiScaleRatio
|
|
anchors.fill: parent
|
|
|
|
Canvas {
|
|
id: clockCanvas
|
|
anchors.fill: parent
|
|
|
|
Connections {
|
|
target: Time
|
|
function onNowChanged() {
|
|
clockCanvas.requestPaint();
|
|
}
|
|
}
|
|
|
|
onPaint: {
|
|
var currentTime = Time.now;
|
|
var hours = currentTime.getHours();
|
|
var minutes = currentTime.getMinutes();
|
|
var seconds = currentTime.getSeconds();
|
|
|
|
const markAlpha = 0.7;
|
|
var ctx = getContext("2d");
|
|
ctx.reset();
|
|
ctx.translate(width / 2, height / 2);
|
|
var radius = Math.min(width, height) / 2;
|
|
|
|
// Hour marks
|
|
ctx.strokeStyle = Qt.alpha(clockColor, markAlpha);
|
|
ctx.lineWidth = 2 * scaleRatio;
|
|
var scaleFactor = 0.7;
|
|
|
|
for (var i = 0; i < 12; i++) {
|
|
var scaleFactor = 0.8;
|
|
if (i % 3 === 0) {
|
|
scaleFactor = 0.65;
|
|
}
|
|
ctx.save();
|
|
ctx.rotate(i * Math.PI / 6);
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, -radius * scaleFactor);
|
|
ctx.lineTo(0, -radius);
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
}
|
|
|
|
// Hour hand
|
|
ctx.save();
|
|
var hourAngle = (hours % 12 + minutes / 60) * Math.PI / 6;
|
|
ctx.rotate(hourAngle);
|
|
ctx.strokeStyle = clockColor;
|
|
ctx.lineWidth = 3 * scaleRatio;
|
|
ctx.lineCap = "round";
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, 0);
|
|
ctx.lineTo(0, -radius * 0.6);
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
|
|
// Minute hand
|
|
ctx.save();
|
|
var minuteAngle = (minutes + seconds / 60) * Math.PI / 30;
|
|
ctx.rotate(minuteAngle);
|
|
ctx.strokeStyle = clockColor;
|
|
ctx.lineWidth = 2 * scaleRatio;
|
|
ctx.lineCap = "round";
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, 0);
|
|
ctx.lineTo(0, -radius * 0.9);
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
|
|
// Second hand
|
|
ctx.save();
|
|
var secondAngle = seconds * Math.PI / 30;
|
|
ctx.rotate(secondAngle);
|
|
ctx.strokeStyle = secondHandColor;
|
|
ctx.lineWidth = 1.6 * scaleRatio;
|
|
ctx.lineCap = "round";
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, 0);
|
|
ctx.lineTo(0, -radius);
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
|
|
// Center dot
|
|
ctx.beginPath();
|
|
ctx.arc(0, 0, 3 * scaleRatio, 0, 2 * Math.PI);
|
|
ctx.fillStyle = clockColor;
|
|
ctx.fill();
|
|
}
|
|
|
|
Component.onCompleted: requestPaint()
|
|
}
|
|
}
|
|
|
|
// Digital Clock Component
|
|
component NClockDigital: Item {
|
|
property var now
|
|
property color backgroundColor: Color.mPrimary
|
|
property color clockColor: Color.mOnPrimary
|
|
property color progressColor: Color.mError
|
|
property real hoursFontSize: Style.fontSizeXS
|
|
property real minutesFontSize: Style.fontSizeXXS
|
|
property int hoursFontWeight: Style.fontWeightBold
|
|
property int minutesFontWeight: Style.fontWeightBold
|
|
property real scaleRatio: Style.uiScaleRatio
|
|
property bool showProgress: true
|
|
|
|
anchors.fill: parent
|
|
|
|
// Digital clock's seconds circular progress
|
|
Canvas {
|
|
id: secondsProgress
|
|
anchors.fill: parent
|
|
visible: showProgress
|
|
property real progress: now.getSeconds() / 60
|
|
onProgressChanged: requestPaint()
|
|
Connections {
|
|
target: Time
|
|
function onNowChanged() {
|
|
const total = now.getSeconds() * 1000 + now.getMilliseconds();
|
|
secondsProgress.progress = total / 60000;
|
|
}
|
|
}
|
|
onPaint: {
|
|
var ctx = getContext("2d");
|
|
var centerX = width / 2;
|
|
var centerY = height / 2;
|
|
var radius = Math.min(width, height) / 2 - 3 * scaleRatio;
|
|
ctx.reset();
|
|
|
|
// Background circle
|
|
ctx.beginPath();
|
|
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
|
|
ctx.lineWidth = 2.5 * scaleRatio;
|
|
ctx.strokeStyle = Qt.alpha(clockColor, 0.15);
|
|
ctx.stroke();
|
|
|
|
// Progress arc
|
|
ctx.beginPath();
|
|
ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progress * 2 * Math.PI);
|
|
ctx.lineWidth = 2.5 * scaleRatio;
|
|
ctx.strokeStyle = progressColor;
|
|
ctx.lineCap = "round";
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
|
|
// Digital clock
|
|
ColumnLayout {
|
|
anchors.centerIn: parent
|
|
spacing: -Style.marginXXS
|
|
|
|
NText {
|
|
text: {
|
|
var t = Settings.data.location.use12hourFormat ? I18n.locale.toString(now, "hh AP") : I18n.locale.toString(now, "HH");
|
|
return t.split(" ")[0];
|
|
}
|
|
|
|
pointSize: hoursFontSize
|
|
font.weight: hoursFontWeight
|
|
color: clockColor
|
|
family: Settings.data.ui.fontFixed
|
|
Layout.alignment: Qt.AlignHCenter
|
|
}
|
|
|
|
NText {
|
|
text: Qt.formatTime(now, "mm")
|
|
pointSize: minutesFontSize
|
|
font.weight: minutesFontWeight
|
|
color: clockColor
|
|
family: Settings.data.ui.fontFixed
|
|
Layout.alignment: Qt.AlignHCenter
|
|
}
|
|
}
|
|
}
|
|
|
|
// Binary Clock Component
|
|
component NClockBinary: Item {
|
|
property var now
|
|
property color backgroundColor
|
|
property color clockColor: Color.mOnPrimary
|
|
|
|
anchors.fill: parent
|
|
|
|
readonly property int h: now.getHours()
|
|
readonly property int m: now.getMinutes()
|
|
readonly property int s: now.getSeconds()
|
|
|
|
// BCD (Binary Coded Decimal) Format:
|
|
// H1 H2 : M1 M2 : S1 S2
|
|
// H1 (tens): 0-2 (2 bits)
|
|
// H2 (ones): 0-9 (4 bits)
|
|
// M1 (tens): 0-5 (3 bits)
|
|
// M2 (ones): 0-9 (4 bits)
|
|
// S1 (tens): 0-5 (3 bits)
|
|
// S2 (ones): 0-9 (4 bits)
|
|
|
|
RowLayout {
|
|
anchors.centerIn: parent
|
|
spacing: parent.width * 0.05
|
|
|
|
// Hours
|
|
RowLayout {
|
|
spacing: parent.parent.width * 0.02
|
|
BinaryColumn {
|
|
value: Math.floor(h / 10)
|
|
bits: 2
|
|
dotSize: root.width * 0.08
|
|
activeColor: clockColor
|
|
Layout.alignment: Qt.AlignBottom
|
|
}
|
|
BinaryColumn {
|
|
value: h % 10
|
|
bits: 4
|
|
dotSize: root.width * 0.08
|
|
activeColor: clockColor
|
|
Layout.alignment: Qt.AlignBottom
|
|
}
|
|
}
|
|
|
|
// Minutes
|
|
RowLayout {
|
|
spacing: parent.parent.width * 0.02
|
|
BinaryColumn {
|
|
value: Math.floor(m / 10)
|
|
bits: 3
|
|
dotSize: root.width * 0.08
|
|
activeColor: clockColor
|
|
Layout.alignment: Qt.AlignBottom
|
|
}
|
|
BinaryColumn {
|
|
value: m % 10
|
|
bits: 4
|
|
dotSize: root.width * 0.08
|
|
activeColor: clockColor
|
|
Layout.alignment: Qt.AlignBottom
|
|
}
|
|
}
|
|
|
|
// Seconds
|
|
RowLayout {
|
|
spacing: parent.parent.width * 0.02
|
|
BinaryColumn {
|
|
value: Math.floor(s / 10)
|
|
bits: 3
|
|
dotSize: root.width * 0.08
|
|
activeColor: clockColor
|
|
Layout.alignment: Qt.AlignBottom
|
|
}
|
|
BinaryColumn {
|
|
value: s % 10
|
|
bits: 4
|
|
dotSize: root.width * 0.08
|
|
activeColor: clockColor
|
|
Layout.alignment: Qt.AlignBottom
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
component BinaryColumn: Column {
|
|
property int value: 0
|
|
property int bits: 4
|
|
property real dotSize: 10
|
|
property color activeColor: "white"
|
|
|
|
spacing: dotSize * 0.4
|
|
|
|
Repeater {
|
|
model: bits
|
|
|
|
Rectangle {
|
|
property int bitIndex: (bits - 1) - index
|
|
property bool isActive: (value >> bitIndex) & 1
|
|
|
|
width: dotSize
|
|
height: dotSize
|
|
radius: dotSize / 2
|
|
color: isActive ? activeColor : Qt.alpha(activeColor, 0.2)
|
|
|
|
Behavior on color {
|
|
ColorAnimation {
|
|
duration: 200
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: analogClockComponent
|
|
NClockAnalog {}
|
|
}
|
|
|
|
Component {
|
|
id: digitalClockComponent
|
|
NClockDigital {}
|
|
}
|
|
|
|
Component {
|
|
id: binaryClockComponent
|
|
NClockBinary {}
|
|
}
|
|
}
|