Files
noctalia-shell/Widgets/NClock.qml
T
MannuVilasara 4a295cdfb9 Binary Clock
fix://
2025-12-25 20:37:51 +05:30

403 lines
11 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"
// 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
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;
});
}
}
}
// Analog Clock Component
component NClockAnalog: Item {
property var now
property color backgroundColor: Color.mPrimary
property color clockColor: Color.mOnPrimary
property color secondHandColor: Color.mError
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 * Style.uiScaleRatio;
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 * Style.uiScaleRatio;
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 * Style.uiScaleRatio;
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 * Style.uiScaleRatio;
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 * Style.uiScaleRatio, 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
anchors.fill: parent
// Digital clock's seconds circular progress
Canvas {
id: secondsProgress
anchors.fill: parent
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;
ctx.reset();
// Background circle
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
ctx.lineWidth = 2.5;
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;
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: Style.fontWeightBold
color: clockColor
family: Settings.data.ui.fontFixed
Layout.alignment: Qt.AlignHCenter
}
NText {
text: Qt.formatTime(now, "mm")
pointSize: minutesFontSize
font.weight: Style.fontWeightBold
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 {}
}
}