Feat: Dashboard tabs and repositioning. New processes view. Settings backend changes #158

Merged
zach merged 21 commits from feat/dashboard-tabs-processes-and-settings-backend into main 2026-09-30 16:51:16 +02:00
297 changed files with 18517 additions and 6021 deletions
+21 -42
View File
@@ -80,49 +80,29 @@ Slider {
property bool moving property bool moving
function update(): void {
animate = !moving;
binding.when = moving;
font.pointSize = moving ? Tokens.font.size.small : Tokens.font.size.larger;
font.family = moving ? Config.appearance.font.family.sans : Config.appearance.font.family.material;
}
anchors.centerIn: parent anchors.centerIn: parent
anchors.verticalCenterOffset: 1
text: moving ? Math.round(root.value * 100) : root.icon
color: Colors.palette.m3inverseOnSurface color: Colors.palette.m3inverseOnSurface
text: root.icon font: moving ? Tokens.font.body.small : Tokens.font.icon.medium
onMovingChanged: anim.restart() Behavior on moving {
SequentialAnimation {
Binding { Anim {
id: binding target: icon
property: "scale"
property: "text" to: 0.3
target: icon duration: Tokens.anim.durations.small / 2
value: Math.round(root.value * root.multiplier) easing: Tokens.anim.standardAccel
when: false }
} PropertyAction {}
Anim {
SequentialAnimation { target: icon
id: anim property: "scale"
to: 1
Anim { duration: Tokens.anim.durations.normal / 2
duration: Tokens.anim.durations.normal / 2 easing: Tokens.anim.standardDecel
easing: Tokens.anim.standardAccel }
property: "scale"
target: icon
to: 0
}
ScriptAction {
script: icon.update()
}
Anim {
duration: Tokens.anim.durations.normal / 2
easing: Tokens.anim.standardDecel
property: "scale"
target: icon
to: 1
} }
} }
} }
@@ -130,7 +110,7 @@ Slider {
} }
Behavior on value { Behavior on value {
Anim { Anim {
duration: Tokens.anim.durations.large type: Anim.StandardLarge
} }
} }
@@ -138,7 +118,6 @@ Slider {
onValueChanged: { onValueChanged: {
if (!initialized) { if (!initialized) {
initialized = true; initialized = true;
oldValue = value;
return; return;
} }
+2 -6
View File
@@ -20,11 +20,7 @@ CustomRect {
property color disabledColor: Qt.alpha(Colors.palette.m3onSurface, 0.1) property color disabledColor: Qt.alpha(Colors.palette.m3onSurface, 0.1)
property color disabledOnColor: Qt.alpha(Colors.palette.m3onSurface, 0.38) property color disabledOnColor: Qt.alpha(Colors.palette.m3onSurface, 0.38)
property bool fillWidth property bool fillWidth
property font font: ({ property font font: Tokens.font.body.small
family: Config.appearance.font.family.sans,
pointSize: Tokens.font.size.larger,
bold: false
})
property real horizontalPadding: padding property real horizontalPadding: padding
readonly property alias hovered: stateLayer.containsMouse readonly property alias hovered: stateLayer.containsMouse
required implicitHeight required implicitHeight
@@ -55,7 +51,7 @@ CustomRect {
if (internalChecked) if (internalChecked)
return checkedRadius; return checkedRadius;
if (isRound) if (isRound)
return (height || implicitHeight) / 2 * Math.min(1, Tokens.rounding.scale); return implicitHeight / 2 * Math.min(1, Tokens.rounding.scale);
return defaultRadius; return defaultRadius;
} }
+5 -5
View File
@@ -46,10 +46,10 @@ Item {
} }
function syncFromPenColor() { function syncFromPenColor() {
if (!drawing.drawingState) if (!drawing.screenState)
return; return;
const c = drawing.drawingState.penColor; const c = drawing.screenState.penColor;
if (c.hsvSaturation > 0) { if (c.hsvSaturation > 0) {
currentHue = c.hsvHue; currentHue = c.hsvHue;
@@ -86,7 +86,7 @@ Item {
currentHue = relative / arcSweep; currentHue = relative / arcSweep;
lastChromaticHue = currentHue; lastChromaticHue = currentHue;
drawing.drawingState.penColor = Qt.hsva(currentHue, drawing.drawingState.penColor.hsvSaturation, drawing.drawingState.penColor.hsvValue, drawing.drawingState.penColor.a); drawing.screenState.penColor = Qt.hsva(currentHue, drawing.screenState.penColor.hsvSaturation, drawing.screenState.penColor.hsvValue, drawing.screenState.penColor.a);
} }
implicitHeight: 180 implicitHeight: 180
@@ -112,7 +112,7 @@ Item {
root.syncFromPenColor(); root.syncFromPenColor();
} }
target: root.drawing.drawingState target: root.drawing.screenState
} }
Canvas { Canvas {
@@ -152,7 +152,7 @@ Item {
CustomRect { CustomRect {
anchors.centerIn: parent anchors.centerIn: parent
color: root.drawing?.drawingState.penColor color: root.drawing?.screenState.penColor
implicitHeight: implicitWidth implicitHeight: implicitWidth
implicitWidth: canvas.height - root.handleSize - Tokens.padding.extraLarge * 2 implicitWidth: canvas.height - root.handleSize - Tokens.padding.extraLarge * 2
radius: Tokens.rounding.full radius: Tokens.rounding.full
+2 -2
View File
@@ -1,12 +1,12 @@
import QtQuick import QtQuick
import qs.Helpers import qs.Services
Flickable { Flickable {
id: root id: root
property bool doneFakeFlick property bool doneFakeFlick
interactive: !Visibilities.getForActive().isDrawing interactive: !ShellState.forActive().isDrawing
maximumFlickVelocity: 3000 maximumFlickVelocity: 3000
rebound: Transition { rebound: Transition {
+2 -2
View File
@@ -1,12 +1,12 @@
import QtQuick import QtQuick
import qs.Helpers import qs.Services
ListView { ListView {
id: root id: root
property bool doneFakeFlick property bool doneFakeFlick
interactive: !Visibilities.getForActive()?.isDrawing interactive: !ShellState.forActive()?.isDrawing
maximumFlickVelocity: 3000 maximumFlickVelocity: 3000
rebound: Transition { rebound: Transition {
+3 -4
View File
@@ -6,7 +6,7 @@ import qs.Services
RadioButton { RadioButton {
id: root id: root
font.pointSize: Tokens.font.size.normal font: Tokens.font.body.medium
implicitHeight: Math.max(implicitIndicatorHeight, implicitContentHeight) implicitHeight: Math.max(implicitIndicatorHeight, implicitContentHeight)
implicitWidth: implicitIndicatorWidth + implicitContentWidth + contentItem.anchors.leftMargin implicitWidth: implicitIndicatorWidth + implicitContentWidth + contentItem.anchors.leftMargin
@@ -14,7 +14,7 @@ RadioButton {
anchors.left: outerCircle.right anchors.left: outerCircle.right
anchors.leftMargin: 10 anchors.leftMargin: 10
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
font.pointSize: root.font.pointSize font: root.font
text: root.text text: root.text
} }
indicator: Rectangle { indicator: Rectangle {
@@ -29,8 +29,7 @@ RadioButton {
radius: Tokens.rounding.full radius: Tokens.rounding.full
Behavior on border.color { Behavior on border.color {
CAnim { CAnim {}
}
} }
StateLayer { StateLayer {
+3 -2
View File
@@ -1,6 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Templates import QtQuick.Templates
import qs.Helpers import qs.Services
import ZShell.Config import ZShell.Config
import qs.Services import qs.Services
@@ -17,7 +17,8 @@ ScrollBar {
property bool shouldBeActive property bool shouldBeActive
readonly property real travelScale: root.rawTravel > 0 ? root.effectiveTravel / root.rawTravel : 0 readonly property real travelScale: root.rawTravel > 0 ? root.effectiveTravel / root.rawTravel : 0
enabled: !Visibilities.getForActive().isDrawing enabled: !ShellState.forActive().isDrawing
implicitHeight: Tokens.padding.extraSmall * 2
implicitWidth: Tokens.padding.extraSmall * 2 implicitWidth: Tokens.padding.extraSmall * 2
contentItem: Item { contentItem: Item {
+9 -7
View File
@@ -100,15 +100,19 @@ Slider {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.top: parent.top anchors.top: parent.top
color: root.insetColor color: root.insetColor
font.pixelSize: Tokens.font.size.smaller * 2 fontStyle: {
if (root.height < 96)
return Tokens.font.icon.pixelSize(24).build();
else
return Tokens.font.icon.pixelSize(32).build();
}
text: root.insetIcon text: root.insetIcon
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
visible: !root.wavy && root.insetIcon !== "" visible: !root.wavy && root.insetIcon !== ""
x: Tokens.spacing.extraSmall + dockT * (handle.x + Tokens.spacing.small) x: Tokens.spacing.extraSmall + dockT * (handle.x + Tokens.spacing.small)
Behavior on dockT { Behavior on dockT {
Anim { Anim {}
}
} }
} }
@@ -138,8 +142,7 @@ Slider {
startX: x startX: x
Behavior on color { Behavior on color {
CAnim { CAnim {}
}
} }
Anim on waveProgress { Anim on waveProgress {
duration: root.waveDuration duration: root.waveDuration
@@ -156,8 +159,7 @@ Slider {
Behavior on filledWidth { Behavior on filledWidth {
id: widthBehavior id: widthBehavior
Anim { Anim {}
}
} }
Component.onCompleted: { Component.onCompleted: {
+3 -6
View File
@@ -14,15 +14,13 @@ Text {
property real animateTo: 1 property real animateTo: 1
color: Colors.palette.m3onSurface color: Colors.palette.m3onSurface
font.family: Config.appearance.font.family.sans font: Tokens.font.body.small
font.pointSize: Tokens.font.size.normal
linkColor: Colors.palette.m3onPrimaryFixedVariant linkColor: Colors.palette.m3onPrimaryFixedVariant
renderType: Text.NativeRendering renderType: Text.NativeRendering
textFormat: Text.PlainText textFormat: Text.PlainText
Behavior on color { Behavior on color {
CAnim { CAnim {}
}
} }
Behavior on text { Behavior on text {
enabled: root.animate enabled: root.animate
@@ -34,8 +32,7 @@ Text {
type: Anim.FastEffects type: Anim.FastEffects
} }
PropertyAction { PropertyAction {}
}
TAnim { TAnim {
target: root target: root
+26 -22
View File
@@ -15,28 +15,33 @@ TextFieldBase {
readonly property int clampedRadius: Math.min(horizontalPadding, Math.min(width, height) / 2, radius) readonly property int clampedRadius: Math.min(horizontalPadding, Math.min(width, height) / 2, radius)
readonly property string effectiveSupportingText: isError && errorText ? errorText : supportingText readonly property string effectiveSupportingText: isError && errorText ? errorText : supportingText
readonly property int filledOffset: type === CustomTextField.Filled ? Tokens.spacing.small : 0
readonly property int leadingOffset: leadingIcon ? leadingIconLoader.width + leadingIconLoader.anchors.leftMargin : 0
readonly property real smallFontScale: smallFontSize / font.pointSize
readonly property int supportingTextOffset: effectiveSupportingText ? supportingTextLoader.height + Tokens.spacing.extraSmall : 0
readonly property int trailingOffset: trailingIcon ? trailingIconLoader.width + trailingIconLoader.anchors.rightMargin : 0
readonly property bool valid: !validate || (!text && emptyIsValid) || (validate instanceof RegExp ? validate.test(text) : !!validate(text))
property int horizontalPadding: Tokens.padding.large
property int verticalPadding: Tokens.padding.large
property int radius: Tokens.rounding.small
property string supportingText
property string trailingIcon
property int smallFontSize: Tokens.font.label.small.pointSize
property int type: CustomTextField.Outlined
property var validate // Regex or function
property bool emptyIsValid: true property bool emptyIsValid: true
property string errorText property string errorText
readonly property int filledOffset: type === CustomTextField.Filled ? Tokens.spacing.small : 0
readonly property int horizontalPadding: Tokens.padding.large
property bool isError property bool isError
property string leadingIcon property string leadingIcon
readonly property int leadingOffset: leadingIcon ? leadingIconLoader.width + leadingIconLoader.anchors.leftMargin : 0
property int radius: Tokens.rounding.small
readonly property real smallFontScale: smallFontSize / font.pointSize
property int smallFontSize: Tokens.font.size.small
property string supportingText
readonly property int supportingTextOffset: effectiveSupportingText ? supportingTextLoader.height + Tokens.spacing.extraSmall : 0
property string trailingIcon
readonly property int trailingOffset: trailingIcon ? trailingIconLoader.width + trailingIconLoader.anchors.rightMargin : 0
property int type: CustomTextField.Outlined
readonly property bool valid: !validate || (!text && emptyIsValid) || (validate instanceof RegExp ? validate.test(text) : !!validate(text))
property var validate // Regex or function
bottomPadding: Tokens.padding.large + supportingTextOffset - filledOffset bottomPadding: verticalPadding + supportingTextOffset - filledOffset
leftPadding: horizontalPadding + leadingOffset leftPadding: horizontalPadding + leadingOffset
rightPadding: horizontalPadding + trailingOffset rightPadding: horizontalPadding + trailingOffset
topPadding: Tokens.padding.large + filledOffset topPadding: verticalPadding + filledOffset
background: Loader { background: Loader {
anchors.bottomMargin: root.supportingTextOffset anchors.bottomMargin: root.supportingTextOffset
@@ -146,6 +151,7 @@ TextFieldBase {
sourceComponent: MaterialIcon { sourceComponent: MaterialIcon {
color: Colors.palette.m3onSurfaceVariant color: Colors.palette.m3onSurfaceVariant
text: root.leadingIcon text: root.leadingIcon
fontStyle: Tokens.font.icon.builders.medium.scale(0.9).build()
} }
} }
@@ -160,6 +166,7 @@ TextFieldBase {
sourceComponent: MaterialIcon { sourceComponent: MaterialIcon {
color: Colors.palette.m3onSurfaceVariant color: Colors.palette.m3onSurfaceVariant
text: root.trailingIcon text: root.trailingIcon
fontStyle: Tokens.font.icon.builders.medium.scale(0.9).build()
} }
} }
} }
@@ -174,7 +181,7 @@ TextFieldBase {
sourceComponent: CustomText { sourceComponent: CustomText {
color: root.isError ? Colors.palette.m3error : Colors.palette.m3onSurfaceVariant color: root.isError ? Colors.palette.m3error : Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.small font: Tokens.font.label.small
text: root.effectiveSupportingText text: root.effectiveSupportingText
} }
} }
@@ -212,12 +219,10 @@ TextFieldBase {
} }
} }
Behavior on strokeColor { Behavior on strokeColor {
CAnim { CAnim {}
}
} }
Behavior on strokeWidth { Behavior on strokeWidth {
Anim { Anim {}
}
} }
PathLine { PathLine {
@@ -292,8 +297,7 @@ TextFieldBase {
implicitHeight: root.activeFocus ? 2 : 1 implicitHeight: root.activeFocus ? 2 : 1
Behavior on implicitHeight { Behavior on implicitHeight {
Anim { Anim {}
}
} }
} }
} }
+2 -1
View File
@@ -8,6 +8,7 @@ ButtonBase {
property alias icon: label.text property alias icon: label.text
readonly property alias label: label readonly property alias label: label
font: Tokens.font.icon.medium
activeColor: type === IconButton.Filled ? Colors.palette.m3primary : Colors.palette.m3secondary activeColor: type === IconButton.Filled ? Colors.palette.m3primary : Colors.palette.m3secondary
activeOnColor: type === IconButton.Filled ? Colors.palette.m3onPrimary : type === IconButton.Tonal ? Colors.palette.m3onSecondary : Colors.palette.m3primary activeOnColor: type === IconButton.Filled ? Colors.palette.m3onPrimary : type === IconButton.Tonal ? Colors.palette.m3onSecondary : Colors.palette.m3primary
implicitHeight: { implicitHeight: {
@@ -36,7 +37,7 @@ ButtonBase {
anchors.verticalCenterOffset: 1 anchors.verticalCenterOffset: 1
color: root.onColor color: root.onColor
fill: !root.isToggle || root.internalChecked ? 1 : 0 fill: !root.isToggle || root.internalChecked ? 1 : 0
font.pointSize: root.font.pointSize fontStyle: root.font
Behavior on fill { Behavior on fill {
Anim { Anim {
+1 -2
View File
@@ -46,10 +46,9 @@ ButtonBase {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
color: root.onColor color: root.onColor
fill: root.internalChecked ? 1 : 0 fill: root.internalChecked ? 1 : 0
font: { fontStyle: {
const f = Qt.font(root.font); const f = Qt.font(root.font);
f.pointSize = Math.round(root.font.pointSize * 1.2); f.pointSize = Math.round(root.font.pointSize * 1.2);
f.family = "Material Symbols Rounded";
return f; return f;
} }
+467
View File
@@ -0,0 +1,467 @@
pragma ComponentBehavior: Bound
import QtQuick
Item {
id: root
required property list<var> values
property int xSteps: 24
property real yMin: NaN
property real yMax: NaN
property real yPaddingTop: 0
property real yPaddingBottom: 0
property int verticalLineStep: 0
property int horizontalLineStep: 0
property bool firstXLine: true
property bool lastXLine: false
property int minHorizontalLines: 0
property var xLabel: null
property var yLabel: null
property real smoothing: 0
property bool curveSmoothing: true
property real markerX: -1
property real markerGap: 2 // px gap between line and marker dot edge, each side
property color lineColor: "white"
property color gridColor: "white"
property color markerColor: "white"
property color labelColor: "white"
property real gridAlpha: 0.12
property real labelAlpha: 0.6
property real fillAlpha: 0.15
property real lineWidth: 2
property real markerDotRadius: 4
property real labelFontSize: 10
Canvas {
id: canvas
anchors.fill: parent
renderStrategy: Canvas.Immediate
onWidthChanged: requestPaint()
onHeightChanged: requestPaint()
function strokeCurve(ctx, pts) {
ctx.beginPath();
ctx.moveTo(pts[0].x, pts[0].y);
if (root.curveSmoothing && pts.length > 2) {
const k = 1 / 6;
const n = pts.length;
for (let i = 0; i < n - 1; i++) {
const p0 = pts[Math.max(i - 1, 0)];
const p1 = pts[i];
const p2 = pts[i + 1];
const p3 = pts[Math.min(i + 2, n - 1)];
let c1x = p1.x + (p2.x - p0.x) * k;
let c1y = p1.y + (p2.y - p0.y) * k;
let c2x = p2.x - (p3.x - p1.x) * k;
let c2y = p2.y - (p3.y - p1.y) * k;
const minY = Math.min(p1.y, p2.y);
const maxY = Math.max(p1.y, p2.y);
c1y = Math.max(minY, Math.min(maxY, c1y));
c2y = Math.max(minY, Math.min(maxY, c2y));
ctx.bezierCurveTo(c1x, c1y, c2x, c2y, p2.x, p2.y);
}
} else {
for (let i = 0; i < pts.length - 1; i++) {
const p0 = pts[i];
const p1 = pts[i + 1];
const cx = (p0.x + p1.x) * 0.5;
ctx.bezierCurveTo(cx, p0.y, cx, p1.y, p1.x, p1.y);
}
}
}
// Control points for segment i (pts[i] -> pts[i+1]), matching strokeCurve
function segmentControls(pts, i) {
const p0 = pts[Math.max(i - 1, 0)];
const p1 = pts[i];
const p2 = pts[i + 1];
const p3 = pts[Math.min(i + 2, pts.length - 1)];
if (root.curveSmoothing && pts.length > 2) {
const k = 1 / 6;
let c1x = p1.x + (p2.x - p0.x) * k;
let c1y = p1.y + (p2.y - p0.y) * k;
let c2x = p2.x - (p3.x - p1.x) * k;
let c2y = p2.y - (p3.y - p1.y) * k;
const minY = Math.min(p1.y, p2.y);
const maxY = Math.max(p1.y, p2.y);
c1y = Math.max(minY, Math.min(maxY, c1y));
c2y = Math.max(minY, Math.min(maxY, c2y));
return [c1x, c1y, c2x, c2y];
}
const cx = (p1.x + p2.x) * 0.5;
return [cx, p1.y, cx, p2.y];
}
// Point on the curve at a given x (x is monotonic per segment)
function pointAtX(pts, targetX) {
let j = pts.length - 2;
for (let i = 0; i < pts.length - 1; i++) {
if (targetX <= pts[i + 1].x) {
j = i;
break;
}
}
const p1 = pts[j];
const p2 = pts[j + 1];
const [c1x, c1y, c2x, c2y] = segmentControls(pts, j);
let tlo = 0;
let thi = 1;
for (let it = 0; it < 24; it++) {
const tm = (tlo + thi) / 2;
const u = 1 - tm;
const x = u * u * u * p1.x + 3 * u * u * tm * c1x + 3 * u * tm * tm * c2x + tm * tm * tm * p2.x;
if (x < targetX)
tlo = tm;
else
thi = tm;
}
const t = (tlo + thi) / 2;
const u = 1 - t;
const y = u * u * u * p1.y + 3 * u * u * t * c1y + 3 * u * t * t * c2y + t * t * t * p2.y;
return { x: targetX, y };
}
// Part of the curve left/right of a cut x, including the cut point
function cutLeft(pts, targetX) {
if (targetX <= pts[0].x)
return [];
if (targetX >= pts[pts.length - 1].x)
return pts.slice();
let j = pts.length - 2;
for (let i = 0; i < pts.length - 1; i++) {
if (targetX <= pts[i + 1].x) {
j = i;
break;
}
}
const left = pts.slice(0, j + 1);
if (targetX > pts[j].x)
left.push(pointAtX(pts, targetX));
return left;
}
function cutRight(pts, targetX) {
if (targetX >= pts[pts.length - 1].x)
return [];
if (targetX <= pts[0].x)
return pts.slice();
let j = pts.length - 2;
for (let i = 0; i < pts.length - 1; i++) {
if (targetX <= pts[i + 1].x) {
j = i;
break;
}
}
const right = pts.slice(j + 1);
if (targetX < pts[j + 1].x)
right.unshift(pointAtX(pts, targetX));
return right;
}
onPaint: {
const ctx = getContext("2d");
ctx.reset();
const W = width;
const H = height;
if (W < 2 || H < 2)
return;
const padLeft = root.labelFontSize * 3.4;
const padRight = 2;
const padTop = 2;
const padBottom = root.labelFontSize + 8;
const plot = {
x: padLeft,
y: padTop,
w: W - padLeft - padRight,
h: H - padTop - padBottom
};
if (plot.w < 2 || plot.h < 2)
return;
// Auto-fit: NaN yMin/yMax derive from the data, snapped to the
// horizontal step grid, honoring minHorizontalLines
let lo = root.yMin;
let hi = root.yMax;
const loAuto = Number.isNaN(lo);
const hiAuto = Number.isNaN(hi);
if (loAuto || hiAuto) {
let dmin = Infinity;
let dmax = -Infinity;
for (const v of root.values) {
if (v == null)
continue;
if (v < dmin)
dmin = v;
if (v > dmax)
dmax = v;
}
if (dmin === Infinity) {
dmin = 0;
dmax = 1;
}
const step = root.horizontalLineStep;
const minSteps = root.minHorizontalLines > 1 ? root.minHorizontalLines - 1 : 0;
if (step > 0) {
if (hiAuto) {
const baseLo = loAuto ? Math.floor(dmin / step) * step : lo;
const span = Math.max(Math.ceil((dmax - baseLo) / step), minSteps, 1);
lo = baseLo;
hi = baseLo + span * step;
} else {
const span = Math.max(Math.ceil((hi - dmin) / step), minSteps, 1);
lo = hi - span * step;
}
} else {
if (loAuto)
lo = dmin;
if (hiAuto)
hi = dmax;
if (hi <= lo)
hi = lo + 1;
}
}
const scaleBottom = lo - root.yPaddingBottom;
const scaleTop = hi + root.yPaddingTop;
const range = scaleTop - scaleBottom;
if (range <= 0 || root.xSteps < 1)
return;
let series = root.values;
if (root.smoothing > 0 && series.length > 0) {
const radius = Math.round(root.smoothing);
const raw = series;
series = raw.map((v, i) => {
if (v == null)
return null;
let sum = 0;
let n = 0;
for (let j = -radius; j <= radius; j++) {
const u = raw[i + j];
if (u != null) {
sum += u;
n++;
}
}
return n > 0 ? sum / n : null;
});
}
const xAt = i => plot.x + (i / root.xSteps) * plot.w;
const yAt = v => plot.y + (1 - (v - scaleBottom) / range) * plot.h;
const crisp = v => Math.floor(v) + 0.5;
// 1. Grid lines
ctx.strokeStyle = Qt.alpha(root.gridColor, root.gridAlpha);
ctx.lineWidth = 1;
if (root.verticalLineStep > 0) {
for (let i = 0; i <= root.xSteps; i += root.verticalLineStep) {
if (i === 0 && !root.firstXLine)
continue;
if (i === root.xSteps && !root.lastXLine)
continue;
const x = crisp(xAt(i));
ctx.beginPath();
ctx.moveTo(x, plot.y);
ctx.lineTo(x, H);
ctx.stroke();
}
}
// Lines extend down into yPaddingBottom, snapped to the grid
const vStart = lo - (root.yPaddingBottom > 0 ? Math.floor(root.yPaddingBottom / root.horizontalLineStep) * root.horizontalLineStep : 0);
if (root.horizontalLineStep > 0) {
for (let v = vStart; v <= hi + 0.001; v += root.horizontalLineStep) {
const y = crisp(yAt(v));
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(plot.x + plot.w, y);
ctx.stroke();
}
}
// 2. Labels
ctx.fillStyle = Qt.alpha(root.labelColor, root.labelAlpha);
ctx.font = root.labelFontSize + "px sans-serif";
ctx.textAlign = "left";
ctx.textBaseline = "top";
if (root.verticalLineStep > 0 && root.xLabel) {
for (let i = 0; i <= root.xSteps; i += root.verticalLineStep) {
if (i === 0 && !root.firstXLine)
continue;
if (i === root.xSteps && !root.lastXLine)
continue;
const text = root.xLabel(i);
if (text)
ctx.fillText(text, xAt(i) + 4, plot.y + plot.h + 4);
}
}
ctx.textBaseline = "bottom";
if (root.horizontalLineStep > 0 && root.yLabel) {
for (let v = vStart; v <= hi + 0.001; v += root.horizontalLineStep) {
const text = root.yLabel(v);
if (text)
ctx.fillText(text, 2, yAt(v) - 2);
}
}
// 3. Curve, split into segments at null gaps
const segments = [];
let current = null;
for (let i = 0; i < series.length; i++) {
if (series[i] == null) {
if (current)
segments.push(current);
current = null;
continue;
}
const pt = {
x: xAt(i),
y: yAt(series[i])
};
if (!current)
current = [];
current.push(pt);
}
if (current)
segments.push(current);
ctx.lineCap = "round";
ctx.lineJoin = "round";
const hasMarker = root.markerX >= 0 && root.markerX <= root.xSteps;
const markerPx = hasMarker ? xAt(root.markerX) : 0;
let gapPx = 0;
if (hasMarker) {
// markerGap is measured from the dot edge, perpendicular to the
// curve; project onto x via the local curve slope
let slope = 0;
const seg = segments.find(s => s[0].x <= markerPx && markerPx <= s[s.length - 1].x);
if (seg && seg.length > 2 && markerPx - 1 >= seg[0].x && markerPx + 1 <= seg[seg.length - 1].x)
slope = (pointAtX(seg, markerPx + 1).y - pointAtX(seg, markerPx - 1).y) / 2;
gapPx = (root.markerDotRadius + root.markerGap) / Math.sqrt(1 + slope * slope);
}
for (const seg of segments) {
if (seg.length < 2)
continue;
// Fill stays continuous under the marker
ctx.fillStyle = Qt.alpha(root.lineColor, root.fillAlpha);
strokeCurve(ctx, seg);
ctx.lineTo(seg[seg.length - 1].x, plot.y + plot.h);
ctx.lineTo(seg[0].x, plot.y + plot.h);
ctx.closePath();
ctx.fill();
// Stroke has a gap around the marker
const pieces = hasMarker ? [cutLeft(seg, markerPx - gapPx), cutRight(seg, markerPx + gapPx)] : [seg];
for (const pts of pieces) {
if (pts.length < 2)
continue;
ctx.strokeStyle = root.lineColor;
ctx.lineWidth = root.lineWidth;
strokeCurve(ctx, pts);
ctx.stroke();
}
}
// 4. Marker: dashed vertical + dot
if (root.markerX >= 0 && root.markerX <= root.xSteps) {
const x = markerPx;
ctx.setLineDash([4, 4]);
ctx.strokeStyle = Qt.alpha(root.markerColor, 0.6);
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(crisp(x), plot.y);
ctx.lineTo(crisp(x), H);
ctx.stroke();
ctx.setLineDash([]);
const i0 = Math.floor(root.markerX);
const v0 = series[i0];
const v1 = series[i0 + 1];
let y = null;
if (v0 != null && v1 != null)
y = yAt(v0 + (v1 - v0) * (root.markerX - i0));
else if (v0 != null)
y = yAt(v0);
if (y != null) {
ctx.fillStyle = root.markerColor;
ctx.beginPath();
ctx.arc(x, y, root.markerDotRadius, 0, Math.PI * 2);
ctx.fill();
}
}
}
}
onValuesChanged: canvas.requestPaint()
onXStepsChanged: canvas.requestPaint()
onYMinChanged: canvas.requestPaint()
onYMaxChanged: canvas.requestPaint()
onYPaddingTopChanged: canvas.requestPaint()
onYPaddingBottomChanged: canvas.requestPaint()
onVerticalLineStepChanged: canvas.requestPaint()
onHorizontalLineStepChanged: canvas.requestPaint()
onFirstXLineChanged: canvas.requestPaint()
onLastXLineChanged: canvas.requestPaint()
onMinHorizontalLinesChanged: canvas.requestPaint()
onXLabelChanged: canvas.requestPaint()
onYLabelChanged: canvas.requestPaint()
onSmoothingChanged: canvas.requestPaint()
onCurveSmoothingChanged: canvas.requestPaint()
onMarkerXChanged: canvas.requestPaint()
onMarkerGapChanged: canvas.requestPaint()
onLineColorChanged: canvas.requestPaint()
onGridColorChanged: canvas.requestPaint()
onMarkerColorChanged: canvas.requestPaint()
onLabelColorChanged: canvas.requestPaint()
onGridAlphaChanged: canvas.requestPaint()
onLabelAlphaChanged: canvas.requestPaint()
onFillAlphaChanged: canvas.requestPaint()
onLineWidthChanged: canvas.requestPaint()
onMarkerDotRadiusChanged: canvas.requestPaint()
onLabelFontSizeChanged: canvas.requestPaint()
}
+3 -17
View File
@@ -1,27 +1,13 @@
import QtQuick
import ZShell.Config import ZShell.Config
import qs.Services import qs.Services
CustomText { CustomText {
id: root id: root
readonly property list<int> allowed: [20, 24, 40, 48]
property real fill: 0 property real fill: 0
property int grade: Colors.light ? 0 : -25 property int grade: Colors.light ? 0 : -25
readonly property int opsz: clampOpsz(optical) property font fontStyle: Tokens.font.icon.small
property int optical: fontInfo.pixelSize
function clampOpsz(value): int { font: Tokens.font.icon.size(fontStyle.pointSize).weight(fontStyle.weight).vaxes(fontStyle.variableAxes).fill(fill.toFixed(1)).grade(grade).build()
return allowed.reduce((closest, current) => {
return Math.abs(current - value) < Math.abs(closest - value) ? current : closest;
});
}
font.family: "Material Symbols Rounded"
font.pointSize: Tokens.font.size.larger
font.variableAxes: ({
FILL: fill,
GRAD: grade,
opsz: opsz,
wght: fontInfo.weight
})
} }
+37
View File
@@ -0,0 +1,37 @@
import QtQuick
import Quickshell
PersistentProperties {
required property ShellScreen modelData
reloadableId: "screenState"
// Drawer visibilities
property bool bar
property bool clipboard
property bool dashboard
property bool dock
property bool isDrawing
property bool launcher
property bool osd
property bool resources
property bool settings
property bool sidebar
// Dashboard state
property int dashboardTab
property date dashboardDate: new Date()
// Sidebar state
property int sidebarTab
property list<string> expandedNotifs: []
// Utilities state
property string recordingConfirmDelete
property bool recordingListExpanded
property string recordingMode
// Drawing state
property color penColor: "white"
property int penWidth: 4
}
+1 -1
View File
@@ -62,7 +62,7 @@ TextFieldBase {
anchors.leftMargin: Tokens.padding.large anchors.leftMargin: Tokens.padding.large
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: Colors.palette.m3onSurfaceVariant color: Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.large fontStyle: Tokens.font.icon.builders.medium.scale(0.9).build()
text: "search" text: "search"
} }
+3 -4
View File
@@ -2,7 +2,6 @@ import QtQuick
import QtQuick.Shapes import QtQuick.Shapes
import ZShell import ZShell
import ZShell.Config import ZShell.Config
import qs.Helpers
import qs.Services import qs.Services
MouseArea { MouseArea {
@@ -52,7 +51,7 @@ MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: !enabled ? undefined : Qt.PointingHandCursor cursorShape: !enabled ? undefined : Qt.PointingHandCursor
enabled: parent.enabled && !Visibilities.getForActive()?.isDrawing enabled: parent.enabled && !ShellState.forActive()?.isDrawing
hoverEnabled: true hoverEnabled: true
Behavior on stateOpacity { Behavior on stateOpacity {
@@ -99,13 +98,13 @@ MouseArea {
id: base id: base
anchors.fill: parent anchors.fill: parent
bottomLeftRadius: root.parent?.bottomLeftRadius ?? radius ?? 0
bottomRightRadius: root.parent?.bottomRightRadius ?? radius ?? 0
color: Colors.palette.m3onSurface color: Colors.palette.m3onSurface
opacity: root.stateOpacity opacity: root.stateOpacity
// Pick up radius from parent if it has one (parent can be anything with radius props) // Pick up radius from parent if it has one (parent can be anything with radius props)
// qmllint disable missing-property // qmllint disable missing-property
radius: root.parent?.radius ?? 0 radius: root.parent?.radius ?? 0
bottomLeftRadius: root.parent?.bottomLeftRadius ?? radius ?? 0
bottomRightRadius: root.parent?.bottomRightRadius ?? radius ?? 0
topLeftRadius: root.parent?.topLeftRadius ?? radius ?? 0 topLeftRadius: root.parent?.topLeftRadius ?? radius ?? 0
topRightRadius: root.parent?.topRightRadius ?? radius ?? 0 topRightRadius: root.parent?.topRightRadius ?? radius ?? 0
// qmllint enable missing-property // qmllint enable missing-property
+4 -7
View File
@@ -8,7 +8,7 @@ TextField {
color: Colors.palette.m3onSurface color: Colors.palette.m3onSurface
cursorVisible: !readOnly cursorVisible: !readOnly
font.pointSize: Tokens.font.size.small font: Tokens.font.body.small
implicitHeight: contentHeight + topPadding + bottomPadding implicitHeight: contentHeight + topPadding + bottomPadding
implicitWidth: contentWidth + leftPadding + rightPadding implicitWidth: contentWidth + leftPadding + rightPadding
placeholderTextColor: Colors.palette.m3onSurfaceVariant // No anim cause placeholder is custom placeholderTextColor: Colors.palette.m3onSurfaceVariant // No anim cause placeholder is custom
@@ -18,14 +18,11 @@ TextField {
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
Behavior on color { Behavior on color {
CAnim { CAnim {}
}
}
cursorDelegate: Item {
} }
cursorDelegate: Item {}
Behavior on selectionColor { Behavior on selectionColor {
CAnim { CAnim {}
}
} }
CustomRect { CustomRect {
+3 -4
View File
@@ -36,8 +36,7 @@ CustomRect {
radius: Tokens.rounding.medium radius: Tokens.rounding.medium
Behavior on border.color { Behavior on border.color {
CAnim { CAnim {}
}
} }
Elevation { Elevation {
@@ -84,7 +83,7 @@ CustomRect {
return Colors.palette.m3onError; return Colors.palette.m3onError;
return Colors.palette.m3onSurfaceVariant; return Colors.palette.m3onSurfaceVariant;
} }
font.pointSize: Math.round(Tokens.font.size.large * 1.2) fontStyle: Tokens.font.icon.builders.large.scale(1.2).build()
text: root.modelData.icon text: root.modelData.icon
} }
} }
@@ -107,7 +106,7 @@ CustomRect {
return Colors.palette.m3onSurface; return Colors.palette.m3onSurface;
} }
elide: Text.ElideRight elide: Text.ElideRight
font.pointSize: Tokens.font.size.normal font: Tokens.font.title.small
text: root.modelData.title text: root.modelData.title
} }
+2 -1
View File
@@ -10,6 +10,7 @@ import ZShell
import qs.Components import qs.Components
import qs.Modules import qs.Modules
import qs.Helpers import qs.Helpers
import qs.Services
import qs.Paths import qs.Paths
import ZShell.Config import ZShell.Config
@@ -25,7 +26,7 @@ Singleton {
property alias server: server property alias server: server
function shouldThrottle(appName: string): bool { function shouldThrottle(appName: string): bool {
if (props.dnd || [...Visibilities.screens.values()].some(v => v.sidebar)) if (props.dnd || ShellState.anySidebarOpen())
return false; return false;
const key = (appName || "unknown").trim().toLowerCase(); const key = (appName || "unknown").trim().toLowerCase();
+5 -10
View File
@@ -3,28 +3,23 @@ pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import QtQuick import QtQuick
import ZShell.Internal import ZShell.Internal
import qs.Components
Item { Item {
id: root id: root
readonly property alias content: contentLoader.item readonly property alias content: contentLoader.item
readonly property PersistentProperties drawingState: PersistentProperties { required property ScreenState screenState
property color penColor: "white"
property int penWidth: 4
reloadableId: "drawingState"
}
required property PersistentProperties visibilities
Loader { Loader {
id: contentLoader id: contentLoader
active: root.visibilities.isDrawing active: root.screenState.isDrawing
anchors.fill: parent anchors.fill: parent
sourceComponent: StrokeCanvas { sourceComponent: StrokeCanvas {
penColor: root.drawingState.penColor penColor: root.screenState.penColor
penWidth: root.drawingState.penWidth penWidth: root.screenState.penWidth
} }
} }
} }
+78 -54
View File
@@ -1,7 +1,7 @@
import Quickshell import Quickshell
import QtQuick import QtQuick
import qs.Components
import ZShell.Config import ZShell.Config
import qs.Components
import qs.Helpers import qs.Helpers
import qs.Modules.Bar.Popouts import qs.Modules.Bar.Popouts
@@ -19,7 +19,7 @@ Item {
required property ShellScreen screen required property ShellScreen screen
property bool singleGestureTriggered: false property bool singleGestureTriggered: false
property bool utilitiesShortcutActive property bool utilitiesShortcutActive
required property PersistentProperties visibilities required property ScreenState screenState
function inBottomPanel(panel: Item, x: real, y: real): bool { function inBottomPanel(panel: Item, x: real, y: real): bool {
const panelHeight = panel.height * (1 - (panel.offsetScale ?? 0)); const panelHeight = panel.height * (1 - (panel.offsetScale ?? 0));
@@ -63,7 +63,7 @@ Item {
cursorShape: (active && centroid.pressPosition.y < root.bar.implicitHeight) ? Qt.ClosedHandCursor : undefined cursorShape: (active && centroid.pressPosition.y < root.bar.implicitHeight) ? Qt.ClosedHandCursor : undefined
dragThreshold: 0 dragThreshold: 0
enabled: !root.visibilities.isDrawing enabled: !root.screenState.isDrawing
grabPermissions: PointerHandler.CanTakeOverFromHandlersOfSameType | PointerHandler.ApprovesTakeOverByAnything grabPermissions: PointerHandler.CanTakeOverFromHandlersOfSameType | PointerHandler.ApprovesTakeOverByAnything
maximumPointCount: 1 maximumPointCount: 1
minimumPointCount: 1 minimumPointCount: 1
@@ -76,45 +76,59 @@ Item {
onCentroidChanged: { onCentroidChanged: {
const x = centroid.position.x; const x = centroid.position.x;
const y = centroid.position.y; const y = centroid.position.y;
const dragX = x - centroid.pressPosition.x; const pressX = centroid.pressPosition.x;
const dragY = y - centroid.pressPosition.y; const pressY = centroid.pressPosition.y;
const dragX = x - pressX;
const dragY = y - pressY;
if (centroid.pressPosition.y >= root.screen.height - Config.bar.border && centroid.pressPosition.x > root.screen.width / 5 && dragY < -Config.launcher.dragThreshold) if (pressY >= root.screen.height - Config.bar.border && pressX > root.screen.width / 5 && dragY < -Config.launcher.dragThreshold)
root.visibilities.launcher = true; root.screenState.launcher = true;
if (root.singleGestureTriggered) if (root.singleGestureTriggered)
return; return;
if (root.geometry.insetTop(root.borderThickness) > centroid.pressPosition.y) { const inDashboard = root.inTopPanel(root.panels.dashboardWrapper, pressX, pressY);
if (dragY > 20) {
root.visibilities.settings = true; if (inDashboard && root.withinPanelWidth(root.panels.dashboardWrapper, x, y)) {
if (dragY > Config.dashboard.dragThreshold) {
root.screenState.dashboard = true;
root.singleGestureTriggered = true; root.singleGestureTriggered = true;
} else if (dragY < -20) { } else if (dragY < -Config.dashboard.dragThreshold) {
root.visibilities.settings = false; root.screenState.dashboard = false;
root.singleGestureTriggered = true; root.singleGestureTriggered = true;
} }
} }
if (centroid.pressPosition.y > root.screen.height - root.geometry.insetBottom(root.borderThickness) && centroid.pressPosition.x < root.screen.width / 5 && dragY < -50) if (root.geometry.insetTop(root.borderThickness) > pressY && (Config.dashboard.showOnHover || !inDashboard)) {
root.visibilities.clipboard = true; if (dragY > 20) {
root.screenState.settings = true;
root.singleGestureTriggered = true;
} else if (dragY < -20) {
root.screenState.settings = false;
root.singleGestureTriggered = true;
}
}
if (!Config.dock.hoverToReveal && centroid.pressPosition.y > root.screen.height - root.geometry.insetTop(root.borderThickness) && centroid.pressPosition.x > root.screen.width / 5 && !root.visibilities.launcher) if (pressY > root.screen.height - root.geometry.insetBottom(root.borderThickness) && pressX < root.screen.width / 5 && dragY < -50)
root.screenState.clipboard = true;
if (!Config.dock.hoverToReveal && pressY > root.screen.height - root.geometry.insetTop(root.borderThickness) && pressX > root.screen.width / 5 && !root.screenState.launcher)
if (dragY < -10) { if (dragY < -10) {
root.visibilities.dock = true; root.screenState.dock = true;
root.singleGestureTriggered = true; root.singleGestureTriggered = true;
} }
if (centroid.pressPosition.x > root.screen.width - Config.bar.border && centroid.pressPosition.y < (root.screen.height / 2) && dragX < -Config.sidebar.dragThreshold) { if (pressX > root.screen.width - Config.bar.border && pressY < (root.screen.height / 2) && dragX < -Config.sidebar.dragThreshold) {
root.visibilities.sidebar = true; root.screenState.sidebar = true;
root.singleGestureTriggered = true; root.singleGestureTriggered = true;
} }
if (centroid.pressPosition.x >= root.screen.width - Config.bar.border && centroid.pressPosition.y > (root.screen.height / 2) && dragX < -20) { if (pressX >= root.screen.width - Config.bar.border && pressY > (root.screen.height / 2) && dragX < -20) {
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r+1', on_current_monitor = true })`); Hypr.dispatch(`hl.dsp.focus({ workspace = 'r+1', on_current_monitor = true })`);
root.singleGestureTriggered = true; root.singleGestureTriggered = true;
} }
if (centroid.pressPosition.x <= Config.bar.border && dragX > 20) { if (pressX <= Config.bar.border && dragX > 20) {
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r-1', on_current_monitor = true })`); Hypr.dispatch(`hl.dsp.focus({ workspace = 'r-1', on_current_monitor = true })`);
root.singleGestureTriggered = true; root.singleGestureTriggered = true;
} }
@@ -127,7 +141,7 @@ Item {
property bool setInitialPoint: false property bool setInitialPoint: false
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
enabled: root.visibilities.isDrawing && (!root.inLeftPanel(root.panels.drawing, hoverHandler.point.position.x, hoverHandler.point.position.y) || !root.panels.drawing.expanded) enabled: root.screenState.isDrawing && (!root.inLeftPanel(root.panels.drawing, hoverHandler.point.position.x, hoverHandler.point.position.y) || !root.panels.drawing.expanded)
onActiveChanged: { onActiveChanged: {
if (!active) { if (!active) {
@@ -178,12 +192,12 @@ Item {
HoverHandler { HoverHandler {
id: hoverHandler id: hoverHandler
cursorShape: root.visibilities.isDrawing && !root.inLeftPanel(root.panels.drawing, point.position.x, point.position.y) ? Qt.BlankCursor : undefined cursorShape: root.screenState.isDrawing && !root.inLeftPanel(root.panels.drawing, point.position.x, point.position.y) ? Qt.BlankCursor : undefined
onHoveredChanged: { onHoveredChanged: {
if (!hovered) { if (!hovered) {
if (!root.osdShortcutActive) { if (!root.osdShortcutActive) {
root.visibilities.osd = false; root.screenState.osd = false;
root.panels.osd.hovered = false; root.panels.osd.hovered = false;
} }
@@ -193,13 +207,16 @@ Item {
if (Config.bar.autoHide || root.bar.fullscreen) if (Config.bar.autoHide || root.bar.fullscreen)
root.bar.isHovered = false; root.bar.isHovered = false;
if (root.screenState.dashboard && Config.dashboard.showOnHover)
root.screenState.dashboard = false;
} }
} }
onPointChanged: { onPointChanged: {
const x = point.position.x; const x = point.position.x;
const y = point.position.y; const y = point.position.y;
if (root.visibilities.isDrawing) { if (root.screenState.isDrawing) {
if (root.inLeftPanel(root.panels.drawing, x, y) && !(drawingHandler.point.pressedButtons & Qt.LeftButton)) { if (root.inLeftPanel(root.panels.drawing, x, y) && !(drawingHandler.point.pressedButtons & Qt.LeftButton)) {
root.panels.drawing.expand(); root.panels.drawing.expand();
root.drawing.content.hideHover(); root.drawing.content.hideHover();
@@ -210,7 +227,7 @@ Item {
return; return;
} }
if (!root.visibilities.bar && Config.bar.autoHide && root.geometry.barContains(x, y, true)) if (!root.screenState.bar && Config.bar.autoHide && root.geometry.barContains(x, y, true))
root.bar.isHovered = true; root.bar.isHovered = true;
if (root.bar.fullscreen && !root.bar.isHovered) if (root.bar.fullscreen && !root.bar.isHovered)
@@ -228,7 +245,7 @@ Item {
const showOsd = outOfSidebar && root.inRightPanel(root.panels.osdWrapper, x, y); const showOsd = outOfSidebar && root.inRightPanel(root.panels.osdWrapper, x, y);
if (!root.osdShortcutActive) { if (!root.osdShortcutActive) {
root.visibilities.osd = showOsd; root.screenState.osd = showOsd;
root.panels.osd.hovered = showOsd; root.panels.osd.hovered = showOsd;
} else if (showOsd) { } else if (showOsd) {
root.osdShortcutActive = false; root.osdShortcutActive = false;
@@ -236,8 +253,13 @@ Item {
} }
} }
if (Config.dock.enable && Config.dock.hoverToReveal && !root.visibilities.dock && !root.visibilities.launcher && root.inBottomPanel(root.panels.dock, x, y)) if (Config.dock.enable && Config.dock.hoverToReveal && !root.screenState.dock && !root.screenState.launcher && root.inBottomPanel(root.panels.dock, x, y))
root.visibilities.dock = true; root.screenState.dock = true;
const inDashboard = root.inTopPanel(root.panels.dashboardWrapper, x, y);
if (Config.dashboard.showOnHover)
root.screenState.dashboard = inDashboard;
if (root.geometry.barContains(x, y)) if (root.geometry.barContains(x, y))
root.bar.checkPopout(root.geometry.axisPos(x, y)); root.bar.checkPopout(root.geometry.axisPos(x, y));
@@ -246,16 +268,17 @@ Item {
Connections { Connections {
function onDashboardChanged() { function onDashboardChanged() {
if (root.visibilities.dashboard) { if (root.screenState.dashboard) {
const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY); const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY);
if (!inDashboardArea) { if (!inDashboardArea) {
root.dashboardShortcutActive = true; root.dashboardShortcutActive = true;
} }
if (root.panels.launcher.x + root.panels.launcher.width > root.panels.dashboardWrapper.x) if (root.panels.launcher.x + root.panels.launcher.width > root.panels.dashboardWrapper.x)
root.visibilities.launcher = false; root.screenState.launcher = false;
root.visibilities.settings = false; root.screenState.settings = false;
root.visibilities.sidebar = false; root.screenState.sidebar = false;
root.screenState.resources = false;
root.popouts.hasCurrent = false; root.popouts.hasCurrent = false;
} else { } else {
root.dashboardShortcutActive = false; root.dashboardShortcutActive = false;
@@ -263,12 +286,12 @@ Item {
} }
function onIsDrawingChanged() { function onIsDrawingChanged() {
if (!root.visibilities.isDrawing) if (!root.screenState.isDrawing)
root.drawing.clear(); root.drawing.clear();
} }
function onLauncherChanged() { function onLauncherChanged() {
if (!root.visibilities.launcher) { if (!root.screenState.launcher) {
root.dashboardShortcutActive = false; root.dashboardShortcutActive = false;
root.osdShortcutActive = false; root.osdShortcutActive = false;
root.utilitiesShortcutActive = false; root.utilitiesShortcutActive = false;
@@ -276,23 +299,23 @@ Item {
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY); const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
if (!inOsdArea) { if (!inOsdArea) {
root.visibilities.osd = false; root.screenState.osd = false;
root.panels.osd.hovered = false; root.panels.osd.hovered = false;
} }
} }
if (root.visibilities.launcher) { if (root.screenState.launcher) {
if (root.panels.dashboardWrapper.x < root.panels.launcher.x + root.panels.launcher.width) { if (root.panels.dashboardWrapper.x < root.panels.launcher.x + root.panels.launcher.width) {
root.visibilities.dashboard = false; root.screenState.dashboard = false;
} }
root.visibilities.dock = false; root.screenState.dock = false;
root.visibilities.settings = false; root.screenState.settings = false;
} }
} }
function onOsdChanged() { function onOsdChanged() {
if (root.visibilities.osd) { if (root.screenState.osd) {
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY); const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
if (!inOsdArea) { if (!inOsdArea) {
root.osdShortcutActive = true; root.osdShortcutActive = true;
@@ -303,32 +326,33 @@ Item {
} }
function onResourcesChanged() { function onResourcesChanged() {
if (root.visibilities.resources && (root.popouts.currentName.startsWith("audio") || root.popouts.currentName.startsWith("updates"))) { if (root.screenState.resources) {
root.popouts.hasCurrent = false; if (root.popouts.currentName.startsWith("audio") || root.popouts.currentName.startsWith("updates"))
} root.popouts.hasCurrent = false;
if (root.visibilities.resources) root.screenState.settings = false;
root.visibilities.settings = false; root.screenState.dashboard = false;
}
} }
function onSettingsChanged() { function onSettingsChanged() {
if (root.visibilities.settings) { if (root.screenState.settings) {
root.visibilities.resources = false; root.screenState.resources = false;
root.visibilities.dashboard = false; root.screenState.dashboard = false;
root.visibilities.sidebar = false; root.screenState.sidebar = false;
root.panels.popouts.hasCurrent = false; root.panels.popouts.hasCurrent = false;
root.visibilities.launcher = false; root.screenState.launcher = false;
} }
} }
function onSidebarChanged() { function onSidebarChanged() {
if (root.visibilities.sidebar) { if (root.screenState.sidebar) {
root.visibilities.dashboard = false; root.screenState.dashboard = false;
root.visibilities.settings = false; root.screenState.settings = false;
root.popouts.hasCurrent = false; root.popouts.hasCurrent = false;
} }
} }
target: root.visibilities target: root.screenState
} }
} }
+318
View File
@@ -0,0 +1,318 @@
import Quickshell
import QtQuick
import ZShell.Config
import ZShell.Internal
import qs.Components
import qs.Helpers
import qs.Modules.Bar.Popouts
CustomMouseArea {
id: root
required property Item bar
required property real borderThickness
required property ScreenState screenState
required property Drawing drawing
required property EdgeGeometry geometry
required property Panels panels
required property Wrapper popouts
required property ShellScreen screen
property bool dashboardShortcutActive
property bool osdShortcutActive
property bool singleGestureTriggered: false
property bool utilitiesShortcutActive
property bool setInitialPoint: false
property point dragStart
function inBottomPanel(panel: Item, x: real, y: real): bool {
const panelHeight = panel.height * (1 - (panel.offsetScale ?? 0));
return y > root.height - (panelHeight + geometry.insetBottom(borderThickness)) && withinPanelWidth(panel, x, y);
}
function inLeftPanel(panel: Item, x: real, y: real): bool {
return x < panel.x + panel.width + geometry.insetLeft(borderThickness) && withinPanelHeight(panel, x, y);
}
function inPopoutArea(x: real, y: real): bool {
const panel = panels.popoutsWrapper;
if (!geometry.horizontal)
return inLeftPanel(panel, x, y);
return withinPanelWidth(panel, x, y) && withinPanelHeight(panel, x, y);
}
function inRightPanel(panel: Item, x: real, y: real): bool {
return x > panel.x + geometry.insetLeft(borderThickness) && withinPanelHeight(panel, x, y);
}
function inTopPanel(panel: Item, x: real, y: real): bool {
const panelHeight = panel.height * (1 - (panel.offsetScale ?? 0));
return y < geometry.insetTop(borderThickness) + panelHeight && withinPanelWidth(panel, x, y);
}
function withinPanelHeight(panel: Item, x: real, y: real): bool {
const panelY = panel.y + geometry.insetTop(borderThickness);
return y >= panelY && y <= panelY + panel.height;
}
function withinPanelWidth(panel: Item, x: real, y: real): bool {
const panelX = panel.x + geometry.insetLeft(borderThickness);
return x >= panelX && x <= panelX + panel.width;
}
anchors.fill: parent
acceptedButtons: bar.fullscreen ? Qt.NoButton : Qt.AllButtons
hoverEnabled: true
onPressed: event => {
dragStart = Qt.point(event.x, event.y);
if (screenState.isDrawing) {
if (event.button & Qt.RightButton) {
drawing.content.clear();
return;
}
if (!inLeftPanel(panels.drawing, mouseX, mouseY))
drawing.content.beginStroke(event.x, event.y);
}
}
onPressedChanged: {
if (!pressed) {
if (screenState.isDrawing) {
setInitialPoint = false;
drawing.content.endStroke();
if (!inLeftPanel(panels.drawing, mouseX, mouseY))
drawing.content.showHover(mouseX, mouseY);
}
} else {
if (screenState.isDrawing) {
panels.drawing.collapse();
}
}
}
onContainsMouseChanged: {
if (!containsMouse) {
if (!osdShortcutActive) {
screenState.osd = false;
panels.osd.hovered = false;
}
if (!popouts.currentName.startsWith("traymenu") || Config.bar.tray.showOnHover) {
popouts.hasCurrent = false;
}
if (Config.bar.autoHide || bar.fullscreen)
bar.isHovered = false;
if (screenState.dashboard && Config.dashboard.showOnHover)
screenState.dashboard = false;
}
}
onPositionChanged: event => {
const x = event.x;
const y = event.y;
const dragX = x - dragStart.x;
const dragY = y - dragStart.y;
if (screenState.isDrawing) {
if (pressed) {
drawing.content.appendPoint(x, y);
} else {
if (inLeftPanel(panels.drawing, x, y)) {
if (!(pressedButtons & Qt.LeftButton)) {
panels.drawing.expand();
drawing.content.hideHover();
Cursor.clearOverride();
}
} else {
Cursor.setOverride(Qt.BlankCursor);
drawing.content.showHover(x, y);
}
}
if (drawing.content.hoverVisible)
drawing.content.showHover(x, y);
return;
}
if (root.bar.fullscreen && !root.bar.isHovered)
unhideTimer.start();
if (pressed && dragStart.y >= screen.height - Config.bar.border && dragStart.x > screen.width / 5 && dragY < -Config.launcher.dragThreshold)
screenState.launcher = true;
const showDashboard = Config.dashboard.showOnHover && inTopPanel(panels.dashboardWrapper, x, y);
if (!dashboardShortcutActive) {
screenState.dashboard = showDashboard;
} else if (showDashboard) {
dashboardShortcutActive = false;
}
if (pressed && geometry.insetTop(borderThickness) > dragStart.y && withinPanelWidth(panels.dashboardWrapper, x, y)) {
if (dragY > Config.dashboard.dragThreshold)
screenState.dashboard = true;
else if (dragY < -Config.dashboard.dragThreshold)
screenState.dashboard = false;
}
if (pressed && geometry.insetTop(borderThickness) > dragStart.y && (Config.dashboard.showOnHover || !inTopPanel(panels.dashboardWrapper, dragStart.x, dragStart.y))) {
if (dragY > 20) {
screenState.settings = true;
} else if (dragY < -20) {
screenState.settings = false;
}
}
if (pressed && dragStart.y > screen.height - geometry.insetBottom(borderThickness) && dragStart.x < screen.width / 5 && dragY < -50)
screenState.clipboard = true;
if (pressed && !Config.dock.hoverToReveal && dragStart.y > screen.height - geometry.insetTop(borderThickness) && dragStart.x > screen.width / 5 && !screenState.launcher)
if (dragY < -10)
screenState.dock = true;
if (pressed && dragStart.x > screen.width - Config.bar.border && dragStart.y < (screen.height / 2) && dragX < -Config.sidebar.dragThreshold)
screenState.sidebar = true;
if (pressed && dragStart.x >= screen.width - Config.bar.border && dragStart.y > (screen.height / 2) && dragX < -20)
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r+1', on_current_monitor = true })`);
if (pressed && dragStart.x <= Config.bar.border && dragX > 20)
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r-1', on_current_monitor = true })`);
if (!screenState.bar && Config.bar.autoHide && geometry.barContains(x, y, true))
bar.isHovered = true;
if (panels.sidebar.offsetScale === 1) {
const showOsd = inRightPanel(panels.osdWrapper, x, y);
if (showOsd) {
osdShortcutActive = false;
panels.osd.hovered = true;
}
} else {
const outOfSidebar = x < width - panels.sidebar.width * (1 - panels.sidebar.offsetScale);
const showOsd = outOfSidebar && inRightPanel(panels.osdWrapper, x, y);
if (!osdShortcutActive) {
screenState.osd = showOsd;
panels.osd.hovered = showOsd;
} else if (showOsd) {
osdShortcutActive = false;
panels.osd.hovered = true;
}
}
if (Config.dock.enable && Config.dock.hoverToReveal && !screenState.dock && !screenState.launcher && inBottomPanel(panels.dock, x, y))
screenState.dock = true;
if (geometry.barContains(x, y))
bar.checkPopout(geometry.axisPos(x, y));
}
Timer {
id: unhideTimer
interval: Config.bar.revealDelay
repeat: false
running: false
onTriggered: {
if (root.containsMouse)
root.bar.isHovered = true;
}
}
Connections {
function onDashboardChanged(): void {
if (root.screenState.dashboard) {
const inDashboardArea = root.inTopPanel(root.panels.dashboardWrapper, root.mouseX, root.mouseY);
if (!inDashboardArea) {
root.dashboardShortcutActive = true;
}
root.screenState.launcher = false;
root.screenState.settings = false;
root.screenState.sidebar = false;
root.screenState.resources = false;
root.popouts.hasCurrent = false;
} else {
root.dashboardShortcutActive = false;
}
}
function onIsDrawingChanged(): void {
if (root.screenState.isDrawing) {
Cursor.setOverride(Qt.BlankCursor);
} else {
Cursor.clearOverride();
}
}
function onLauncherChanged(): void {
if (!root.screenState.launcher) {
root.dashboardShortcutActive = false;
root.osdShortcutActive = false;
root.utilitiesShortcutActive = false;
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
if (!inOsdArea) {
root.screenState.osd = false;
root.panels.osd.hovered = false;
}
}
if (root.screenState.launcher) {
root.screenState.dashboard = false;
root.screenState.dock = false;
root.screenState.settings = false;
}
}
function onOsdChanged(): void {
if (root.screenState.osd) {
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
if (!inOsdArea) {
root.osdShortcutActive = true;
}
} else {
root.osdShortcutActive = false;
}
}
function onResourcesChanged(): void {
if (root.screenState.resources) {
if (root.popouts.currentName.startsWith("audio") || root.popouts.currentName.startsWith("updates"))
root.popouts.hasCurrent = false;
root.screenState.settings = false;
root.screenState.dashboard = false;
}
}
function onSettingsChanged(): void {
if (root.screenState.settings) {
root.screenState.resources = false;
root.screenState.dashboard = false;
root.screenState.sidebar = false;
root.panels.popouts.hasCurrent = false;
root.screenState.launcher = false;
}
}
function onSidebarChanged(): void {
if (root.screenState.sidebar) {
if (root.panels.dashboardWrapper.x + root.panels.dashboardWrapper.width > root.panels.sidebar.x)
root.screenState.dashboard = false;
root.screenState.settings = false;
root.popouts.hasCurrent = false;
}
}
target: root.screenState
}
}
+16 -20
View File
@@ -37,12 +37,12 @@ Item {
readonly property alias resources: resources readonly property alias resources: resources
readonly property alias resourcesWrapper: resourcesWrapper readonly property alias resourcesWrapper: resourcesWrapper
required property ShellScreen screen required property ShellScreen screen
required property ScreenState screenState
readonly property alias settings: settings readonly property alias settings: settings
readonly property alias settingsWrapper: settingsWrapper readonly property alias settingsWrapper: settingsWrapper
readonly property alias sidebar: sidebar readonly property alias sidebar: sidebar
readonly property alias toasts: toasts readonly property alias toasts: toasts
readonly property alias utilities: utilities readonly property alias utilities: utilities
required property PersistentProperties visibilities
anchors.bottomMargin: geometry.insetBottom(borderThickness) anchors.bottomMargin: geometry.insetBottom(borderThickness)
anchors.fill: parent anchors.fill: parent
@@ -64,7 +64,7 @@ Item {
id: resources id: resources
position: root.geometry.position position: root.geometry.position
visibilities: root.visibilities screenState: root.screenState
} }
} }
@@ -84,8 +84,8 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
screen: root.screen screen: root.screen
screenState: root.screenState
sidebarOrSessionVisible: sidebar.visible sidebarOrSessionVisible: sidebar.visible
visibilities: root.visibilities
} }
} }
@@ -96,7 +96,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
drawing: root.drawingItem drawing: root.drawingItem
screen: root.screen screen: root.screen
visibilities: root.visibilities screenState: root.screenState
} }
Popouts.ClipWrapper { Popouts.ClipWrapper {
@@ -121,8 +121,8 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
panels: root panels: root
screenState: root.screenState
sidebarPanel: sidebar sidebarPanel: sidebar
visibilities: root.visibilities
} }
Launcher.Wrapper { Launcher.Wrapper {
@@ -132,7 +132,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
panels: root panels: root
screen: root.screen screen: root.screen
visibilities: root.visibilities screenState: root.screenState
} }
Utils.Wrapper { Utils.Wrapper {
@@ -140,9 +140,8 @@ Item {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
popouts: popouts screenState: root.screenState
sidebar: sidebar sidebar: sidebar
visibilities: root.visibilities
} }
Item { Item {
@@ -150,13 +149,11 @@ Item {
property real offsetScale: dashboard.shouldBeActive ? 0 : 1 property real offsetScale: dashboard.shouldBeActive ? 0 : 1
anchors.bottom: root.geometry.barOnLeft || root.geometry.barOnBottom ? parent.bottom : undefined anchors.horizontalCenter: parent.horizontalCenter
anchors.left: root.geometry.barOnLeft ? parent.left : undefined anchors.top: parent.top
anchors.right: root.geometry.barOnLeft ? undefined : parent.right
anchors.top: root.geometry.barOnBottom || root.geometry.barOnLeft ? undefined : parent.top
clip: true clip: true
implicitHeight: root.geometry.horizontal ? dashboard.implicitHeight * (1 - offsetScale) : dashboard.implicitHeight implicitHeight: dashboard.implicitHeight * (1 - offsetScale)
implicitWidth: root.geometry.horizontal ? dashboard.implicitWidth : dashboard.implicitWidth * (1 - offsetScale) implicitWidth: dashboard.implicitWidth
Behavior on offsetScale { Behavior on offsetScale {
Anim { Anim {
@@ -169,8 +166,7 @@ Item {
id: dashboard id: dashboard
offsetScale: dashboardWrapper.offsetScale offsetScale: dashboardWrapper.offsetScale
position: root.geometry.position screenState: root.screenState
visibilities: root.visibilities
} }
} }
@@ -181,7 +177,7 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.top: notifications.bottom anchors.top: notifications.bottom
panels: root panels: root
visibilities: root.visibilities screenState: root.screenState
} }
Item { Item {
@@ -197,7 +193,7 @@ Item {
anchors.verticalCenterOffset: (-implicitHeight - 5 - ((root.height - implicitHeight) / 2)) * offsetScale anchors.verticalCenterOffset: (-implicitHeight - 5 - ((root.height - implicitHeight) / 2)) * offsetScale
panels: root panels: root
screen: root.screen screen: root.screen
visibilities: root.visibilities screenState: root.screenState
} }
} }
@@ -208,7 +204,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
panels: root panels: root
screen: root.screen screen: root.screen
visibilities: root.visibilities screenState: root.screenState
} }
Clipboard.Wrapper { Clipboard.Wrapper {
@@ -217,6 +213,6 @@ Item {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
screen: root.screen screen: root.screen
visibilities: root.visibilities screenState: root.screenState
} }
} }
+52 -54
View File
@@ -50,16 +50,17 @@ CustomWindow {
readonly property alias menuRegion: regions.menuPopoutRegion readonly property alias menuRegion: regions.menuPopoutRegion
readonly property HyprlandMonitor monitor: Hypr.monitorFor(screen) readonly property HyprlandMonitor monitor: Hypr.monitorFor(screen)
property var root: Quickshell.shellDir property var root: Quickshell.shellDir
readonly property ScreenState screenState: ShellState.forScreen(screen)
readonly property real sdfBorderOffset: 2 * fsTransitionProg readonly property real sdfBorderOffset: 2 * fsTransitionProg
readonly property real shadowOpacity: 0.7 * (1 - (bar.isHovered ? 0 : fsTransitionProg)) readonly property real shadowOpacity: 0.7 * (1 - (bar.isHovered ? 0 : fsTransitionProg))
property color surfaceColor: Colors.tPalette.m3surface property color surfaceColor: Colors.tPalette.m3surface
WlrLayershell.exclusionMode: ExclusionMode.Ignore WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.keyboardFocus: visibilities.launcher || visibilities.settings ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None WlrLayershell.keyboardFocus: screenState.launcher || screenState.settings || screenState.sidebar ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
WlrLayershell.layer: (fsTransitionProg > 0 && Config.general.showOverFullscreen) || (hasSpecialWorkspace && hasFullscreenOnNormalWs) ? WlrLayer.Overlay : WlrLayer.Top WlrLayershell.layer: (fsTransitionProg > 0 && Config.general.showOverFullscreen) || (hasSpecialWorkspace && hasFullscreenOnNormalWs) ? WlrLayer.Overlay : WlrLayer.Top
color: "transparent" color: "transparent"
contentItem.focus: true contentItem.focus: true
mask: visibilities.isDrawing ? null : (hasFullscreen && !bar.isHovered ? emptyRegion : regions) mask: screenState.isDrawing ? null : (hasFullscreen && !bar.isHovered ? emptyRegion : regions)
name: "Bar" name: "Bar"
Behavior on fsTransitionProg { Behavior on fsTransitionProg {
@@ -73,26 +74,26 @@ CustomWindow {
contentItem.Keys.onEscapePressed: { contentItem.Keys.onEscapePressed: {
if (Config.bar.autoHide) if (Config.bar.autoHide)
visibilities.bar = false; screenState.bar = false;
visibilities.launcher = false; screenState.launcher = false;
visibilities.sidebar = false; screenState.sidebar = false;
visibilities.dashboard = false; screenState.dashboard = false;
visibilities.osd = false; screenState.osd = false;
visibilities.settings = false; screenState.settings = false;
visibilities.resources = false; screenState.resources = false;
visibilities.dock = false; screenState.dock = false;
visibilities.clipboard = false; screenState.clipboard = false;
panels.popouts.hasCurrent = false; panels.popouts.hasCurrent = false;
} }
onHasFullscreenChanged: { onHasFullscreenChanged: {
visibilities.launcher = false; screenState.launcher = false;
visibilities.sidebar = false; screenState.sidebar = false;
visibilities.dashboard = false; screenState.dashboard = false;
visibilities.osd = false; screenState.osd = false;
visibilities.settings = false; screenState.settings = false;
visibilities.resources = false; screenState.resources = false;
visibilities.clipboard = false; screenState.clipboard = false;
visibilities.dock = false; screenState.dock = false;
panels.popouts.hasCurrent = false; panels.popouts.hasCurrent = false;
} }
@@ -144,44 +145,41 @@ CustomWindow {
HyprlandFocusGrab { HyprlandFocusGrab {
id: focusGrab id: focusGrab
active: visibilities.dock || visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.clipboard || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu") && (!Config.bar.tray.showOnHover || (panels.popouts.current as StackView)?.depth > 1))
windows: [root] windows: [root]
active: {
const s = root.screenState;
const c = Config;
if ((s.launcher && c.launcher.enabled) || (s.sidebar && c.sidebar.enabled) || (s.resources && c.dashboard.performance.enabled) || (s.clipboard && c.clipboard.enabled))
return true;
if (s.dashboard && !c.dashboard.showOnHover && c.dashboard.enabled)
return true;
if (!c.dock.hoverToReveal && s.dock && c.dock.enable)
return true;
if (panels.popouts.currentName.startsWith("traymenu") && (panels.popouts.current as StackView)?.depth > 1)
return true;
if (s.settings)
return true;
return false;
}
onCleared: { onCleared: {
visibilities.launcher = false; root.screenState.launcher = false;
visibilities.sidebar = false; root.screenState.sidebar = false;
visibilities.dashboard = false; root.screenState.dashboard = false;
visibilities.osd = false; root.screenState.osd = false;
visibilities.settings = false; root.screenState.settings = false;
visibilities.resources = false; root.screenState.resources = false;
visibilities.clipboard = false; root.screenState.clipboard = false;
visibilities.dock = false; root.screenState.dock = false;
panels.popouts.hasCurrent = false; panels.popouts.hasCurrent = false;
} }
} }
PersistentProperties {
id: visibilities
property bool bar
property bool clipboard
property bool dashboard
property bool dock
property bool isDrawing
property bool launcher
property bool notif: NotifServer.popups.length > 0
property bool osd
property bool resources
property bool settings
property bool sidebar
Component.onCompleted: Visibilities.load(root.screen, this)
}
Binding { Binding {
property: "bar" property: "bar"
target: visibilities target: root.screenState
value: visibilities.sidebar || visibilities.dashboard || visibilities.osd || (!Config.bar.hideWhenNotif && visibilities.notif) || visibilities.resources || visibilities.settings || bar.isHovered value: root.screenState.sidebar || root.screenState.dashboard || root.screenState.osd || (!Config.bar.hideWhenNotif && NotifServer.popups.length > 0) || root.screenState.resources || root.screenState.settings || bar.isHovered
when: Config.bar.autoHide when: Config.bar.autoHide
} }
@@ -348,7 +346,7 @@ CustomWindow {
anchors.fill: parent anchors.fill: parent
layer.enabled: true layer.enabled: true
visibilities: visibilities screenState: root.screenState
z: 2 z: 2
layer.effect: MultiEffect { layer.effect: MultiEffect {
@@ -377,7 +375,7 @@ CustomWindow {
} }
} }
Interactions { Interactions2 {
id: interactions id: interactions
anchors.fill: parent anchors.fill: parent
@@ -389,7 +387,7 @@ CustomWindow {
panels: panels panels: panels
popouts: panels.popouts popouts: panels.popouts
screen: root.screen screen: root.screen
visibilities: visibilities screenState: root.screenState
Panels { Panels {
id: panels id: panels
@@ -399,7 +397,7 @@ CustomWindow {
drawingItem: drawing drawingItem: drawing
geometry: geometry geometry: geometry
screen: root.screen screen: root.screen
visibilities: visibilities screenState: root.screenState
clipboard.transform: Matrix4x4 { clipboard.transform: Matrix4x4 {
matrix: clipboardBg.deformMatrix matrix: clipboardBg.deformMatrix
@@ -442,14 +440,14 @@ CustomWindow {
anchors.bottom: geometry.barOnBottom ? parent.bottom : undefined anchors.bottom: geometry.barOnBottom ? parent.bottom : undefined
anchors.left: parent.left anchors.left: parent.left
anchors.top: geometry.barOnBottom ? undefined : parent.top anchors.top: geometry.barOnBottom ? undefined : parent.top
enabled: !visibilities.isDrawing enabled: !screenState.isDrawing
fullscreen: root.hasFullscreen fullscreen: root.hasFullscreen
height: geometry.horizontal ? implicitHeight : parent.height height: geometry.horizontal ? implicitHeight : parent.height
popouts: panels.popouts popouts: panels.popouts
popoutsWrapper: panels.popoutsWrapper popoutsWrapper: panels.popoutsWrapper
position: geometry.position position: geometry.position
screen: root.screen screen: root.screen
visibilities: visibilities screenState: root.screenState
width: geometry.horizontal ? parent.width : implicitWidth width: geometry.horizontal ? parent.width : implicitWidth
} }
} }
+5 -10
View File
@@ -13,21 +13,17 @@ import qs.Paths
Singleton { Singleton {
id: root id: root
readonly property M3Palette current: M3Palette { readonly property M3Palette current: M3Palette {}
}
property bool currentLight property bool currentLight
property string flavour property string flavour
readonly property bool light: showPreview ? previewLight : currentLight readonly property bool light: showPreview ? previewLight : currentLight
readonly property M3Palette palette: showPreview ? preview : current readonly property M3Palette palette: showPreview ? preview : current
readonly property M3Palette preview: M3Palette { readonly property M3Palette preview: M3Palette {}
}
property bool previewLight property bool previewLight
property string scheme property string scheme
property bool showPreview property bool showPreview
readonly property M3TPalette tPalette: M3TPalette { readonly property M3TPalette tPalette: M3TPalette {}
} readonly property Transparency transparency: Transparency {}
readonly property Transparency transparency: Transparency {
}
readonly property alias wallLuminance: analyser.luminance readonly property alias wallLuminance: analyser.luminance
function alterColor(c: color, a: real, layer: int): color { function alterColor(c: color, a: real, layer: int): color {
@@ -112,8 +108,7 @@ Singleton {
function setMode(mode: string): void { function setMode(mode: string): void {
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", mode]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", mode]);
Config.general.color.mode = mode; Config.colors.mode = mode;
Config.save();
} }
function swapRG(c: color): color { function swapRG(c: color): color {
+2 -1
View File
@@ -119,7 +119,8 @@ ColumnLayout {
length: count length: count
}, () => null); }, () => null);
return forecast.slice(0, count); const start = Math.max(0, Weather.currentHourIndex);
return forecast.slice(start, start + count);
} }
ColumnLayout { ColumnLayout {
+6 -6
View File
@@ -11,14 +11,14 @@ import qs.Services
Singleton { Singleton {
id: root id: root
readonly property int darkEnd: Config.general.color.scheduleDarkEnd readonly property int darkEnd: Config.colors.scheduleDarkEnd
readonly property int darkStart: Config.general.color.scheduleDarkStart readonly property int darkStart: Config.colors.scheduleDarkStart
readonly property bool enabled: Config.general.color.scheduleDark && Config.general.color.schemeGeneration readonly property bool enabled: Config.colors.scheduleDark && Config.colors.schemeGen
function applyDarkMode() { function applyDarkMode() {
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", "dark"]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", "dark"]);
Config.general.color.mode = "dark"; Config.colors.mode = "dark";
Quickshell.execDetached(["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", "'prefer-dark'"]); Quickshell.execDetached(["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", "'prefer-dark'"]);
@@ -30,13 +30,13 @@ Singleton {
function applyLightMode() { function applyLightMode() {
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", "light"]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", "light"]);
Config.general.color.mode = "light"; Config.colors.mode = "light";
Quickshell.execDetached(["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", "'prefer-light'"]); Quickshell.execDetached(["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", "'prefer-light'"]);
Quickshell.execDetached(["sh", "-c", `sed -i 's/color_scheme_path=\\(.*\\)Dark.colors/color_scheme_path=\\1Light.colors/' ${Paths.home}/.config/qt6ct/qt6ct.conf`]); Quickshell.execDetached(["sh", "-c", `sed -i 's/color_scheme_path=\\(.*\\)Dark.colors/color_scheme_path=\\1Light.colors/' ${Paths.home}/.config/qt6ct/qt6ct.conf`]);
if (Config.general.color.neovimColors) if (Config.colors.neovimColors)
Quickshell.execDetached(["sed", "-i", "'s/\\(vim.cmd.colorscheme \\).*/\\1\"onelight\"/'", "~/.config/nvim/lua/config/load-colorscheme.lua"]); Quickshell.execDetached(["sed", "-i", "'s/\\(vim.cmd.colorscheme \\).*/\\1\"onelight\"/'", "~/.config/nvim/lua/config/load-colorscheme.lua"]);
} }
-242
View File
@@ -1,242 +0,0 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
import ZShell.Internal
import ZShell.Config
Singleton {
id: root
property real _downloadSpeed: 0
property real _downloadTotal: 0
property real _initialRxBytes: 0
property real _initialTxBytes: 0
property bool _initialized: false
property real _prevRxBytes: 0
property real _prevTimestamp: 0
property real _prevTxBytes: 0
property real _uploadSpeed: 0
property real _uploadTotal: 0
readonly property CircularBuffer downloadBuffer: _downloadBuffer
readonly property real downloadSpeed: _downloadSpeed
readonly property real downloadTotal: _downloadTotal
readonly property int historyLength: Config.dashboard.performance.showVram ? 70 : 30
property int refCount: 0
readonly property CircularBuffer uploadBuffer: _uploadBuffer
readonly property real uploadSpeed: _uploadSpeed
readonly property real uploadTotal: _uploadTotal
function resetSampling(): void {
netDevFile.reload();
const content = netDevFile.text();
if (!content)
return;
const data = root.parseNetDev(content);
const now = Date.now();
root._prevRxBytes = data.rx;
root._prevTxBytes = data.tx;
root._prevTimestamp = now;
}
function formatBytes(bytes: real): var {
// Handle negative or invalid values
if (bytes < 0 || isNaN(bytes) || !isFinite(bytes)) {
return {
value: 0,
unit: "B/s"
};
}
if (bytes < 1024) {
return {
value: bytes,
unit: "B/s"
};
} else if (bytes < 1024 * 1024) {
return {
value: bytes / 1024,
unit: "KB/s"
};
} else if (bytes < 1024 * 1024 * 1024) {
return {
value: bytes / (1024 * 1024),
unit: "MB/s"
};
} else {
return {
value: bytes / (1024 * 1024 * 1024),
unit: "GB/s"
};
}
}
function formatBytesTotal(bytes: real): var {
// Handle negative or invalid values
if (bytes < 0 || isNaN(bytes) || !isFinite(bytes)) {
return {
value: 0,
unit: "B"
};
}
if (bytes < 1024) {
return {
value: bytes,
unit: "B"
};
} else if (bytes < 1024 * 1024) {
return {
value: bytes / 1024,
unit: "KB"
};
} else if (bytes < 1024 * 1024 * 1024) {
return {
value: bytes / (1024 * 1024),
unit: "MB"
};
} else {
return {
value: bytes / (1024 * 1024 * 1024),
unit: "GB"
};
}
}
function parseNetDev(content: string): var {
const lines = content.split("\n");
let totalRx = 0;
let totalTx = 0;
for (let i = 2; i < lines.length; i++) {
const line = lines[i].trim();
if (!line)
continue;
const parts = line.split(/\s+/);
if (parts.length < 10)
continue;
const iface = parts[0].replace(":", "");
// Skip loopback interface
if (iface === "lo")
continue;
const rxBytes = parseFloat(parts[1]) || 0;
const txBytes = parseFloat(parts[9]) || 0;
totalRx += rxBytes;
totalTx += txBytes;
}
return {
rx: totalRx,
tx: totalTx
};
}
CircularBuffer {
id: _downloadBuffer
capacity: root.historyLength + 1
}
CircularBuffer {
id: _uploadBuffer
capacity: root.historyLength + 1
}
FileView {
id: netDevFile
path: "/proc/net/dev"
}
Timer {
interval: Config.dashboard.resourceUpdateInterval
repeat: true
running: true
triggeredOnStart: true
onTriggered: {
if (root.refCount <= 0) {
root.resetSampling();
return;
}
netDevFile.reload();
const content = netDevFile.text();
if (!content)
return;
const data = root.parseNetDev(content);
const now = Date.now();
if (!root._initialized) {
root._initialRxBytes = data.rx;
root._initialTxBytes = data.tx;
root._prevRxBytes = data.rx;
root._prevTxBytes = data.tx;
root._prevTimestamp = now;
root._initialized = true;
return;
}
const timeDelta = (now - root._prevTimestamp) / 1000; // seconds
if (timeDelta > 0) {
// Calculate byte deltas
let rxDelta = data.rx - root._prevRxBytes;
let txDelta = data.tx - root._prevTxBytes;
// Handle counter overflow (when counters wrap around from max to 0)
// This happens when counters exceed 32-bit or 64-bit limits
if (rxDelta < 0) {
// Counter wrapped around - assume 64-bit counter
rxDelta += Math.pow(2, 64);
}
if (txDelta < 0) {
txDelta += Math.pow(2, 64);
}
// Calculate speeds
root._downloadSpeed = rxDelta / timeDelta;
root._uploadSpeed = txDelta / timeDelta;
if (root._downloadSpeed >= 0 && isFinite(root._downloadSpeed))
_downloadBuffer.push(root._downloadSpeed);
if (root._uploadSpeed >= 0 && isFinite(root._uploadSpeed))
_uploadBuffer.push(root._uploadSpeed);
}
// Calculate totals with overflow handling
let downTotal = data.rx - root._initialRxBytes;
let upTotal = data.tx - root._initialTxBytes;
// Handle counter overflow for totals
if (downTotal < 0) {
downTotal += Math.pow(2, 64);
}
if (upTotal < 0) {
upTotal += Math.pow(2, 64);
}
root._downloadTotal = downTotal;
root._uploadTotal = upTotal;
root._prevRxBytes = data.rx;
root._prevTxBytes = data.tx;
root._prevTimestamp = now;
}
}
}
@@ -9,13 +9,13 @@ Singleton {
id: root id: root
readonly property bool enabled: service.enabled readonly property bool enabled: service.enabled
readonly property int end: Config.general.color.scheduleHyprsunsetEnd readonly property int end: Config.display.nightlight.scheduleEnd
property bool manualToggle: false property bool manualToggle: false
readonly property int start: Config.general.color.scheduleHyprsunsetStart readonly property int start: Config.display.nightlight.scheduleStart
readonly property int temp: Config.general.color.hyprsunsetTemp readonly property int temp: Config.display.nightlight.temp
function checkStartup(): void { function checkStartup(): void {
if (!Config.general.color.scheduleHyprsunset) if (!Config.display.nightlight.schedule)
return; return;
service.apply(); service.apply();
@@ -26,12 +26,14 @@ Singleton {
service.toggle(); service.toggle();
} }
HyprsunsetManager { NightlightManager {
id: service id: service
activeAuto: Config.general.color.scheduleHyprsunset activeAuto: Config.display.nightlight.schedule
endTime: root.end endTime: root.end
startTime: root.start startTime: root.start
temp: root.temp temp: root.temp
fadeDuration: Config.display.nightlight.fadeDuration / 1000
useNativeNightlight: Config.display.nightlight.useNative
} }
} }
+1 -1
View File
@@ -42,7 +42,7 @@ Item {
readonly property real scaleRatio: Hypr.monitorFor(screen).scale readonly property real scaleRatio: Hypr.monitorFor(screen).scale
required property ShellScreen screen required property ShellScreen screen
property real sh: Math.abs(sy - ey) property real sh: Math.abs(sy - ey)
readonly property color shadowColor: Hypr.options.decoration.shadow.color readonly property color shadowColor: Hypr.options.decoration.shadow?.color ?? "black"
readonly property bool shadowEnabled: Hypr.options.decoration.shadow.enabled readonly property bool shadowEnabled: Hypr.options.decoration.shadow.enabled
readonly property var shadowOffset: Hypr.options.decoration.shadow.offset readonly property var shadowOffset: Hypr.options.decoration.shadow.offset
readonly property int shadowRange: Hypr.options.decoration.shadow.range readonly property int shadowRange: Hypr.options.decoration.shadow.range
+7 -7
View File
@@ -73,7 +73,7 @@ CustomRect {
required property var modelData required property var modelData
color: root.tool === modelData.id ? "#3584e4" : "transparent" color: root.tool === modelData.id ? "#3584e4" : "transparent"
font.pointSize: Tokens.font.size.large font: Tokens.font.icon.medium
icon: tool.modelData.glyph icon: tool.modelData.glyph
inactiveColor: root.tool === modelData.id ? Colors.palette.m3primary : "transparent" inactiveColor: root.tool === modelData.id ? Colors.palette.m3primary : "transparent"
inactiveOnColor: root.tool === modelData.id ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface inactiveOnColor: root.tool === modelData.id ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
@@ -103,7 +103,7 @@ CustomRect {
required property color modelData required property color modelData
fillWidth: false fillWidth: false
font.pointSize: Tokens.font.size.large font: Tokens.font.icon.medium
icon: "" icon: ""
implicitHeight: buttonSize implicitHeight: buttonSize
implicitWidth: buttonSize implicitWidth: buttonSize
@@ -128,7 +128,7 @@ CustomRect {
spacing: Tokens.spacing.extraSmall spacing: Tokens.spacing.extraSmall
IconButton { IconButton {
font.pointSize: Tokens.font.size.large font: Tokens.font.icon.medium
icon: "undo" icon: "undo"
inactiveColor: "transparent" inactiveColor: "transparent"
inactiveOnColor: Colors.palette.m3onSurface inactiveOnColor: Colors.palette.m3onSurface
@@ -140,7 +140,7 @@ CustomRect {
} }
IconButton { IconButton {
font.pointSize: Tokens.font.size.large font: Tokens.font.icon.medium
icon: "delete_history" icon: "delete_history"
inactiveColor: "transparent" inactiveColor: "transparent"
inactiveOnColor: Colors.palette.m3onSurface inactiveOnColor: Colors.palette.m3onSurface
@@ -152,7 +152,7 @@ CustomRect {
} }
IconButton { IconButton {
font.pointSize: Tokens.font.size.large font: Tokens.font.icon.medium
icon: "content_copy" icon: "content_copy"
inactiveColor: "transparent" inactiveColor: "transparent"
inactiveOnColor: Colors.palette.m3onSurface inactiveOnColor: Colors.palette.m3onSurface
@@ -164,7 +164,7 @@ CustomRect {
} }
IconButton { IconButton {
font.pointSize: Tokens.font.size.large font: Tokens.font.icon.medium
icon: "delete_forever" icon: "delete_forever"
inactiveColor: "transparent" inactiveColor: "transparent"
inactiveOnColor: Colors.palette.m3onSurface inactiveOnColor: Colors.palette.m3onSurface
@@ -176,7 +176,7 @@ CustomRect {
} }
IconButton { IconButton {
font.pointSize: Tokens.font.size.large font: Tokens.font.icon.medium
icon: "check" icon: "check"
inactiveColor: "transparent" inactiveColor: "transparent"
inactiveOnColor: Colors.palette.m3onSurface inactiveOnColor: Colors.palette.m3onSurface
+9
View File
@@ -5,6 +5,15 @@ import Quickshell
Singleton { Singleton {
property var _regexCache: ({}) property var _regexCache: ({})
function percent(value: int): string {
// TRANSLATORS: %1 = a number
return qsTr("%1%").arg(value);
}
function percentOne(value: real): string {
return percent(Math.round(value * 100));
}
function testRegexList(filterList: list<string>, target: string): bool { function testRegexList(filterList: list<string>, target: string): bool {
const regexChecker = /^\^.*\$$/; const regexChecker = /^\^.*\$$/;
for (const filter of filterList) { for (const filter of filterList) {
-16
View File
@@ -1,16 +0,0 @@
pragma Singleton
import Quickshell
Singleton {
property var bars: new Map()
property var screens: new Map()
function getForActive(): PersistentProperties {
return screens.get(Hypr.focusedMonitor);
}
function load(screen: ShellScreen, visibilities: var): void {
screens.set(Hypr.monitorFor(screen), visibilities);
}
}
+3 -3
View File
@@ -38,7 +38,7 @@ Searcher {
previewPath = path; previewPath = path;
showPreview = true; showPreview = true;
if (Config.general.color.schemeGeneration) if (Config.colors.schemeGen)
previewColorsProc.running = true; previewColorsProc.running = true;
} }
@@ -62,8 +62,8 @@ Searcher {
WallpaperPath.currentWallpaperPath = path; WallpaperPath.currentWallpaperPath = path;
Quickshell.screens.forEach(n => setCrop(n.name, Qt.rect(0, 0, 1, 1), 1.0)); Quickshell.screens.forEach(n => setCrop(n.name, Qt.rect(0, 0, 1, 1), 1.0));
Quickshell.execDetached(["zshell-cli", "wallpaper", "lockscreen", "--input-image", `${root.actualCurrent}`, "--output-path", `${Paths.state}/lockscreen_bg.png`, "--blur-amount", `${Config.lock.blurAmount}`]); Quickshell.execDetached(["zshell-cli", "wallpaper", "lockscreen", "--input-image", `${root.actualCurrent}`, "--output-path", `${Paths.state}/lockscreen_bg.png`, "--blur-amount", `${Config.lock.blurAmount}`]);
if (Config.general.color.schemeGeneration) if (Config.colors.schemeGen)
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${root.actualCurrent}`, "--scheme", `${Config.colors.schemeType}`, "--mode", `${Config.general.color.mode}`]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${root.actualCurrent}`, "--scheme", `${Config.colors.schemeType}`, "--mode", `${Config.colors.mode}`]);
} }
function stopPreview(): void { function stopPreview(): void {
+28 -7
View File
@@ -3,25 +3,36 @@ pragma Singleton
import QtQuick import QtQuick
import Quickshell import Quickshell
import ZShell import ZShell
import ZShell.Internal
import ZShell.Config import ZShell.Config
Singleton { Singleton {
id: root id: root
readonly property var cachedCities: new Map() readonly property var cachedCities: new Map()
property var cc
property string city
readonly property string description: cc?.weatherDesc ?? qsTr("No weather") readonly property string description: cc?.weatherDesc ?? qsTr("No weather")
readonly property string feelsLike: formatTemp(cc?.feelsLikeC) readonly property string feelsLike: formatTemp(cc?.feelsLikeC)
property list<var> forecast
property list<var> hourlyForecast
readonly property int humidity: cc?.humidity ?? 0 readonly property int humidity: cc?.humidity ?? 0
readonly property string icon: cc ? Icons.getWeatherIcon(cc.weatherCode) : "cloud_alert" readonly property string icon: cc ? Icons.getWeatherIcon(cc.weatherCode) : "cloud_alert"
property string loc
readonly property string sunrise: cc ? Qt.formatDateTime(new Date(cc.sunrise), Config.services.useTwelveHourClock ? "h:mm A" : "h:mm") : "--:--" readonly property string sunrise: cc ? Qt.formatDateTime(new Date(cc.sunrise), Config.services.useTwelveHourClock ? "h:mm A" : "h:mm") : "--:--"
readonly property string sunset: cc ? Qt.formatDateTime(new Date(cc.sunset), Config.services.useTwelveHourClock ? "h:mm A" : "h:mm") : "--:--" readonly property string sunset: cc ? Qt.formatDateTime(new Date(cc.sunset), Config.services.useTwelveHourClock ? "h:mm A" : "h:mm") : "--:--"
readonly property string temp: formatTemp(cc?.tempC) readonly property string temp: formatTemp(cc?.tempC)
readonly property real windSpeed: cc?.windSpeed ?? 0 readonly property real windSpeed: cc?.windSpeed ?? 0
readonly property CircularBuffer precipitationForecast: precipitationBuffer
property var cc
property string city
property list<var> forecast
property list<var> hourlyForecast
readonly property int currentHourIndex: {
const now = new Date();
for (let i = 0; i < hourlyForecast.length; i++) {
if (new Date(hourlyForecast[i].timestamp.replace("T", " ")) >= now)
return i;
}
return -1;
}
property string loc
function fetchCityFromCoords(coords: string): void { function fetchCityFromCoords(coords: string): void {
if (cachedCities.has(coords)) { if (cachedCities.has(coords)) {
@@ -112,11 +123,12 @@ Singleton {
forecast = forecastList; forecast = forecastList;
const hourlyList = []; const hourlyList = [];
const now = new Date(); const startOfToday = new Date();
startOfToday.setHours(0, 0, 0, 0);
for (let i = 0; i < json.hourly.time.length; i++) { for (let i = 0; i < json.hourly.time.length; i++) {
const time = new Date(json.hourly.time[i].replace("T", " ")); const time = new Date(json.hourly.time[i].replace("T", " "));
if (time < now) if (time < startOfToday)
continue; continue;
hourlyList.push({ hourlyList.push({
@@ -129,6 +141,10 @@ Singleton {
}); });
} }
hourlyForecast = hourlyList; hourlyForecast = hourlyList;
precipitationBuffer.clear();
for (const hour of hourlyList)
precipitationBuffer.push(hour.precipChance);
}); });
} }
@@ -258,6 +274,11 @@ Singleton {
onLocChanged: fetchWeatherData() onLocChanged: fetchWeatherData()
CircularBuffer {
id: precipitationBuffer
capacity: 24
}
Connections { Connections {
function onWeatherLocationChanged(): void { function onWeatherLocationChanged(): void {
root.reload(); root.reload();
+9 -9
View File
@@ -4,6 +4,7 @@ import Quickshell
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import ZShell.Config import ZShell.Config
import qs.Components
import qs.Modules.Bar.Components import qs.Modules.Bar.Components
import qs.Modules.Bar.Popouts import qs.Modules.Bar.Popouts
@@ -17,7 +18,7 @@ GridLayout {
required property ClipWrapper popoutsWrapper required property ClipWrapper popoutsWrapper
required property ShellScreen screen required property ShellScreen screen
readonly property int vPadding: 6 readonly property int vPadding: 6
required property PersistentProperties visibilities required property ScreenState screenState
function axisCenterOf(item: Item): real { function axisCenterOf(item: Item): real {
const c = item.mapToItem(root, item.implicitWidth / 2, item.implicitHeight / 2); const c = item.mapToItem(root, item.implicitWidth / 2, item.implicitHeight / 2);
@@ -34,7 +35,7 @@ GridLayout {
return; return;
} }
if (visibilities.sidebar || visibilities.dashboard || visibilities.resources || visibilities.settings) if (screenState.sidebar || screenState.dashboard || screenState.resources || screenState.settings)
return; return;
if (id === "statusIcons" && Config.bar.popouts.statusIcons) { if (id === "statusIcons" && Config.bar.popouts.statusIcons) {
@@ -88,10 +89,10 @@ GridLayout {
role: "id" role: "id"
DelegateChoice { DelegateChoice {
roleValue: "hyprsunset" roleValue: "nightlight"
delegate: EntryWrapper { delegate: EntryWrapper {
HyprsunsetWidget { NightlightWidget {
horizontal: root.horizontal horizontal: root.horizontal
visible: !root.fullscreen || root.isHovered visible: !root.fullscreen || root.isHovered
} }
@@ -149,7 +150,7 @@ GridLayout {
delegate: EntryWrapper { delegate: EntryWrapper {
Resources { Resources {
horizontal: root.horizontal horizontal: root.horizontal
visibilities: root.visibilities screenState: root.screenState
visible: !root.fullscreen || root.isHovered visible: !root.fullscreen || root.isHovered
} }
} }
@@ -173,7 +174,7 @@ GridLayout {
NotifBell { NotifBell {
horizontal: root.horizontal horizontal: root.horizontal
popouts: root.popouts popouts: root.popouts
visibilities: root.visibilities screenState: root.screenState
visible: !root.fullscreen || root.isHovered visible: !root.fullscreen || root.isHovered
} }
} }
@@ -187,7 +188,7 @@ GridLayout {
horizontal: root.horizontal horizontal: root.horizontal
loader: root loader: root
popouts: root.popouts popouts: root.popouts
visibilities: root.visibilities screenState: root.screenState
visible: !root.fullscreen || root.isHovered visible: !root.fullscreen || root.isHovered
} }
} }
@@ -209,8 +210,7 @@ GridLayout {
roleValue: "network" roleValue: "network"
delegate: EntryWrapper { delegate: EntryWrapper {
NetworkWidget { NetworkWidget {}
}
} }
} }
+4 -4
View File
@@ -10,7 +10,7 @@ Item {
id: root id: root
readonly property int clampedExtent: Math.max(Config.bar.border, extent) readonly property int clampedExtent: Math.max(Config.bar.border, extent)
readonly property int contentThickness: Math.floor(Math.max(Config.bar.height, Tokens.bar.innerSize * (horizontal ? 1 : 1.5))) + padding * 2 readonly property int contentThickness: Math.floor(Math.max(Config.bar.height, Tokens.sizes.bar.innerSize * (horizontal ? 1 : 1.5))) + padding * 2
readonly property int exclusiveZone: Config.bar.autoHide ? Config.bar.border : contentThickness readonly property int exclusiveZone: Config.bar.autoHide ? Config.bar.border : contentThickness
property real extent: fullscreen ? 0 : Config.bar.border property real extent: fullscreen ? 0 : Config.bar.border
required property bool fullscreen required property bool fullscreen
@@ -21,9 +21,9 @@ Item {
required property ClipWrapper popoutsWrapper required property ClipWrapper popoutsWrapper
required property string position required property string position
required property ShellScreen screen required property ShellScreen screen
readonly property bool shouldBeVisible: (!fullscreen && (!Config.bar.autoHide || visibilities.bar)) || isHovered readonly property bool shouldBeVisible: (!fullscreen && (!Config.bar.autoHide || screenState.bar)) || isHovered
readonly property int vPadding: 6 readonly property int vPadding: 6
required property PersistentProperties visibilities required property ScreenState screenState
function checkPopout(pos: real): void { function checkPopout(pos: real): void {
(content.item as Bar)?.checkPopout(pos); (content.item as Bar)?.checkPopout(pos);
@@ -85,7 +85,7 @@ Item {
popouts: root.popouts popouts: root.popouts
popoutsWrapper: root.popoutsWrapper popoutsWrapper: root.popoutsWrapper
screen: root.screen screen: root.screen
visibilities: root.visibilities screenState: root.screenState
} }
} }
} }
+1 -1
View File
@@ -11,7 +11,7 @@ Item {
id: root id: root
required property Item bar required property Item bar
required property PersistentProperties visibilities required property ScreenState screenState
anchors.fill: parent anchors.fill: parent
+8 -17
View File
@@ -13,7 +13,8 @@ import qs.Services
WidgetBase { WidgetBase {
id: root id: root
readonly property color contentColor: visibilities.dashboard ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface readonly property font font: Tokens.font.clock.family(Config.appearance.font.family.clock).build()
readonly property color contentColor: Colors.palette.m3onSurface
readonly property string effectiveFormat: { readonly property string effectiveFormat: {
let fmt = Config.general.dateFormat; let fmt = Config.general.dateFormat;
if (!root.horizontal) if (!root.horizontal)
@@ -44,10 +45,9 @@ WidgetBase {
} }
required property GridLayout loader required property GridLayout loader
required property Wrapper popouts required property Wrapper popouts
readonly property real size: horizontal ? timeText.contentWidth + Tokens.padding.small * 2 : verticalColumn.implicitHeight + Tokens.padding.small * 2 readonly property real size: horizontal ? timeText.contentWidth + Tokens.padding.medium * 2 : verticalColumn.implicitHeight + Tokens.padding.medium * 2
required property PersistentProperties visibilities required property ScreenState screenState
color: visibilities.dashboard ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer
implicitHeight: horizontal ? baseSize : size implicitHeight: horizontal ? baseSize : size
implicitWidth: horizontal ? size : baseSize implicitWidth: horizontal ? size : baseSize
radius: Tokens.rounding.full radius: Tokens.rounding.full
@@ -57,7 +57,7 @@ WidgetBase {
anchors.centerIn: parent anchors.centerIn: parent
color: root.contentColor color: root.contentColor
font.family: Config.appearance.font.family.mono // qmllint disable incompatible-type font: root.font
height: implicitHeight height: implicitHeight
text: Time.dateStr text: Time.dateStr
visible: root.horizontal visible: root.horizontal
@@ -84,7 +84,7 @@ WidgetBase {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
color: root.contentColor color: root.contentColor
font.family: Config.appearance.font.family.mono // qmllint disable incompatible-type font: root.font
text: Qt.formatDateTime(Time.date, modelData) text: Qt.formatDateTime(Time.date, modelData)
Behavior on color { Behavior on color {
@@ -114,7 +114,7 @@ WidgetBase {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
color: root.contentColor color: root.contentColor
font.family: Config.appearance.font.family.mono // qmllint disable incompatible-type font: root.font
text: { text: {
if (modelData.includes("h")) if (modelData.includes("h"))
return Time.hourStr; return Time.hourStr;
@@ -135,7 +135,7 @@ WidgetBase {
CustomText { CustomText {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
color: root.contentColor color: root.contentColor
font.family: Config.appearance.font.family.mono // qmllint disable incompatible-type font: root.font
text: Qt.formatDateTime(Time.date, "AP") text: Qt.formatDateTime(Time.date, "AP")
visible: Config.services.useTwelveHourClock && root.formatParts.timeTokens.length > 0 visible: Config.services.useTwelveHourClock && root.formatParts.timeTokens.length > 0
@@ -145,13 +145,4 @@ WidgetBase {
} }
} }
} }
StateLayer {
acceptedButtons: Qt.LeftButton
color: root.contentColor
onClicked: {
root.visibilities.dashboard = !root.visibilities.dashboard;
}
}
} }
@@ -28,8 +28,7 @@ Row {
CustomText { CustomText {
color: Battery.getColors(root.percentage, root.devState).fg color: Battery.getColors(root.percentage, root.devState).fg
font.pointSize: Tokens.font.size.larger / 1.5 font: Tokens.font.body.builders.small.size(11).weight(Font.Bold).build()
font.weight: 800
height: track.height height: track.height
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: Math.round(root.percentage * 100) text: Math.round(root.percentage * 100)
@@ -60,8 +59,7 @@ Row {
clip: true clip: true
color: Battery.getColors(root.percentage, root.devState).bg color: Battery.getColors(root.percentage, root.devState).bg
font.pointSize: Tokens.font.size.larger / 1.5 font: Tokens.font.body.builders.small.size(11).weight(Font.Bold).build()
font.weight: 800
height: track.height height: track.height
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: Math.round(root.percentage * 100) text: Math.round(root.percentage * 100)
+1 -1
View File
@@ -6,7 +6,7 @@ import qs.Services
CustomRect { CustomRect {
id: root id: root
readonly property int baseSize: horizontal ? Math.max(Config.bar.height, Tokens.bar.innerSize) : Math.max(Config.bar.height, Math.floor(Tokens.bar.innerSize * 1.5)) readonly property int baseSize: horizontal ? Math.max(Config.bar.height, Tokens.sizes.bar.innerSize) : Math.max(Config.bar.height, Math.floor(Tokens.sizes.bar.innerSize * 1.5))
required property bool horizontal required property bool horizontal
color: Colors.tPalette.m3surfaceContainer color: Colors.tPalette.m3surfaceContainer
+4 -8
View File
@@ -19,14 +19,12 @@ WidgetBase {
Behavior on implicitHeight { Behavior on implicitHeight {
enabled: !root.horizontal enabled: !root.horizontal
Anim { Anim {}
}
} }
Behavior on implicitWidth { Behavior on implicitWidth {
enabled: root.horizontal enabled: root.horizontal
Anim { Anim {}
}
} }
TextMetrics { TextMetrics {
@@ -47,15 +45,14 @@ WidgetBase {
columns: root.horizontal ? -1 : 1 columns: root.horizontal ? -1 : 1
Behavior on implicitWidth { Behavior on implicitWidth {
Anim { Anim {}
}
} }
MaterialIcon { MaterialIcon {
Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter
animate: true animate: true
color: Players.active?.isPlaying ? Colors.palette.m3primary : Colors.palette.m3onSurface color: Players.active?.isPlaying ? Colors.palette.m3primary : Colors.palette.m3onSurface
font.pointSize: Tokens.font.size.larger fontStyle: Tokens.font.icon.small
text: Players.active?.isPlaying ? "music_note" : "music_off" text: Players.active?.isPlaying ? "music_note" : "music_off"
} }
@@ -65,7 +62,6 @@ WidgetBase {
Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter
animate: true animate: true
color: Players.active?.isPlaying ? Colors.palette.m3primary : Colors.palette.m3onSurface color: Players.active?.isPlaying ? Colors.palette.m3primary : Colors.palette.m3onSurface
font.pointSize: Tokens.font.size.normal
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
implicitHeight: root.horizontal ? root.implicitHeight : root.textWidth implicitHeight: root.horizontal ? root.implicitHeight : root.textWidth
implicitWidth: root.horizontal ? root.textWidth : root.implicitWidth implicitWidth: root.horizontal ? root.textWidth : root.implicitWidth
@@ -8,15 +8,15 @@ import qs.Services
WidgetBase { WidgetBase {
id: root id: root
property bool tempEnabled: Hyprsunset.enabled property bool tempEnabled: Nightlight.enabled
color: root.tempEnabled ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer color: root.tempEnabled ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer
radius: Tokens.rounding.full radius: Tokens.rounding.full
StateLayer { StateLayer {
onClicked: { onClicked: {
Hyprsunset.toggleNightLight(); Nightlight.toggleNightLight();
Hyprsunset.manualToggle = true; Nightlight.manualToggle = true;
} }
} }
@@ -25,12 +25,11 @@ WidgetBase {
animate: true animate: true
color: root.tempEnabled ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface color: root.tempEnabled ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
fill: root.tempEnabled ? 1 : 0 fill: root.tempEnabled ? 1 : 0
font.pointSize: Tokens.font.size.larger fontStyle: Tokens.font.icon.small
text: root.tempEnabled ? "lightbulb" : "light_off" text: root.tempEnabled ? "lightbulb" : "light_off"
Behavior on fill { Behavior on fill {
Anim { Anim {}
}
} }
} }
} }
+9 -11
View File
@@ -11,35 +11,33 @@ WidgetBase {
id: root id: root
required property Wrapper popouts required property Wrapper popouts
required property PersistentProperties visibilities required property ScreenState screenState
color: visibilities.sidebar ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer color: screenState.sidebar ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer
radius: Tokens.rounding.full radius: Tokens.rounding.full
MaterialIcon { MaterialIcon {
id: notificationCenterIcon id: notificationCenterIcon
anchors.centerIn: parent anchors.centerIn: parent
color: root.visibilities.sidebar ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface color: root.screenState.sidebar ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
fill: root.visibilities.sidebar ? 1 : 0 fill: root.screenState.sidebar ? 1 : 0
font.pointSize: Tokens.font.size.larger fontStyle: Tokens.font.icon.size(16).build()
text: NotifServer.list.length ? "\uf4fe" : "\ue7f4" text: NotifServer.list.length ? "\uf4fe" : "\ue7f4"
Behavior on color { Behavior on color {
CAnim { CAnim {}
}
} }
Behavior on fill { Behavior on fill {
Anim { Anim {}
}
} }
} }
StateLayer { StateLayer {
color: root.visibilities.sidebar ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface color: root.screenState.sidebar ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
onClicked: { onClicked: {
root.visibilities.sidebar = !root.visibilities.sidebar; root.screenState.sidebar = !root.screenState.sidebar;
} }
} }
} }
+2 -3
View File
@@ -27,8 +27,7 @@ GridLayout {
percentage: 0 percentage: 0
Behavior on animatedPercentage { Behavior on animatedPercentage {
Anim { Anim {}
}
} }
Component.onCompleted: animatedPercentage = percentage Component.onCompleted: animatedPercentage = percentage
@@ -44,7 +43,7 @@ GridLayout {
Layout.preferredWidth: 16 Layout.preferredWidth: 16
color: root.iconColor color: root.iconColor
font.pointSize: Tokens.font.size.larger fontStyle: Tokens.font.icon.small
text: root.icon text: root.icon
} }
+12 -12
View File
@@ -12,18 +12,18 @@ import qs.Components
WidgetBase { WidgetBase {
id: root id: root
required property PersistentProperties visibilities required property ScreenState screenState
clip: true clip: true
color: visibilities.resources ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer color: screenState.resources ? Colors.palette.m3primary : Colors.tPalette.m3surfaceContainer
implicitHeight: horizontal ? baseSize : gridLayout.implicitHeight + Tokens.padding.medium * 2 implicitHeight: horizontal ? baseSize : gridLayout.implicitHeight + Tokens.padding.medium * 2
implicitWidth: horizontal ? gridLayout.implicitWidth + Tokens.padding.medium * 2 : baseSize implicitWidth: horizontal ? gridLayout.implicitWidth + Tokens.padding.medium * 2 : baseSize
radius: Tokens.rounding.full radius: Tokens.rounding.full
StateLayer { StateLayer {
color: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface color: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
onClicked: root.visibilities.resources = !root.visibilities.resources onClicked: root.screenState.resources = !root.screenState.resources
} }
GridLayout { GridLayout {
@@ -53,8 +53,8 @@ WidgetBase {
Layout.fillWidth: !root.horizontal Layout.fillWidth: !root.horizontal
horizontal: root.horizontal horizontal: root.horizontal
icon: "memory" icon: "memory"
iconColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface iconColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
mainColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3primary mainColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3primary
percentage: Cpu.percentage percentage: Cpu.percentage
warningThreshold: 95 warningThreshold: 95
} }
@@ -64,8 +64,8 @@ WidgetBase {
Layout.fillWidth: !root.horizontal Layout.fillWidth: !root.horizontal
horizontal: root.horizontal horizontal: root.horizontal
icon: "memory_alt" icon: "memory_alt"
iconColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface iconColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
mainColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3secondary mainColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3secondary
percentage: Memory.percentage percentage: Memory.percentage
warningThreshold: 80 warningThreshold: 80
} }
@@ -75,8 +75,8 @@ WidgetBase {
Layout.fillWidth: !root.horizontal Layout.fillWidth: !root.horizontal
horizontal: root.horizontal horizontal: root.horizontal
icon: "gamepad" icon: "gamepad"
iconColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface iconColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
mainColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3tertiary mainColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3tertiary
percentage: Gpu.percentage percentage: Gpu.percentage
} }
@@ -85,8 +85,8 @@ WidgetBase {
Layout.fillWidth: !root.horizontal Layout.fillWidth: !root.horizontal
horizontal: root.horizontal horizontal: root.horizontal
icon: "developer_board" icon: "developer_board"
iconColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface iconColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
mainColor: root.visibilities.resources ? Colors.palette.m3onPrimary : Colors.palette.m3primary mainColor: root.screenState.resources ? Colors.palette.m3onPrimary : Colors.palette.m3primary
percentage: Gpu.memoryUsed / Gpu.memoryTotal percentage: Gpu.memoryUsed / Gpu.memoryTotal
} }
} }
@@ -13,19 +13,16 @@ MaterialIcon {
animate: true animate: true
color: Audio.muted ? Colors.palette.m3error : Colors.palette.m3onSurface color: Audio.muted ? Colors.palette.m3error : Colors.palette.m3onSurface
fill: 1 fill: 1
font.pointSize: Tokens.font.size.larger fontStyle: Tokens.font.icon.small
text: Audio.muted ? "volume_off" : "volume_up" text: Audio.muted ? "volume_off" : "volume_up"
Behavior on Layout.maximumWidth { Behavior on Layout.maximumWidth {
Anim { Anim {}
}
} }
Behavior on opacity { Behavior on opacity {
Anim { Anim {}
}
} }
Behavior on scale { Behavior on scale {
Anim { Anim {}
}
} }
} }
@@ -13,19 +13,16 @@ MaterialIcon {
animate: true animate: true
color: (Audio.sourceMuted ?? false) ? Colors.palette.m3error : Colors.palette.m3onSurface color: (Audio.sourceMuted ?? false) ? Colors.palette.m3error : Colors.palette.m3onSurface
fill: 1 fill: 1
font.pointSize: Tokens.font.size.larger fontStyle: Tokens.font.icon.small
text: Audio.sourceMuted ? "mic_off" : "mic" text: Audio.sourceMuted ? "mic_off" : "mic"
Behavior on Layout.maximumWidth { Behavior on Layout.maximumWidth {
Anim { Anim {}
}
} }
Behavior on opacity { Behavior on opacity {
Anim { Anim {}
}
} }
Behavior on scale { Behavior on scale {
Anim { Anim {}
}
} }
} }
@@ -15,20 +15,16 @@ Item {
implicitWidth: currentItem?.implicitWidth ?? 0 implicitWidth: currentItem?.implicitWidth ?? 0
Behavior on Layout.preferredHeight { Behavior on Layout.preferredHeight {
Anim { Anim {}
}
} }
Behavior on Layout.preferredWidth { Behavior on Layout.preferredWidth {
Anim { Anim {}
}
} }
Behavior on opacity { Behavior on opacity {
Anim { Anim {}
}
} }
Behavior on scale { Behavior on scale {
Anim { Anim {}
}
} }
Component.onCompleted: Battery.startDaemon() Component.onCompleted: Battery.startDaemon()
@@ -67,7 +63,7 @@ Item {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
animate: true animate: true
fill: 1 fill: 1
font.pointSize: Tokens.font.size.larger fontStyle: Tokens.font.icon.small
text: { text: {
if (PowerProfiles.profile === PowerProfile.PowerSaver) if (PowerProfiles.profile === PowerProfile.PowerSaver)
return "energy_savings_leaf"; return "energy_savings_leaf";
+4 -4
View File
@@ -62,10 +62,10 @@ Item {
root.popouts.currentName = `traymenu${root.ind}`; root.popouts.currentName = `traymenu${root.ind}`;
root.popouts.currentCenter = Qt.binding(() => root.mapToItem(root.loader, root.implicitWidth / 2, 0).x); root.popouts.currentCenter = Qt.binding(() => root.mapToItem(root.loader, root.implicitWidth / 2, 0).x);
root.popouts.hasCurrent = true; root.popouts.hasCurrent = true;
if (visibilities.sidebar || visibilities.dashboard || visibilities.settings) { if (screenState.sidebar || screenState.dashboard || screenState.settings) {
visibilities.sidebar = false; screenState.sidebar = false;
visibilities.dashboard = false; screenState.dashboard = false;
visibilities.settings = false; screenState.settings = false;
} }
} }
} }
+1 -1
View File
@@ -42,7 +42,7 @@ WidgetBase {
Repeater { Repeater {
id: repeater id: repeater
model: SystemTray.items model: SystemTray.items.values.filter(i => i.status !== Status.Passive && i.hasMenu)
TrayItem { TrayItem {
id: trayItem id: trayItem
+1 -2
View File
@@ -27,14 +27,13 @@ WidgetBase {
MaterialIcon { MaterialIcon {
Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter
font.pointSize: root.horizontal ? Tokens.font.size.larger : Tokens.font.size.large fontStyle: Tokens.font.icon.small
text: "package_2" text: "package_2"
} }
CustomText { CustomText {
Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter Layout.alignment: root.horizontal ? Qt.AlignVCenter : Qt.AlignHCenter
color: root.textColor color: root.textColor
font.pointSize: Tokens.font.size.normal
text: root.countUpdates text: root.countUpdates
} }
} }
+2 -4
View File
@@ -52,8 +52,7 @@ Item {
elide: Qt.ElideRight elide: Qt.ElideRight
elideWidth: root.maxSize - (root.horizontal ? root.current.anchors.leftMargin : 0) elideWidth: root.maxSize - (root.horizontal ? root.current.anchors.leftMargin : 0)
font.family: "Rubik" font: Tokens.font.body.small
font.pointSize: Tokens.font.size.normal
text: Hypr.activeToplevel?.title ?? qsTr("Desktop") text: Hypr.activeToplevel?.title ?? qsTr("Desktop")
onElideWidthChanged: root.current.text = elidedText onElideWidthChanged: root.current.text = elidedText
@@ -81,8 +80,7 @@ Item {
width: root.horizontal ? implicitWidth : implicitHeight width: root.horizontal ? implicitWidth : implicitHeight
Behavior on opacity { Behavior on opacity {
Anim { Anim {}
}
} }
transform: [ transform: [
Translate { Translate {
+3 -5
View File
@@ -17,7 +17,7 @@ Item {
required property bool horizontal required property bool horizontal
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen) readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen)
required property ShellScreen screen required property ShellScreen screen
readonly property real shortSize: Math.max(Config.bar.height, Tokens.bar.innerSize) readonly property real shortSize: Math.max(Config.bar.height, Tokens.sizes.bar.innerSize)
readonly property real size: (workspaceButtonWidth * workspacesShown) + activeWorkspaceMargin * 2 readonly property real size: (workspaceButtonWidth * workspacesShown) + activeWorkspaceMargin * 2
property int workspaceButtonWidth: (horizontal ? bgRect.implicitHeight : bgRect.implicitWidth) - root.activeWorkspaceMargin * 2 property int workspaceButtonWidth: (horizontal ? bgRect.implicitHeight : bgRect.implicitWidth) - root.activeWorkspaceMargin * 2
property int workspaceIndexInGroup: (effectiveActiveWorkspaceId - 1) % root.workspacesShown property int workspaceIndexInGroup: (effectiveActiveWorkspaceId - 1) % root.workspacesShown
@@ -30,14 +30,12 @@ Item {
Behavior on implicitHeight { Behavior on implicitHeight {
enabled: !root.horizontal enabled: !root.horizontal
Anim { Anim {}
}
} }
Behavior on implicitWidth { Behavior on implicitWidth {
enabled: root.horizontal enabled: root.horizontal
Anim { Anim {}
}
} }
CustomRect { CustomRect {
+2 -3
View File
@@ -181,8 +181,7 @@ Column {
} }
] ]
transitions: Transition { transitions: Transition {
AnchorAnim { AnchorAnim {}
}
} }
} }
@@ -246,7 +245,7 @@ Column {
anchors.centerIn: parent anchors.centerIn: parent
color: profiles.current === text ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface color: profiles.current === text ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
fill: profiles.current === text ? 1 : 0 fill: profiles.current === text ? 1 : 0
font.pointSize: Tokens.font.size.large * 2 fontStyle: Tokens.font.icon.extraLarge
grade: Colors.light ? 0 : (text === "balance" ? -25 : 0) grade: Colors.light ? 0 : (text === "balance" ? -25 : 0)
text: parent.icon text: parent.icon
+6 -6
View File
@@ -39,7 +39,7 @@ CustomClippingRect {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
color: Colors.tPalette.m3onSurfaceVariant color: Colors.tPalette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.extraLarge * 3 fontStyle: Tokens.font.icon.builders.large.size(48).weight(Font.DemiBold).build()
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: "check" text: "check"
} }
@@ -89,7 +89,7 @@ CustomClippingRect {
anchors.rightMargin: Tokens.padding.small anchors.rightMargin: Tokens.padding.small
MaterialIcon { MaterialIcon {
font.pointSize: Tokens.font.size.large * 2 fontStyle: Tokens.font.icon.extraLarge
text: "package_2" text: "package_2"
} }
@@ -100,7 +100,7 @@ CustomClippingRect {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 25 Layout.preferredHeight: 25
elide: Text.ElideRight elide: Text.ElideRight
font.pointSize: Tokens.font.size.large font: Tokens.font.title.medium
text: update.sections[0] text: update.sections[0]
} }
@@ -122,7 +122,7 @@ CustomClippingRect {
Layout.preferredWidth: 125 Layout.preferredWidth: 125
animate: true animate: true
color: Colors.palette.m3tertiary color: Colors.palette.m3tertiary
font.pointSize: Tokens.font.size.large font: Tokens.font.title.medium
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
marqueeEnabled: true marqueeEnabled: true
pauseMs: 4000 pauseMs: 4000
@@ -133,7 +133,7 @@ CustomClippingRect {
MaterialIcon { MaterialIcon {
Layout.fillHeight: true Layout.fillHeight: true
color: Colors.palette.m3secondary color: Colors.palette.m3secondary
font.pointSize: Tokens.font.size.extraLarge fontStyle: Tokens.font.icon.large
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: "arrow_right_alt" text: "arrow_right_alt"
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
@@ -146,7 +146,7 @@ CustomClippingRect {
Layout.preferredWidth: 120 Layout.preferredWidth: 120
animate: true animate: true
color: Colors.palette.m3primary color: Colors.palette.m3primary
font.pointSize: Tokens.font.size.large font: Tokens.font.title.medium
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
marqueeEnabled: true marqueeEnabled: true
pauseMs: 4000 pauseMs: 4000
+4 -6
View File
@@ -14,7 +14,7 @@ Item {
readonly property int itemHeight: Config.clipboard.sizes.itemHeight readonly property int itemHeight: Config.clipboard.sizes.itemHeight
required property ShellScreen screen required property ShellScreen screen
required property PersistentProperties visibilities required property ScreenState screenState
implicitHeight: search.implicitHeight + entries.anchors.topMargin + ((view.spacing + itemHeight) * Config.clipboard.maxEntriesShown) - view.spacing implicitHeight: search.implicitHeight + entries.anchors.topMargin + ((view.spacing + itemHeight) * Config.clipboard.maxEntriesShown) - view.spacing
implicitWidth: Config.clipboard.sizes.width + preview.width + preview.anchors.leftMargin implicitWidth: Config.clipboard.sizes.width + preview.width + preview.anchors.leftMargin
@@ -49,7 +49,7 @@ Item {
Keys.onUpPressed: view.decrementCurrentIndex() Keys.onUpPressed: view.decrementCurrentIndex()
onAccepted: { onAccepted: {
ClipHistory.copy(view.currentItem.modelData); ClipHistory.copy(view.currentItem.modelData);
root.visibilities.clipboard = false; root.screenState.clipboard = false;
} }
} }
} }
@@ -66,8 +66,7 @@ Item {
radius: 25 radius: 25
Behavior on implicitWidth { Behavior on implicitWidth {
Anim { Anim {}
}
} }
Column { Column {
@@ -104,7 +103,6 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: Colors.palette.m3onSurfaceVariant color: Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.small
text: lineRow.modelData.line text: lineRow.modelData.line
} }
} }
@@ -252,7 +250,7 @@ Item {
anchors.left: parent.left anchors.left: parent.left
anchors.margins: Tokens.padding.small anchors.margins: Tokens.padding.small
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
font.pointSize: Tokens.font.size.large fontStyle: Tokens.font.icon.large
text: clipItem.isImage ? "image" : "text_fields" text: clipItem.isImage ? "image" : "text_fields"
} }
+3 -3
View File
@@ -11,8 +11,8 @@ Item {
property int contentHeight property int contentHeight
property real offsetScale: shouldBeActive ? 0 : 1 property real offsetScale: shouldBeActive ? 0 : 1
required property ShellScreen screen required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.clipboard && Config.clipboard.enabled readonly property bool shouldBeActive: screenState.clipboard && Config.clipboard.enabled
required property PersistentProperties visibilities required property ScreenState screenState
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
implicitHeight: content.implicitHeight + Tokens.padding.small * 2 implicitHeight: content.implicitHeight + Tokens.padding.small * 2
@@ -35,7 +35,7 @@ Item {
sourceComponent: Content { sourceComponent: Content {
screen: root.screen screen: root.screen
visibilities: root.visibilities screenState: root.screenState
} }
} }
} }
+150 -39
View File
@@ -5,78 +5,189 @@ import Quickshell.Widgets
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import ZShell.Config import ZShell.Config
import ZShell
import qs.Components import qs.Components
Item { Item {
id: root id: root
required property PersistentProperties dashState
readonly property real nonAnimHeight: view.implicitHeight + viewWrapper.anchors.margins * 2
readonly property real nonAnimWidth: view.implicitWidth + viewWrapper.anchors.margins * 2 readonly property real nonAnimWidth: view.implicitWidth + viewWrapper.anchors.margins * 2
required property PersistentProperties visibilities readonly property real nonAnimHeight: tabs.implicitHeight + tabs.anchors.topMargin + view.implicitHeight + viewWrapper.anchors.margins + viewWrapper.anchors.topMargin
required property ScreenState screenState
readonly property var dashboardTabs: {
const allTabs = [
{
component: dashComponent,
iconName: "dashboard",
text: qsTr("Dashboard"),
enabled: Config.dashboard.showDashboard
},
{
component: weatherComponent,
iconName: "cloud",
text: qsTr("Weather"),
enabled: Config.dashboard.showWeather
},
{
component: mediaComponent,
iconName: "queue_music",
text: qsTr("Media"),
enabled: Config.dashboard.showMedia
},
{
component: procsComponent,
iconName: "speed",
text: qsTr("Processes"),
enabled: Config.dashboard.showProcesses
},
];
return allTabs.filter(tab => tab.enabled);
}
implicitHeight: nonAnimHeight
implicitWidth: nonAnimWidth implicitWidth: nonAnimWidth
implicitHeight: nonAnimHeight
Behavior on implicitHeight { Behavior on implicitHeight {
Anim { Anim {
type: Anim.SlowEffects
} }
} }
Behavior on implicitWidth { Behavior on implicitWidth {
Anim { Anim {
type: Anim.SlowEffects
} }
} }
ClippingRectangle { Tabs {
id: tabs
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
nonAnimWidth: view.implicitWidth
screenState: root.screenState
tabs: root.dashboardTabs
anchors.topMargin: ZUtils.clamp(anchors.margins - Config.bar.border, 0, anchors.margins)
anchors.margins: Tokens.padding.small
}
Item {
id: viewWrapper id: viewWrapper
anchors.bottom: parent.bottom property int radius: Tokens.rounding.extraLarge - anchors.margins
anchors.left: parent.left
anchors.margins: Tokens.padding.small
anchors.right: parent.right
anchors.top: parent.top
color: "transparent"
radius: Tokens.rounding.extraLarge - anchors.margins
Item { anchors.top: tabs.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.topMargin: Tokens.spacing.small
anchors.margins: Tokens.padding.small
clip: true
Flickable {
id: view id: view
readonly property int currentIndex: root.dashState?.currentTab readonly property int currentIndex: root.screenState.dashboardTab
readonly property Item currentItem: row.children[currentIndex] readonly property Item currentItem: {
repeater.count;
return repeater.itemAt(currentIndex);
}
anchors.fill: parent anchors.fill: parent
implicitHeight: currentItem.implicitHeight
implicitWidth: currentItem.implicitWidth implicitWidth: currentItem?.implicitWidth ?? 0
implicitHeight: currentItem?.implicitHeight ?? 0
contentX: currentItem?.x ?? 0
contentWidth: row.implicitWidth
contentHeight: row.implicitHeight
interactive: false
onContentXChanged: {
if (!moving || !currentItem)
return;
const x = contentX - currentItem.x;
if (x > currentItem.implicitWidth / 2)
root.screenState.dashboardTab = Math.min(root.screenState.dashboardTab + 1, tabs.count - 1);
else if (x < -currentItem.implicitWidth / 2)
root.screenState.dashboardTab = Math.max(root.screenState.dashboardTab - 1, 0);
}
RowLayout { RowLayout {
id: row id: row
Pane { Repeater {
index: 0 id: repeater
sourceComponent: Dash { model: ScriptModel {
dashState: root.dashState values: root.dashboardTabs
visibilities: root.visibilities
} }
delegate: Loader {
id: paneLoader
required property int index
required property var modelData
Layout.alignment: Qt.AlignTop
sourceComponent: modelData.component
Component.onCompleted: active = Qt.binding(() => {
if (index === view.currentIndex)
return true;
const vx = Math.floor(view.visibleArea.xPosition * view.contentWidth);
const vex = Math.floor(vx + view.visibleArea.widthRatio * view.contentWidth);
return (vx >= x && vx <= x + implicitWidth) || (vex >= x && vex <= x + implicitWidth);
})
}
}
}
Component {
id: dashComponent
Dash {
screenState: root.screenState
radius: viewWrapper.radius
}
}
Component {
id: weatherComponent
WeatherTab {
radius: viewWrapper.radius
}
}
Component {
id: mediaComponent
Media {
screenState: root.screenState
radius: viewWrapper.radius
}
}
Component {
id: procsComponent
ProcessesTab {
radius: viewWrapper.radius
}
}
Behavior on contentX {
Anim {
type: Anim.DefaultEffects
} }
} }
} }
} }
component Pane: Loader {
id: pane
required property int index
Layout.alignment: Qt.AlignTop
Component.onCompleted: active = Qt.binding(() => {
// Always keep current tab loaded
if (pane.index === view.currentIndex)
return true;
const vx = Math.floor(view.visibleArea.xPosition * view.contentWidth);
const vex = Math.floor(vx + view.visibleArea.widthRatio * view.contentWidth);
return (vx >= x && vx <= x + implicitWidth) || (vex >= x && vex <= x + implicitWidth);
})
}
} }
+7 -23
View File
@@ -11,10 +11,9 @@ import qs.Services
GridLayout { GridLayout {
id: root id: root
required property PersistentProperties dashState readonly property bool dashboardVisible: screenState.dashboard
readonly property bool dashboardVisible: visibilities.dashboard required property int radius
property int radius: Tokens.rounding.medium required property ScreenState screenState
required property PersistentProperties visibilities
columnSpacing: Tokens.spacing.small columnSpacing: Tokens.spacing.small
rowSpacing: Tokens.spacing.small rowSpacing: Tokens.spacing.small
@@ -61,19 +60,18 @@ GridLayout {
User { User {
id: user id: user
dashState: root.dashState screenState: root.screenState
} }
} }
Rect { Rect {
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillHeight: true Layout.fillHeight: true
Layout.preferredWidth: Config.dashboard.sizes.weatherWidth Layout.preferredWidth: Tokens.sizes.dashboard.weatherWidth
Layout.row: 0 Layout.row: 0
radius: root.radius radius: root.radius
Weather { Weather {}
}
} }
Rect { Rect {
@@ -87,7 +85,7 @@ GridLayout {
Calendar { Calendar {
id: calendar id: calendar
dashState: root.dashState screenState: root.screenState
} }
} }
@@ -104,20 +102,6 @@ GridLayout {
} }
} }
CustomClippingRect {
Layout.column: 0
Layout.columnSpan: 5
Layout.fillWidth: true
Layout.preferredHeight: media.implicitHeight
Layout.row: 2
color: Colors.tPalette.m3surfaceContainer
radius: root.radius
Media {
id: media
}
}
component Rect: CustomRect { component Rect: CustomRect {
color: Colors.tPalette.m3surfaceContainer color: Colors.tPalette.m3surfaceContainer
} }
+9 -9
View File
@@ -12,12 +12,12 @@ CustomMouseArea {
id: root id: root
property int activeGrid: 1 property int activeGrid: 1
required property PersistentProperties dashState required property ScreenState screenState
property bool initialized: false property bool initialized: false
property int month1 property int month1
property int month2 property int month2
readonly property int realCurrMonth: dashState.currentDate.getMonth() readonly property int realCurrMonth: screenState.dashboardDate.getMonth()
readonly property int realCurrYear: dashState.currentDate.getFullYear() readonly property int realCurrYear: screenState.dashboardDate.getFullYear()
property int year1 property int year1
property int year2 property int year2
@@ -43,9 +43,9 @@ CustomMouseArea {
function onWheel(event: WheelEvent): void { function onWheel(event: WheelEvent): void {
if (event.angleDelta.y > 0) if (event.angleDelta.y > 0)
root.dashState.currentDate = new Date(root.realCurrYear, root.realCurrMonth - 1, 1); root.screenState.dashboardDate = new Date(root.realCurrYear, root.realCurrMonth - 1, 1);
else if (event.angleDelta.y < 0) else if (event.angleDelta.y < 0)
root.dashState.currentDate = new Date(root.realCurrYear, root.realCurrMonth + 1, 1); root.screenState.dashboardDate = new Date(root.realCurrYear, root.realCurrMonth + 1, 1);
} }
acceptedButtons: Qt.MiddleButton acceptedButtons: Qt.MiddleButton
@@ -61,7 +61,7 @@ CustomMouseArea {
initialized = true; initialized = true;
} }
onClicked: root.dashState.currentDate = new Date() onClicked: root.screenState.dashboardDate = new Date()
onRealCurrMonthChanged: handleDateChange() onRealCurrMonthChanged: handleDateChange()
onRealCurrYearChanged: handleDateChange() onRealCurrYearChanged: handleDateChange()
@@ -135,7 +135,7 @@ CustomMouseArea {
padding: Tokens.padding.extraSmall padding: Tokens.padding.extraSmall
type: IconButton.Text type: IconButton.Text
onClicked: root.dashState.currentDate = new Date(root.realCurrYear, root.realCurrMonth - 1, 1) onClicked: root.screenState.dashboardDate = new Date(root.realCurrYear, root.realCurrMonth - 1, 1)
} }
Item { Item {
@@ -158,7 +158,7 @@ CustomMouseArea {
} }
} }
onClicked: root.dashState.currentDate = new Date() onClicked: root.screenState.dashboardDate = new Date()
} }
Item { Item {
@@ -206,7 +206,7 @@ CustomMouseArea {
padding: Tokens.padding.extraSmall padding: Tokens.padding.extraSmall
type: IconButton.Text type: IconButton.Text
onClicked: root.dashState.currentDate = new Date(root.realCurrYear, root.realCurrMonth + 1, 1) onClicked: root.screenState.dashboardDate = new Date(root.realCurrYear, root.realCurrMonth + 1, 1)
} }
} }
-51
View File
@@ -1,51 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import ZShell.Config
import qs.Helpers
import qs.Services
Item {
id: root
anchors.bottom: parent.bottom
anchors.top: parent.top
implicitWidth: 110
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 0
CustomText {
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: -(font.pointSize * 0.4)
color: Colors.palette.m3secondary
font.family: "Rubik"
font.pointSize: 18
font.weight: 600
text: Time.hourStr
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: Colors.palette.m3primary
font.family: "Rubik"
font.pointSize: 18 * 0.9
text: "•••"
}
CustomText {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: -(font.pointSize * 0.4)
color: Colors.palette.m3secondary
font.family: "Rubik"
font.pointSize: 18
font.weight: 600
text: Time.minuteStr
}
}
}
-329
View File
@@ -1,329 +0,0 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Services.Mpris
import QtQuick
import QtQuick.Layouts
import QtQuick.Shapes
import QtQuick.Effects
import ZShell.Components
import ZShell.Services
import ZShell.Config
import qs.Modules.Dashboard.Dash.Media
import qs.Daemons
import qs.Components
import qs.Helpers
import qs.Services
Item {
id: root
readonly property bool hasUnknownLength: (Players.active?.length ?? 0) > 2147483647
property real playerProgress: {
const active = Players.active;
return active?.length ? (active.position % active.length) / active.length : 0;
}
property int rowHeight: Tokens.padding.large + Config.dashboard.sizes.mediaProgressThickness + Tokens.spacing.small
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
function lengthStr(length: int): string {
if (length < 0)
return "-1:-1";
const hours = Math.floor(length / 3600);
const mins = Math.floor((length % 3600) / 60);
const secs = Math.floor(length % 60).toString().padStart(2, "0");
if (hours > 0)
return `${hours}:${mins.toString().padStart(2, "0")}:${secs}`;
return `${mins}:${secs}`;
}
Behavior on playerProgress {
Anim {
duration: Tokens.anim.durations.large
}
}
Timer {
running: Players.active?.isPlaying ?? false
interval: Config.dashboard.mediaUpdateInterval
triggeredOnStart: true
repeat: true
onTriggered: Players.active?.positionChanged()
}
ServiceRef {
service: Audio.cava
}
BackgroundShapes {
anchors.fill: parent
}
Shape {
id: visualizer
readonly property int bars: Config.services.visualizerBars
property color color: Qt.alpha(Colors.palette.m3primary, 0.5)
property color fillColor: Qt.alpha(color, 0.1)
anchors.fill: parent
anchors.leftMargin: -(shape.strokeWidth / 2)
layer.enabled: true
asynchronous: true
preferredRendererType: Shape.CurveRenderer
ShapePath {
id: shape
strokeColor: visualizer.color
fillColor: visualizer.fillColor
strokeWidth: 2
PathSvg {
path: shape.curvePath
}
property string curvePath: {
const values = Audio.cava.values;
const n = values.length;
if (n < 2)
return "";
const h = root.height + shape.strokeWidth / 2;
const w = root.width + shape.strokeWidth;
function x(i) {
return i * w / (n - 1);
}
function y(i) {
return h - (values[i] * Config.dashboard.sizes.mediaVisualizerSize) / 2;
}
let d = `M 0 ${h} `;
d += `L ${x(0)} ${y(0)} `;
for (let i = 0; i < n - 1; ++i) {
const x0 = x(i);
const y0 = y(i);
const x1 = x(i + 1);
const y1 = y(i + 1);
const prev = Math.max(0, i - 1);
const next = Math.min(n - 1, i + 2);
const c1x = x0 + (x1 - x(prev)) / 6;
const c1y = y0 + (y1 - y(prev)) / 6;
const c2x = x1 - (x(next) - x0) / 6;
const c2y = y1 - (y(next) - y0) / 6;
d += `C ${c1x} ${c1y}, ${c2x} ${c2y}, ${x1} ${y1} `;
}
d += `L ${w} ${h} `;
d += `L 0 ${h} Z`;
return d;
}
}
}
CustomRect {
anchors.fill: visualizer
color: Qt.alpha(Colors.palette.m3shadow, 0.2)
layer.enabled: true
layer.effect: MultiEffect {
maskEnabled: true
maskSource: visualizer
}
}
RowLayout {
id: layout
anchors.fill: parent
anchors.margins: Tokens.padding.large
anchors.leftMargin: Tokens.padding.extraLarge
CoverVisualizer {
Layout.fillHeight: true
implicitWidth: Config.dashboard.sizes.mediaCoverArtSize
}
ColumnLayout {
Layout.fillWidth: true
Layout.leftMargin: Tokens.spacing.largeIncreased
Layout.rightMargin: Tokens.spacing.largeIncreased
spacing: Tokens.spacing.small
MarqueeText {
id: title
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
color: Colors.palette.m3primary
font.pointSize: Tokens.font.size.normal
horizontalAlignment: Text.AlignHCenter
pauseMs: 4000
text: (Players.active?.trackTitle ?? qsTr("No media")) || qsTr("Unknown title")
implicitWidth: parent.width - Tokens.padding.large * 4
}
MarqueeText {
id: album
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
color: Colors.palette.m3outline
font.pointSize: Tokens.font.size.small
horizontalAlignment: Text.AlignHCenter
pauseMs: 4000
text: (Players.active?.trackAlbum ?? qsTr("No media")) || qsTr("Unknown album")
implicitWidth: parent.width - Tokens.padding.large * 4
}
MarqueeText {
id: artist
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
color: Colors.palette.m3secondary
horizontalAlignment: Text.AlignHCenter
pauseMs: 4000
text: (Players.active?.trackArtist ?? qsTr("No media")) || qsTr("Unknown artist")
implicitWidth: parent.width - Tokens.padding.large * 4
}
RowLayout {
id: positionSliderLayout
Layout.topMargin: Tokens.spacing.large
Layout.fillWidth: true
spacing: Tokens.spacing.small
TextMetrics {
id: timeMetrics
text: Players.active ? root.lengthStr(Math.max(Players.active.position, root.hasUnknownLength ? 0 : Players.active.length)).replace(/[1-9]/g, "0") : "00:00"
}
CustomText {
id: positionLabel
Layout.preferredWidth: timeMetrics.width
text: root.lengthStr(Players.active?.position ?? -1)
color: Colors.palette.m3onSurfaceVariant
font: timeMetrics.font
horizontalAlignment: Text.AlignHCenter
}
CustomSlider {
id: positionSlider
Layout.fillWidth: true
value: Players.active ? Players.active.position / (Players.active.length || 1) : 0
enabled: (Players.active?.canSeek ?? false) && !root.hasUnknownLength
wavy: true
animateWave: Players.active?.isPlaying ?? false
waveFrequency: 5
waveDuration: 2000
interactionOnMove: false
onInteraction: value => {
const active = Players.active;
if (active?.canSeek && active?.positionSupported)
active.position = value * active.length;
}
Binding {
target: positionLabel
property: "text"
value: root.lengthStr(positionSlider.pos * (Players.active?.length ?? 0))
when: positionSlider.dragging
}
}
CustomText {
Layout.preferredWidth: timeMetrics.width
text: root.hasUnknownLength ? "--:--" : root.lengthStr(Players.active?.length ?? -1)
color: Colors.palette.m3onSurfaceVariant
font: timeMetrics.font
horizontalAlignment: Text.AlignHCenter
}
}
ButtonRow {
Layout.topMargin: Tokens.spacing.largeIncreased
Layout.fillWidth: true
spacing: Tokens.spacing.extraSmall
Layout.leftMargin: positionSliderLayout.implicitWidth / 4
Layout.rightMargin: positionSliderLayout.implicitWidth / 4
IconButton {
type: IconButton.Tonal
icon: "shuffle"
isRound: true
shapeMorph: true
checked: Players.active?.shuffle ?? false
enabled: Players.active?.shuffleSupported
onClicked: Players.active.shuffle = !Players.active?.shuffle
}
IconButton {
id: previousBtn
type: IconButton.Tonal
icon: "skip_previous"
isRound: true
shapeMorph: true
enabled: Players.active?.canGoPrevious
onClicked: Players.active?.previous()
font.pointSize: Tokens.font.size.large
}
IconButton {
id: playPauseBtn
icon: Players.active?.isPlaying ? "pause" : "play_arrow"
isRound: true
shapeMorph: true
fillWidth: true
checked: Players.active?.isPlaying ?? false
enabled: Players.active?.canTogglePlaying
onClicked: Players.active?.togglePlaying()
font.pointSize: Tokens.font.size.large
}
IconButton {
id: nextBtn
type: IconButton.Tonal
icon: "skip_next"
isRound: true
shapeMorph: true
enabled: Players.active?.canGoNext
onClicked: Players.active?.next()
font.pointSize: Tokens.font.size.large
}
IconButton {
type: IconButton.Tonal
icon: Players.active?.loopState === MprisLoopState.Track ? "repeat_one" : "repeat"
isRound: true
shapeMorph: true
checked: Players.active?.loopState === MprisLoopState.Track || Players.active?.loopState === MprisLoopState.Playlist
enabled: Players.active?.loopSupported
onClicked: {
const state = Players.active.loopState;
if (state === MprisLoopState.None)
Players.active.loopState = MprisLoopState.Track;
else if (state === MprisLoopState.Track)
Players.active.loopState = MprisLoopState.Playlist;
else
Players.active.loopState = MprisLoopState.None;
}
}
}
}
}
}
+7 -10
View File
@@ -9,7 +9,7 @@ import qs.Helpers
Row { Row {
id: root id: root
required property PersistentProperties dashState required property ScreenState screenState
padding: 20 padding: 20
spacing: 12 spacing: 12
@@ -23,8 +23,7 @@ Row {
MaterialIcon { MaterialIcon {
anchors.centerIn: parent anchors.centerIn: parent
fill: 1 fill: 1
font.pointSize: Math.floor(info.implicitHeight / 2) || 1 fontStyle: Tokens.font.icon.large
grade: 200
text: "person" text: "person"
} }
@@ -52,7 +51,7 @@ Row {
id: icon id: icon
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: (Config.dashboard.sizes.infoIconSize - implicitWidth) / 2 anchors.leftMargin: (Tokens.sizes.dashboard.infoIconSize - implicitWidth) / 2
color: Colors.palette.m3primary color: Colors.palette.m3primary
implicitSize: Math.floor(13 * 1.34) implicitSize: Math.floor(13 * 1.34)
source: SystemInfo.osLogo source: SystemInfo.osLogo
@@ -65,9 +64,8 @@ Row {
anchors.leftMargin: icon.anchors.leftMargin anchors.leftMargin: icon.anchors.leftMargin
anchors.verticalCenter: icon.verticalCenter anchors.verticalCenter: icon.verticalCenter
elide: Text.ElideRight elide: Text.ElideRight
font.pointSize: 13
text: `: ${SystemInfo.osPrettyName || SystemInfo.osName}` text: `: ${SystemInfo.osPrettyName || SystemInfo.osName}`
width: Config.dashboard.sizes.infoWidth width: Tokens.sizes.dashboard.infoWidth
} }
} }
@@ -100,10 +98,10 @@ Row {
id: icon id: icon
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: (Config.dashboard.sizes.infoIconSize - implicitWidth) / 2 anchors.leftMargin: (Tokens.sizes.dashboard.infoIconSize - implicitWidth) / 2
color: line.colour color: line.colour
fill: 1 fill: 1
font.pointSize: 13 fontStyle: Tokens.font.icon.small
text: line.icon text: line.icon
} }
@@ -114,9 +112,8 @@ Row {
anchors.leftMargin: icon.anchors.leftMargin anchors.leftMargin: icon.anchors.leftMargin
anchors.verticalCenter: icon.verticalCenter anchors.verticalCenter: icon.verticalCenter
elide: Text.ElideNone elide: Text.ElideNone
font.pointSize: 13
text: `: ${line.text}` text: `: ${line.text}`
width: Config.dashboard.sizes.infoWidth width: Tokens.sizes.dashboard.infoWidth
} }
} }
} }
+2 -3
View File
@@ -19,7 +19,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
animate: true animate: true
color: Colors.palette.m3secondary color: Colors.palette.m3secondary
font.pointSize: 54 fontStyle: Tokens.font.icon.extraLarge
text: Weather.icon text: Weather.icon
} }
@@ -35,8 +35,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
animate: true animate: true
color: Colors.palette.m3primary color: Colors.palette.m3primary
font.pointSize: Tokens.font.size.extraLarge font: Tokens.font.body.large
font.weight: 500
text: Weather.temp text: Weather.temp
} }
+158
View File
@@ -0,0 +1,158 @@
import QtQuick
import QtQuick.Layouts
import M3Shapes
import ZShell.Config
import qs.Modules.Dashboard.Media
import qs.Components
import qs.Services
import qs.Helpers
Item {
id: root
property ScreenState screenState
required property int radius
implicitWidth: 1000
implicitHeight: 320
CustomClippingRect {
anchors.fill: parent
radius: root.radius
BackgroundShapes {
anchors.fill: parent
}
}
RowLayout {
anchors.fill: parent
anchors.margins: Tokens.padding.large
spacing: Tokens.spacing.extraLarge
CoverVisualizer {
Layout.fillHeight: true
implicitWidth: 250
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
state: Players.active ? "" : "noMedia"
states: State {
name: "noMedia"
PropertyChanges {
noMedia.opacity: 1
content.opacity: 0
}
}
transitions: [
Transition {
from: ""
SequentialAnimation {
Anim {
target: content
property: "opacity"
type: Anim.DefaultEffects
}
Anim {
target: noMedia
property: "opacity"
type: Anim.SlowEffects
}
}
},
Transition {
to: ""
SequentialAnimation {
Anim {
target: noMedia
property: "opacity"
type: Anim.DefaultEffects
}
Anim {
target: content
property: "opacity"
type: Anim.SlowEffects
}
}
}
]
Loader {
id: noMedia
anchors.centerIn: parent
anchors.horizontalCenterOffset: -Tokens.padding.extraLarge * 2
asynchronous: true
active: opacity > 0
opacity: 0
sourceComponent: ColumnLayout {
spacing: Tokens.spacing.small
MaterialShape {
Layout.topMargin: (pathBounds().height - implicitSize) / 2
Layout.bottomMargin: (pathBounds().height - implicitSize) / 2 + Tokens.spacing.small
Layout.alignment: Qt.AlignHCenter
color: Colors.palette.m3primaryContainer
implicitSize: icon.implicitHeight + Tokens.padding.extraLarge * 2
shape: MaterialShape.ClamShell
Behavior on color {
CAnim {}
}
MaterialIcon {
id: icon
anchors.centerIn: parent
text: "queue_music"
fontStyle: Tokens.font.icon.large
color: Colors.palette.m3onPrimaryContainer
}
}
CustomText {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Nothing playing")
font: Tokens.font.body.medium
}
CustomText {
text: qsTr("Play something for it to show up here!")
color: Colors.palette.m3onSurfaceVariant
font: Tokens.font.body.large
}
}
}
Loader {
id: content
anchors.fill: parent
asynchronous: true
active: opacity > 0
sourceComponent: RowLayout {
spacing: Tokens.spacing.extraLarge
Details {
Layout.fillWidth: true
}
LyricsAndSelector {
Layout.fillHeight: true
implicitWidth: 300
}
}
}
}
}
}
@@ -11,8 +11,8 @@ Item {
id: cover id: cover
anchors.centerIn: parent anchors.centerIn: parent
implicitHeight: Config.dashboard.sizes.mediaCoverArtSize implicitHeight: 200
implicitWidth: Config.dashboard.sizes.mediaCoverArtSize implicitWidth: 200
shape.shape: MaterialShape.Cookie9Sided shape.shape: MaterialShape.Cookie9Sided
} }
} }
+194
View File
@@ -0,0 +1,194 @@
import QtQuick
import QtQuick.Layouts
import Quickshell.Services.Mpris
import ZShell.Components
import ZShell.Config
import qs.Components
import qs.Services
import qs.Helpers
ColumnLayout {
id: root
readonly property bool hasUnknownLength: (Players.active?.length ?? 0) > 2147483647
function lengthStr(length: int): string {
if (length < 0)
return "-1:-1";
const hours = Math.floor(length / 3600);
const mins = Math.floor((length % 3600) / 60);
const secs = Math.floor(length % 60).toString().padStart(2, "0");
if (hours > 0)
return `${hours}:${mins.toString().padStart(2, "0")}:${secs}`;
return `${mins}:${secs}`;
}
spacing: Tokens.spacing.extraSmall
Timer {
running: Players.active?.isPlaying ?? false
interval: Config.dashboard.mediaUpdateInterval
triggeredOnStart: true
repeat: true
onTriggered: Players.active?.positionChanged()
}
CustomText {
Layout.fillWidth: true
text: Players.active?.trackTitle ?? ""
font: Tokens.font.title.large
elide: Text.ElideRight
animate: true
}
CustomText {
Layout.fillWidth: true
text: Players.active?.trackArtist || qsTr("Unknown artist")
color: Colors.palette.m3onSurfaceVariant
font: Tokens.font.title.medium
elide: Text.ElideRight
animate: true
}
CustomText {
Layout.fillWidth: true
text: Players.active?.trackAlbum || qsTr("Unknown album")
color: Colors.palette.m3secondary
font: Tokens.font.title.medium
elide: Text.ElideRight
animate: true
}
RowLayout {
Layout.topMargin: Tokens.spacing.extraLargeIncreased
Layout.fillWidth: true
spacing: Tokens.spacing.small
TextMetrics {
id: timeMetrics
text: Players.active ? root.lengthStr(Math.max(Players.active.position, root.hasUnknownLength ? 0 : Players.active.length)).replace(/[1-9]/g, "0") : "00:00"
font: Tokens.font.label.medium
}
CustomText {
id: positionLabel
Layout.preferredWidth: timeMetrics.width
text: root.lengthStr(Players.active?.position ?? -1)
color: Colors.palette.m3onSurfaceVariant
font: timeMetrics.font
horizontalAlignment: Text.AlignHCenter
}
CustomSlider {
id: positionSlider
Layout.fillWidth: true
value: Players.active ? Players.active.position / (Players.active.length || 1) : 0
enabled: (Players.active?.canSeek ?? false) && !root.hasUnknownLength
wavy: true
animateWave: Players.active?.isPlaying ?? false
waveFrequency: 5
waveDuration: 2000
interactionOnMove: false
onInteraction: value => {
const active = Players.active;
if (active?.canSeek && active?.positionSupported)
active.position = value * active.length;
}
Binding {
target: positionLabel
property: "text"
value: root.lengthStr(positionSlider.pos * (Players.active?.length ?? 0))
when: positionSlider.dragging
}
}
CustomText {
Layout.preferredWidth: timeMetrics.width
text: root.hasUnknownLength ? "--:--" : root.lengthStr(Players.active?.length ?? -1)
color: Colors.palette.m3onSurfaceVariant
font: timeMetrics.font
horizontalAlignment: Text.AlignHCenter
}
}
ButtonRow {
Layout.topMargin: Tokens.spacing.largeIncreased
Layout.fillWidth: true
spacing: Tokens.spacing.extraSmall
IconButton {
type: IconButton.Tonal
icon: "shuffle"
isRound: true
shapeMorph: true
checked: Players.active?.shuffle ?? false
font: Tokens.font.icon.builders.medium.weight(Font.Medium).build()
enabled: Players.active?.shuffleSupported
onClicked: Players.active.shuffle = !Players.active?.shuffle
implicitWidth: Math.round(implicitHeight * 0.9)
}
IconButton {
id: previousBtn
type: IconButton.Tonal
icon: "skip_previous"
isRound: true
shapeMorph: true
font: Tokens.font.icon.large
enabled: Players.active?.canGoPrevious
onClicked: Players.active?.previous()
}
IconButton {
id: playPauseBtn
icon: Players.active?.isPlaying ? "pause" : "play_arrow"
isRound: true
shapeMorph: true
fillWidth: true
checked: Players.active?.isPlaying ?? false
font: Tokens.font.icon.large
enabled: Players.active?.canTogglePlaying
onClicked: Players.active?.togglePlaying()
}
IconButton {
id: nextBtn
type: IconButton.Tonal
icon: "skip_next"
isRound: true
shapeMorph: true
font: Tokens.font.icon.large
enabled: Players.active?.canGoNext
onClicked: Players.active?.next()
}
IconButton {
type: IconButton.Tonal
icon: Players.active?.loopState === MprisLoopState.Track ? "repeat_one" : "repeat"
isRound: true
shapeMorph: true
checked: Players.active?.loopState === MprisLoopState.Track || Players.active?.loopState === MprisLoopState.Playlist
font: Tokens.font.icon.builders.medium.weight(Font.Medium).build()
enabled: Players.active?.loopSupported
onClicked: {
const state = Players.active.loopState;
if (state === MprisLoopState.None)
Players.active.loopState = MprisLoopState.Track;
else if (state === MprisLoopState.Track)
Players.active.loopState = MprisLoopState.Playlist;
else
Players.active.loopState = MprisLoopState.None;
}
implicitWidth: Math.round(implicitHeight * 0.9)
}
}
}
+317
View File
@@ -0,0 +1,317 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import ZShell.Config
import ZShell.Services
import qs.Components
import qs.Effects
import qs.Services
import qs.Helpers
Item {
id: root
// Funny binding hack to make lyrics update
readonly property var _: {
const p = Players.active;
if (p)
Lyrics.setTrack(p.trackArtist, p.trackTitle, p.trackAlbum, p.length);
else
Lyrics.clearTrack();
}
readonly property real fadeAmount: 0.1
property list<string> lyricList: Lyrics.lyrics
layer.enabled: true
layer.effect: Mask {
maskSource: mask
Rectangle {
id: mask
layer.enabled: true
visible: false
implicitWidth: root.width
implicitHeight: root.height
gradient: Gradient {
orientation: Gradient.Vertical
GradientStop {
color: Qt.alpha("black", 0)
position: 0
}
GradientStop {
color: Qt.alpha("black", 1)
position: root.fadeAmount
}
GradientStop {
color: Qt.alpha("black", 1)
position: 1 - root.fadeAmount
}
GradientStop {
color: Qt.alpha("black", 0)
position: 1
}
}
}
}
state: {
if (Lyrics.hasLyrics)
return "hasLyrics";
if (Lyrics.loading)
return "loading";
return "noLyrics";
}
states: [
State {
name: "loading"
PropertyChanges {
loadingIndicator.opacity: 1
lyrics.opacity: 0
noLyrics.opacity: 0
}
},
State {
name: "hasLyrics"
PropertyChanges {
loadingIndicator.opacity: 0
lyrics.opacity: 1
noLyrics.opacity: 0
}
},
State {
name: "noLyrics"
PropertyChanges {
loadingIndicator.opacity: 0
lyrics.opacity: 0
noLyrics.opacity: 1
}
}
]
transitions: [
Transition {
from: "loading"
SequentialAnimation {
Anim {
target: loadingIndicator
property: "opacity"
type: Anim.DefaultEffects
}
Anim {
targets: [lyrics, noLyrics]
property: "opacity"
type: Anim.SlowEffects
}
}
},
Transition {
from: "hasLyrics"
SequentialAnimation {
Anim {
target: lyrics
property: "opacity"
type: Anim.DefaultEffects
}
Anim {
targets: [loadingIndicator, noLyrics]
property: "opacity"
type: Anim.SlowEffects
}
}
},
Transition {
from: "noLyrics"
SequentialAnimation {
Anim {
target: noLyrics
property: "opacity"
type: Anim.DefaultEffects
}
Anim {
targets: [loadingIndicator, lyrics]
property: "opacity"
type: Anim.SlowEffects
}
}
}
]
Loader {
id: loadingIndicator
anchors.centerIn: parent
asynchronous: true
active: opacity > 0
opacity: 0
sourceComponent: ColumnLayout {
spacing: Tokens.spacing.large
CustomRect {
Layout.alignment: Qt.AlignHCenter
implicitWidth: shape.implicitSize + Tokens.padding.medium * 2
implicitHeight: shape.implicitSize + Tokens.padding.medium * 2
color: Colors.palette.m3primaryContainer
radius: Tokens.rounding.full
LoadingIndicator {
id: shape
anchors.centerIn: parent
implicitSize: Math.round(300 / 5)
containsIcon: true // This removes the pentagon, which is not centered
}
}
CustomText {
text: qsTr("Loading lyrics...")
color: Colors.palette.m3onSurfaceVariant
font: Tokens.font.body.medium
}
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
Loader {
id: noLyrics
anchors.centerIn: parent
asynchronous: true
active: opacity > 0
opacity: 0
sourceComponent: ColumnLayout {
spacing: Tokens.spacing.small
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
text: "sentiment_sad"
fontStyle: Tokens.font.icon.extraLarge
color: Colors.palette.m3outline
}
CustomText {
text: qsTr("No lyrics found")
color: Colors.palette.m3outline
font: Tokens.font.body.medium
}
}
}
CustomListView {
id: lyrics
anchors.fill: parent
anchors.topMargin: parent.height * root.fadeAmount / 2
anchors.bottomMargin: parent.height * root.fadeAmount / 2
displayMarginBeginning: anchors.topMargin
displayMarginEnd: anchors.bottomMargin
model: root.lyricList
Component.onCompleted: {
currentIndex = Qt.binding(() => {
model; // Force update when lyrics change
return Lyrics.indexForTime(Players.active?.position ?? 0);
});
positionViewAtIndex(currentIndex, ListView.Center);
}
onModelChanged: Qt.callLater(() => positionViewAtIndex(currentIndex, ListView.Center))
highlightRangeMode: ListView.ApplyRange
highlightMoveDuration: Tokens.anim.durations.large
highlightMoveVelocity: -1
preferredHighlightBegin: (height - (currentItem?.implicitHeight ?? 0)) / 2
preferredHighlightEnd: (height + (currentItem?.implicitHeight ?? 0)) / 2
spacing: Tokens.spacing.small
opacity: 0
enabled: opacity > 0
delegate: CustomText {
id: lyric
required property string modelData
required property int index
property real effectScale: ListView.isCurrentItem ? 1 : 0
anchors.left: lyrics.contentItem.left
anchors.right: lyrics.contentItem.right
text: modelData || ". . ."
color: ListView.isCurrentItem ? Colors.palette.m3primary : mouse.containsMouse ? Colors.palette.m3onSurface : Colors.palette.m3outline
font: Tokens.font.body.medium
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
layer.enabled: effectScale > 0
layer.effect: MultiEffect {
shadowEnabled: true
shadowColor: Colors.palette.m3primary
shadowOpacity: 0.5 * lyric.effectScale
shadowBlur: 0.6 * lyric.effectScale
blur: 0.4 * lyric.effectScale
}
Behavior on effectScale {
Anim {
type: Anim.SlowEffects
}
}
MouseArea {
id: mouse
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: {
const p = Players.active;
if (p)
p.position = Lyrics.timeForIndex(lyric.index);
}
}
}
Behavior on opacity {
Anim {
type: Anim.SlowEffects
}
}
}
Behavior on lyricList {
SequentialAnimation {
Anim {
target: lyrics
property: "opacity"
to: 0
type: Anim.DefaultEffects
}
PropertyAction {}
Anim {
target: lyrics
property: "opacity"
to: 1
type: Anim.SlowEffects
}
}
}
}
@@ -0,0 +1,79 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.Mpris
import ZShell.Config
import qs.Components
import qs.Helpers
import qs.Services
Item {
ColumnLayout {
id: layout
anchors.fill: parent
anchors.leftMargin: Tokens.padding.medium
spacing: Tokens.spacing.medium
RowLayout {
Layout.bottomMargin: -Tokens.spacing.medium
spacing: Tokens.spacing.medium
z: 1
MaterialIcon {
Layout.topMargin: Math.round(fontInfo.pointSize * 0.12)
text: "lyrics"
fontStyle: Tokens.font.icon.large
}
CustomText {
Layout.fillWidth: true
text: qsTr("Lyrics")
font: Tokens.font.body.large
}
LyricsInfo {}
}
LyricList {
Layout.fillWidth: true
Layout.fillHeight: true
}
SplitButton {
Layout.alignment: Qt.AlignHCenter
type: SplitButton.Tonal
enabled: Players.list.length
active: menuItems.find(m => m.modelData === Players.active) ?? menuItems[0] ?? null
menu.onItemSelected: item => Players.manualActive = (item as PlayerItem).modelData
menuItems: playerList.instances
fallbackIcon: "music_off"
fallbackText: qsTr("No players", "no media players active")
minLeftWidth: layout.width - expandBtn.implicitWidth - spacing
label.Layout.maximumWidth: minLeftWidth - iconLabel.implicitWidth - textRow.spacing - textRow.anchors.horizontalCenterOffset / 2 - horizontalPadding * 2
label.elide: Text.ElideRight
stateLayer.enabled: true
menuOnTop: true
Variants {
id: playerList
model: Players.list
PlayerItem {}
}
}
}
component PlayerItem: MenuItem {
required property MprisPlayer modelData
icon: modelData === Players.active ? "check" : ""
text: Players.getIdentity(modelData)
activeIcon: "animated_images"
}
}
+212
View File
@@ -0,0 +1,212 @@
import QtQuick
import QtQuick.Layouts
import ZShell
import ZShell.Blobs
import ZShell.Config
import ZShell.Services
import qs.Components
import qs.Services
Item {
id: root
property bool open
readonly property real padding: Tokens.padding.large
implicitWidth: btn.implicitWidth * 0.9
implicitHeight: btn.implicitHeight * 0.9
BlobGroup {
id: blobGroup
color: Colors.palette.m3surfaceContainerHighest
smoothing: Tokens.rounding.medium
cornerFill: false
Behavior on color {
CAnim {}
}
}
BlobRect {
id: btnRect
anchors.fill: parent
anchors.margins: !btn.pressed && btn.containsMouse ? -Tokens.padding.extraSmall : 0
group: blobGroup
radius: Tokens.rounding.medium
Behavior on anchors.margins {
Anim {}
}
}
BlobRect {
id: rect
anchors.right: parent.right
anchors.top: parent.top
implicitWidth: parent.width
implicitHeight: parent.height
group: blobGroup
radius: Tokens.rounding.medium
deformScale: 0.00001
states: State {
name: "open"
when: root.open
PropertyChanges {
rect.anchors.rightMargin: root.width - Tokens.spacing.small
rect.anchors.topMargin: -Tokens.padding.medium
rect.implicitWidth: Math.max(layout.implicitWidth, placeholder.implicitWidth) + root.padding * 2
rect.implicitHeight: Math.max(layout.implicitHeight, placeholder.implicitHeight) + root.padding * 2
content.opacity: 1
}
}
transitions: Transition {
Anim {
properties: "rightMargin,implicitWidth"
}
Anim {
properties: "topMargin,implicitHeight"
easing: Tokens.anim.expressiveFastSpatial
}
Anim {
property: "opacity"
type: Anim.DefaultEffects
}
}
Behavior on implicitWidth {
Anim {}
}
Behavior on implicitHeight {
Anim {}
}
Item {
id: content
anchors.fill: parent
clip: true
opacity: 0
state: Lyrics.loading || !Lyrics.hasLyrics ? "" : "hasLyrics"
states: State {
name: "hasLyrics"
PropertyChanges {
layout.opacity: 1
placeholder.opacity: 0
}
}
transitions: [
Transition {
from: "hasLyrics"
SequentialAnimation {
Anim {
target: layout
property: "opacity"
type: Anim.FastEffects
}
Anim {
target: placeholder
property: "opacity"
type: Anim.DefaultEffects
}
}
},
Transition {
to: "hasLyrics"
SequentialAnimation {
Anim {
target: placeholder
property: "opacity"
type: Anim.FastEffects
}
Anim {
target: layout
property: "opacity"
type: Anim.DefaultEffects
}
}
}
]
ColumnLayout {
id: layout
anchors.centerIn: parent
spacing: Tokens.spacing.extraSmall
opacity: 0
CustomText {
// TRANSLATORS: %1 = lyrics backend name, e.g. LRCLIB
text: qsTr("Backend: %1").arg(ZUtils.enumToString(Lyrics, "backend"))
color: Colors.palette.m3onSurfaceVariant
animate: true
}
CustomText {
Layout.maximumWidth: 1000 / 2
// TRANSLATORS: %1/%2/%3 = matched track title, artist and album
text: qsTr("Selected candidate: %1 | %2 | %3").arg(Lyrics.selectedCandidate.title).arg(Lyrics.selectedCandidate.artist).arg(Lyrics.selectedCandidate.album)
color: Colors.palette.m3onSurfaceVariant
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
animate: true
}
CustomText {
// TRANSLATORS: %1 = lyric timing offset; ms is the millisecond unit, leave untranslated
text: qsTr("Offset: %1 ms").arg(Lyrics.offset)
color: Colors.palette.m3onSurfaceVariant
animate: true
}
}
Item {
id: placeholder
anchors.centerIn: parent
implicitWidth: placeholderText.implicitWidth
implicitHeight: placeholderText.implicitHeight
CustomText {
id: placeholderText
text: Lyrics.loading ? qsTr("Loading...") : qsTr("No lyrics found")
color: Colors.palette.m3onSurfaceVariant
font: Tokens.font.body.medium
animate: true
}
}
}
}
MouseArea {
id: btn
anchors.centerIn: parent
implicitWidth: implicitHeight
implicitHeight: icon.implicitHeight + Tokens.padding.extraSmall * 2
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: root.open = !root.open
MaterialIcon {
id: icon
anchors.centerIn: parent
text: "more_vert"
fontStyle: Tokens.font.icon.medium
}
}
}
@@ -0,0 +1,220 @@
pragma Singleton
import QtQuick
import Quickshell
import ZShell.Config
import ZShell.Services
Singleton {
id: root
readonly property var columnDefs: [
{
id: "command",
title: qsTr("Command"),
width: 200,
field: 3
},
{
id: "pid",
title: qsTr("PID"),
width: 60,
field: 1
},
{
id: "cpuUsage",
title: qsTr("CPU %"),
width: 70,
field: 7
},
{
id: "memoryUsage",
title: qsTr("Memory"),
width: 90,
field: 8
},
{
id: "elapsed",
title: qsTr("Elapsed"),
width: 80,
field: 9
},
{
id: "ppid",
title: qsTr("PPID"),
width: 60,
field: 2
},
{
id: "user",
title: qsTr("User"),
width: 100,
field: 11
},
{
id: "uid",
title: qsTr("UID"),
width: 60,
field: 10
},
{
id: "state",
title: qsTr("State"),
width: 60,
field: 5
},
{
id: "sleeping",
title: qsTr("Sleeping"),
width: 80,
field: 6
},
{
id: "commandLine",
title: qsTr("Command line"),
width: 240,
field: 4
},
{
id: "ioReadBytes",
title: qsTr("IO read"),
width: 90,
field: 12
},
{
id: "ioWriteBytes",
title: qsTr("IO write"),
width: 90,
field: 13
}
]
readonly property var desktopEntries: DesktopEntries.applications.values.map(e => ({
icon: e.icon,
command: e.command
}))
Component.onCompleted: {
Processes.setDesktopEntries(root.desktopEntries);
Qt.callLater(ensurePinnedFirst);
}
onDesktopEntriesChanged: Processes.setDesktopEntries(root.desktopEntries)
readonly property string pinnedColumnId: "command"
readonly property var hiddenColumns: {
const visible = Config.dashboard.processColumns.values.map(e => e.id);
return root.columnDefs.filter(d => !visible.includes(d.id));
}
function pinnedIndex(): int {
const values = Config.dashboard.processColumns.values;
for (let i = 0; i < values.length; i++)
if (values[i].id === root.pinnedColumnId)
return i;
return -1;
}
function ensurePinnedFirst() {
const i = root.pinnedIndex();
if (i > 0)
Qt.callLater(() => {
const idx = root.pinnedIndex();
if (idx > 0)
Config.dashboard.processColumns.move(idx, 0);
});
}
function defFor(id: string): var {
for (const def of root.columnDefs)
if (def.id === id)
return def;
return null;
}
function addColumn(id: string) {
const def = root.defFor(id);
Config.dashboard.processColumns.insert({
id: id,
width: def ? def.width : 100
});
}
function removeColumn(index: int) {
const entry = Config.dashboard.processColumns.at(index);
if (entry && entry.id === root.pinnedColumnId)
return;
Config.dashboard.processColumns.remove(index);
}
function moveColumns(from: int, to: int) {
const list = Config.dashboard.processColumns;
const entry = list.at(from);
if (!entry)
return;
if (entry.id === root.pinnedColumnId)
return;
if (to === 0 && list.at(0)?.id === root.pinnedColumnId)
return;
list.move(from, to);
const i = root.pinnedIndex();
if (i > 0)
list.move(i, 0);
}
function resizeColumn(index: int, width: real) {
const entry = Config.dashboard.processColumns.at(index);
if (entry)
entry.width = Math.max(50, width);
}
function dampOvershoot(overshoot: real, maxOvershoot: real): real {
return overshoot / (1 + overshoot / maxOvershoot);
}
function formatCell(id: string, value: var): string {
switch (id) {
case "cpuUsage":
return value.toFixed(1) + "%";
case "memoryUsage":
return root.formatBytes(value * 1024);
case "ioReadBytes":
case "ioWriteBytes":
return root.formatBytes(value);
case "elapsed":
return root.formatElapsed(value);
case "sleeping":
return value ? qsTr("Yes") : qsTr("No");
default:
return String(value);
}
}
function formatBytes(bytes: var): string {
if (bytes === undefined || bytes === null || bytes <= 0)
return "0 B";
const units = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"];
let v = Number(bytes);
let i = 0;
while (v >= 1024 && i < units.length - 1) {
v /= 1024;
++i;
}
return (i === 0 ? v.toFixed(0) : v.toFixed(1)) + " " + units[i];
}
function formatElapsed(secs: var): string {
const s = Math.max(0, Math.floor(Number(secs)));
const h = Math.floor(s / 3600);
const m = Math.floor((s % 3600) / 60);
const ss = s % 60;
if (h > 0)
return h + "h " + m + "m";
if (m > 0)
return m + "m " + ss + "s";
return ss + "s";
}
}
@@ -0,0 +1,665 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import ZShell.Config
import ZShell.Internal
import ZShell.Services
import qs.Components
import qs.Services
Item {
id: root
required property int headerHeight
required property ProcessesList pList
readonly property alias columnList: columnList
readonly property alias columnsModel: columnsData
readonly property bool anyHeld: columnList.contentItem.children.some(c => c.held)
z: 1
implicitHeight: headerHeight
function widthForColumn(id: string, configured: real): real {
for (const item of columnList.contentItem.children) {
if (item.modelData !== undefined && item.modelData.id === id)
return item.liveWidth > 0 ? item.liveWidth : (configured > 0 ? configured : (ProcessesColumns.defFor(id)?.width ?? 100));
}
return configured > 0 ? configured : (ProcessesColumns.defFor(id)?.width ?? 100);
}
function fillMissingWidths() {
const n = columnsData.values.length;
if (n === 0 || columnList.width <= 0)
return;
const spacingTotal = columnList.spacing * Math.max(0, n - 1);
let savedTotal = 0;
const unsavedIndices = [];
for (let i = 0; i < n; i++) {
const w = columnsData.values[i].width;
if (w > 0)
savedTotal += w;
else
unsavedIndices.push(i);
}
if (unsavedIndices.length === 0)
return;
const available = columnList.width - spacingTotal - savedTotal;
if (available <= 0)
return;
const each = Math.max(50, available / unsavedIndices.length);
for (const idx of unsavedIndices)
ProcessesColumns.resizeColumn(idx, each);
}
Connections {
target: columnList
function onWidthChanged() {
root.fillMissingWidths();
}
function onCountChanged() {
resizeOverlay.geometryTick++;
root.fillMissingWidths();
}
function onContentWidthChanged() {
resizeOverlay.geometryTick++;
}
}
Connections {
target: columnsData
function onValuesChanged() {
root.fillMissingWidths();
resizeOverlay.geometryTick++;
}
}
Component.onCompleted: Qt.callLater(fillMissingWidths)
Component.onDestruction: Cursor.clearOverride()
CustomListView {
id: columnList
anchors.fill: parent
orientation: ListView.Horizontal
interactive: false
spacing: Tokens.spacing.extraSmall / 2
add: Transition {
Anim {
from: 0
properties: "opacity"
to: 1
type: Anim.DefaultEffects
}
}
displaced: Transition {
Anim {
properties: "opacity"
to: 1
type: Anim.DefaultEffects
}
Anim {
properties: "x"
}
}
move: Transition {
Anim {
properties: "opacity"
to: 1
type: Anim.DefaultEffects
}
Anim {
properties: "x"
}
}
remove: Transition {
Anim {
to: 0
properties: "opacity"
type: Anim.DefaultEffects
}
}
model: DelegateModel {
id: columnsVisual
delegate: ColumnHeaderCell {
parentModel: columnsVisual
}
model: ScriptModel {
id: columnsData
values: Config.dashboard.processColumns.values
}
}
}
Item {
id: resizeOverlay
property real gapBoost: pressed ? Tokens.spacing.small : Tokens.spacing.extraSmall
property bool pressed: false
property int geometryTick: 0
property int activeGapIndex: -1
anchors.fill: columnList
z: 10
Component.onCompleted: Qt.callLater(() => geometryTick++)
Repeater {
model: Math.max(0, columnsData.values.length - 1)
MouseArea {
id: handleRoot
required property int index
readonly property bool hovered: containsMouse
property ColumnHeaderCell leftCell: {
const left = leftCell;
if (resizing && left)
return left;
const tick = resizeOverlay.geometryTick;
return resolveCell(true);
}
property ColumnHeaderCell rightCell: {
const right = rightCell;
if (resizing && right)
return right;
const tick = resizeOverlay.geometryTick;
return resolveCell(false);
}
property bool resizing: false
property real originX: 0
property real originWidth: 0
property real originRightWidth: 0
property real originAbsX: 0
function resolveCell(left: bool): ColumnHeaderCell {
if (left) { // qmllint disable incompatible-type
const leftItem = columnList.itemAtIndex(index);
if (leftItem)
return leftItem;
else
return null;
} else {
const rightItem = columnList.itemAtIndex(index + 1);
if (rightItem)
return rightItem;
else
return null;
}
}
cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor
preventStealing: true
hoverEnabled: true
enabled: !root.anyHeld
x: {
if (!leftCell)
return 0;
const gapCenter = leftCell.x + leftCell.width + columnList.spacing / 2;
return gapCenter - width / 2;
}
y: 0
implicitWidth: 16
implicitHeight: columnList.height
function pairAvailableWidth(rightCell) {
let othersWidth = 0;
for (const item of columnList.contentItem.children) {
if (item.modelData === undefined || item === handleRoot.leftCell || item === rightCell)
continue;
othersWidth += item.width;
}
const spacingTotal = columnList.spacing * Math.max(0, columnsData.values.length - 1);
return columnList.width - othersWidth - spacingTotal;
}
CustomRect {
id: handle
readonly property bool shouldBeVisible: handleRoot.hovered || handleRoot.pressed
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
implicitWidth: shouldBeVisible ? resizeOverlay.gapBoost : 0
color: Colors.palette.m3secondary
radius: Tokens.rounding.full
opacity: shouldBeVisible ? 1.0 : 0
Behavior on implicitWidth {
Anim {
type: Anim.FastSpatial
}
}
Behavior on opacity {
Anim {}
}
transform: Scale {
origin {
x: handle.implicitWidth / 2
y: handle.height / 2
}
yScale: handle.shouldBeVisible ? 1 : 0.5
xScale: handle.shouldBeVisible ? 1 : 0.5
Behavior on xScale {
Anim {}
}
Behavior on yScale {
Anim {}
}
}
}
onHoveredChanged: resizeOverlay.activeGapIndex = hovered || pressed ? handleRoot.index : (resizeOverlay.activeGapIndex === handleRoot.index ? -1 : resizeOverlay.activeGapIndex)
onPressedChanged: {
resizeOverlay.pressed = pressed;
if (!pressed)
resizeOverlay.activeGapIndex = hovered ? handleRoot.index : resizeOverlay.activeGapIndex === handleRoot.index ? -1 : resizeOverlay.activeGapIndex;
}
onPressed: e => {
if (!handleRoot.leftCell)
return;
Cursor.setOverride(Qt.ClosedHandCursor);
resizing = true;
originAbsX = handleRoot.mapToItem(resizeOverlay, e.x, 0).x;
originWidth = handleRoot.leftCell.implicitWidth;
originRightWidth = handleRoot.rightCell ? handleRoot.rightCell.implicitWidth : 0;
}
onPositionChanged: e => {
if (!resizing || !handleRoot.leftCell)
return;
const nowAbsX = handleRoot.mapToItem(resizeOverlay, e.x, 0).x;
let newLeftWidth = Math.max(50, originWidth + (nowAbsX - originAbsX));
const rightCell = handleRoot.rightCell;
let roundedLeft = Math.round(newLeftWidth);
if (rightCell) {
const available = Math.round(handleRoot.pairAvailableWidth(rightCell));
const maxLeftWidth = Math.max(50, available - 50);
roundedLeft = Math.min(roundedLeft, maxLeftWidth);
rightCell.liveWidth = Math.max(50, available - roundedLeft);
}
handleRoot.leftCell.liveWidth = roundedLeft;
}
onReleased: {
Cursor.clearOverride();
resizing = false;
const cell = handleRoot.leftCell;
const rightCell = handleRoot.rightCell;
if (cell.liveWidth !== 0)
ProcessesColumns.resizeColumn(cell.DelegateModel.itemsIndex, cell.liveWidth);
if (rightCell.liveWidth !== 0)
ProcessesColumns.resizeColumn(rightCell.DelegateModel.itemsIndex, rightCell.liveWidth);
if (cell || rightCell) {
cell.liveWidth = 0;
rightCell.liveWidth = 0;
}
resizeOverlay.geometryTick++;
}
}
}
}
component ColumnHeaderCell: Item {
id: cell
required property var modelData
required property int index
required property DelegateModel parentModel
property int leftRadius: pushSide !== 1 && DelegateModel.itemsIndex === 0 ? cell.implicitHeight / 2 : Tokens.rounding.extraSmall
property int rightRadius: pushSide !== -1 && DelegateModel.itemsIndex === parentModel.count - 1 ? cell.implicitHeight / 2 : Tokens.rounding.extraSmall
property real radiusLerpProg
property bool held
property bool moved: false
property int pressIndex: -1
property point pressPos
property real lastMoveX: 0
property real liveWidth: 0
readonly property bool isActiveSort: Processes.sortField === (ProcessesColumns.defFor(cell.modelData.id)?.field ?? 0)
readonly property int pushSide: {
const gapIdx = resizeOverlay.activeGapIndex;
if (gapIdx < 0)
return 0;
const myIdx = cell.DelegateModel.itemsIndex;
if (myIdx === gapIdx)
return -1;
if (myIdx === gapIdx + 1)
return 1;
return 0;
}
property real gapBoost: resizeOverlay.gapBoost
readonly property real gapShrink: gapBoost / 2
property real gapProg: pushSide !== 0 ? 1 : 0
property real gapX: pushSide === 1 ? 1 : 0
function lerpRadius(a: real, b: real): real {
return a + (b - a) * radiusLerpProg;
}
Behavior on gapBoost {
Anim {
type: Anim.FastSpatial
}
}
Behavior on gapX {
Anim {
type: Anim.FastSpatial
}
}
Behavior on gapProg {
Anim {
type: Anim.FastSpatial
}
}
implicitHeight: root.headerHeight
implicitWidth: liveWidth > 0 ? liveWidth : (modelData.width > 0 ? modelData.width : (ProcessesColumns.defFor(modelData.id)?.width ?? 100))
z: held || returnAnim.running ? 1 : 0
states: State {
name: "held"
PropertyChanges {
cellBg.color: Colors.palette.m3primaryContainer
elevation.opacity: 1
placeholder.opacity: 0.1
cell.radiusLerpProg: 1
title.color: Colors.palette.m3onPrimaryContainer
removeBtn.inactiveOnColor: Colors.palette.m3onPrimaryContainer
}
}
Behavior on leftRadius {
Anim {
type: Anim.DefaultEffects
}
}
Behavior on rightRadius {
Anim {
type: Anim.DefaultEffects
}
}
transitions: Transition {
Anim {
properties: "opacity,radiusLerpProg"
type: Anim.SlowEffects
}
PropertyAction {
properties: "color,inactiveOnColor"
}
}
state: held ? "held" : ""
CustomRect {
id: placeholder
anchors.fill: parent
color: Colors.palette.m3primary
opacity: 0
topLeftRadius: cell.leftRadius
bottomLeftRadius: cell.leftRadius
topRightRadius: cell.rightRadius
bottomRightRadius: cell.rightRadius
}
DragHandler {
id: dragHandler
property real centerOffset: 0
enabled: cell.modelData.id !== ProcessesColumns.pinnedColumnId
target: null
xAxis.enabled: true
dragThreshold: 10
grabPermissions: PointerHandler.CanTakeOverFromItems
onActiveChanged: {
if (active) {
Cursor.setOverride(Qt.ClosedHandCursor);
returnAnim.stop();
cell.pressIndex = cell.DelegateModel.itemsIndex;
cell.lastMoveX = cell.x;
cell.moved = true;
cell.held = true;
const p = centroid.position;
cell.pressPos = Qt.point(p.x, p.y);
dragHandler.centerOffset = p.x - cell.width / 2;
cellContent.x = Qt.binding(() => {
const maxOvershoot = Tokens.padding.extraLarge;
const xDiff = cell.x - cell.lastMoveX;
const x = dragHandler.centroid.position.x - cell.pressPos.x - xDiff;
const absX = cell.mapToItem(columnList.contentItem, x, 0).x;
const maxX = columnList.contentWidth - cell.width;
if (absX < 0)
return x - absX - ProcessesColumns.dampOvershoot(-absX, maxOvershoot);
if (absX > maxX)
return x - absX + maxX + ProcessesColumns.dampOvershoot(absX - maxX, maxOvershoot);
return x;
});
cellContent.y = Qt.binding(() => {
const maxOvershoot = Tokens.padding.extraLargeIncreased;
const y = dragHandler.centroid.position.y - cell.pressPos.y;
return ProcessesColumns.dampOvershoot(Math.abs(y), maxOvershoot) * Math.sign(y);
});
} else {
Cursor.clearOverride();
if (cell.held) {
cellContent.x = cellContent.x;
cellContent.y = cellContent.y;
cell.held = false;
returnAnim.start();
if (cell.DelegateModel.itemsIndex !== cell.pressIndex) {
ProcessesColumns.moveColumns(cell.pressIndex, cell.DelegateModel.itemsIndex);
}
}
}
}
onCentroidChanged: {
if (!active)
return;
const p = centroid.position;
cell.lastMoveX = cell.x;
const cursorAbsX = cell.mapToItem(columnList.contentItem, p.x, 0).x;
const centerAbsX = cursorAbsX - dragHandler.centerOffset;
const movingRight = p.x > cell.pressPos.x;
const grabbedRight = dragHandler.centerOffset > 0;
const useCursor = movingRight === grabbedRight;
const refX = useCursor ? cursorAbsX : centerAbsX;
const myIndex = cell.DelegateModel.itemsIndex;
const rightItem = columnList.itemAtIndex(myIndex + 1);
if (rightItem) {
const rightMid = rightItem.x + rightItem.width / 2;
if (refX > rightMid) {
columnsVisual.items.move(myIndex, myIndex + 1);
return;
}
}
const leftItem = columnList.itemAtIndex(myIndex - 1);
if (leftItem) {
const leftMid = leftItem.x + leftItem.width / 2;
const pinnedAtZero = columnList.itemAtIndex(0)?.modelData?.id === ProcessesColumns.pinnedColumnId;
if (refX < leftMid && !(myIndex === 1 && pinnedAtZero)) {
columnsVisual.items.move(myIndex, myIndex - 1);
}
}
}
}
Anim {
id: returnAnim
properties: "x,y"
target: cellContent
to: 0
}
Item {
id: cellVisual
x: cell.gapShrink * cell.gapX
y: 0
width: cell.width - cell.gapShrink * cell.gapProg
height: cell.height
Item {
id: cellContent
width: cellVisual.width
height: cellVisual.height
readonly property int topLeftRadius: cell.lerpRadius(cell.leftRadius, cell.implicitHeight / 2)
readonly property int topRightRadius: cell.lerpRadius(cell.rightRadius, cell.implicitHeight / 2)
readonly property int bottomLeftRadius: cell.lerpRadius(cell.leftRadius, cell.implicitHeight / 2)
readonly property int bottomRightRadius: cell.lerpRadius(cell.rightRadius, cell.implicitHeight / 2)
Elevation {
id: elevation
anchors.fill: parent
level: 3
opacity: 0
topLeftRadius: parent.topLeftRadius
topRightRadius: parent.topRightRadius
bottomLeftRadius: parent.bottomLeftRadius
bottomRightRadius: parent.bottomRightRadius
}
CustomRect {
id: cellBg
anchors.fill: parent
color: cell.isActiveSort ? Colors.palette.m3primary : Colors.palette.m3surfaceContainer
topLeftRadius: cell.lerpRadius(cell.leftRadius, cell.implicitHeight / 2)
topRightRadius: cell.lerpRadius(cell.rightRadius, cell.implicitHeight / 2)
bottomLeftRadius: cell.lerpRadius(cell.leftRadius, cell.implicitHeight / 2)
bottomRightRadius: cell.lerpRadius(cell.rightRadius, cell.implicitHeight / 2)
StateLayer {
id: layer
color: cell.isActiveSort ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
manualHoverOverride: cell.held
onClicked: {
Processes.sortBy(ProcessesColumns.defFor(cell.modelData.id)?.field ?? 0);
root.pList.positionViewAtBeginning();
}
}
}
Item {
anchors.fill: parent
anchors.leftMargin: Tokens.padding.medium
anchors.rightMargin: 12
CustomText {
id: title
anchors.left: parent.left
anchors.right: removeBtn.visible ? removeBtn.left : parent.right
anchors.rightMargin: Tokens.spacing.extraSmall
anchors.verticalCenter: parent.verticalCenter
color: cell.isActiveSort ? Colors.palette.m3onPrimary : Colors.palette.m3onSurfaceVariant
elide: CustomText.ElideRight
text: ProcessesColumns.defFor(cell.modelData.id)?.title ?? cell.modelData.id
}
IconButton {
id: removeBtn
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
type: IconButton.Text
icon: "close"
font: Tokens.font.icon.small
inactiveOnColor: Colors.palette.m3onPrimary
visible: cell.modelData.id !== ProcessesColumns.pinnedColumnId
onClicked: ProcessesColumns.removeColumn(cell.DelegateModel.itemsIndex)
}
MaterialIcon {
anchors.right: removeBtn.visible ? removeBtn.left : parent.right
anchors.verticalCenter: parent.verticalCenter
text: "keyboard_arrow_down"
opacity: cell.isActiveSort ? 1 : 0
visible: opacity > 0
rotation: !Processes.sortAscending && opacity > 0 ? 180 : 0
color: Colors.palette.m3onPrimary
Behavior on rotation {
Anim {}
}
Behavior on opacity {
Anim {}
}
}
}
}
}
}
component HiddenColumnItem: MenuItem {
required property var modelData
text: modelData.title
}
}
@@ -0,0 +1,140 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import ZShell.Config
import ZShell.Services
import qs.Components
import qs.Services
CustomListView {
id: root
required property ProcessesHeader columnHeader
required property int rowHeight
property real rowSpacing: Tokens.spacing.small
model: Processes.list
spacing: root.rowSpacing
clip: true
CustomScrollBar.vertical: CustomScrollBar {
flickable: root
}
delegate: CustomRect {
id: processRow
required property var modelData
required property int index
implicitWidth: ListView.view.width
implicitHeight: rowLayout.implicitHeight
radius: Tokens.rounding.full
StateLayer {
id: rowHover
}
RowLayout {
id: rowLayout
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
spacing: Tokens.spacing.extraSmall / 2
CustomClippingRect {
id: iconClip
Layout.fillHeight: true
Layout.margins: Tokens.padding.medium
implicitWidth: height
radius: Tokens.rounding.full
IconImage {
id: appIcon
anchors.fill: parent
implicitSize: height
source: Quickshell.iconPath(processRow.modelData.iconName, "application-x-executable")
}
}
Repeater {
id: cellRepeater
model: root.columnHeader.columnsModel
Item {
id: rowCell
required property var modelData
required property int index
implicitWidth: root.columnHeader.widthForColumn(modelData.id, modelData.width) - (index === 0 ? iconClip.implicitWidth + iconClip.Layout.margins * 2 + rowLayout.spacing : 0)
implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
Layout.preferredWidth: implicitWidth
Layout.fillHeight: true
CustomRect {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: Tokens.padding.small
anchors.leftMargin: -(rowLayout.spacing)
implicitWidth: 2
color: Colors.palette.m3outlineVariant
visible: rowCell.index !== 0
}
Item {
id: layout
anchors.fill: parent
anchors.margins: Tokens.padding.small
anchors.leftMargin: rowCell.index !== 0 ? Tokens.padding.medium : 0
anchors.rightMargin: Tokens.padding.medium
implicitHeight: mainText.implicitHeight + subText.implicitHeight + mainText.anchors.margins * 2 + subText.anchors.topMargin
CustomText {
id: mainText
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
elide: CustomText.ElideRight
text: {
const value = processRow.modelData[rowCell.modelData.id];
return value === undefined ? "" : ProcessesColumns.formatCell(rowCell.modelData.id, value);
}
states: State {
name: "centerAnchored"
when: !subText.visible
AnchorChanges {
target: mainText
anchors.top: undefined
anchors.verticalCenter: layout.verticalCenter
}
}
}
CustomText {
id: subText
anchors.top: mainText.bottom
anchors.left: parent.left
anchors.right: parent.right
visible: rowCell.modelData.id === "command"
elide: CustomText.ElideRight
text: rowCell.modelData.id === "command" ? processRow.modelData["commandLine"] : ""
font: Tokens.font.label.small
color: Colors.palette.m3outline
}
}
}
}
}
}
}
@@ -0,0 +1,86 @@
pragma Singleton
import QtQuick
import Quickshell
import ZShell.Internal
import ZShell.Services
Singleton {
id: root
readonly property int historyLength: 30
readonly property CircularBuffer cpuBuffer: _cpuBuffer
readonly property CircularBuffer memoryBuffer: _memoryBuffer
readonly property list<var> cpuInfo: [
{
label: qsTr("Usage"),
value: `${Math.round(Cpu.percentage * 100)}%`
},
{
label: qsTr("Temp"),
value: `${Math.ceil(Cpu.temperature)}°${"C"}`
}
]
readonly property list<var> memoryInfo: [
{
label: qsTr("Used"),
value: memoryUsageFormat(Memory.used, Memory.total)
},
{
label: qsTr("Cached"),
value: memoryTotalFormat(Memory.cached)
},
{
label: qsTr("Writes"),
value: memoryTotalFormat(Memory.writeTotal)
},
{
label: qsTr("Reads"),
value: memoryTotalFormat(Memory.readTotal)
}
]
function memoryTotalFormat(kib: int): string {
const fmt = UsageFmt.formatKib(kib);
return `${fmt.value.toFixed(1)} ${fmt.unit}`;
}
function memoryUsageFormat(used: int, total: int): string {
const fmt = UsageFmt.formatKib(used, total);
return `${fmt.value.toFixed(1)} / ${Math.floor(fmt.total)} ${fmt.unit}`;
}
Connections {
function onUpdateTriggered(): void {
if (Cpu.percentage >= 0 && isFinite(Cpu.percentage))
_cpuBuffer.push(Cpu.percentage);
}
target: Cpu
}
Connections {
function onUpdateTriggered(): void {
if (Memory.percentage >= 0 && isFinite(Memory.percentage))
_memoryBuffer.push(Memory.percentage);
}
target: Memory
}
CircularBuffer {
id: _cpuBuffer
capacity: root.historyLength + 1
smoothingRadius: 2
}
CircularBuffer {
id: _memoryBuffer
capacity: root.historyLength + 1
smoothingRadius: 2
}
}
+187
View File
@@ -0,0 +1,187 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import ZShell.Components
import ZShell.Services
import qs.Components
import qs.Services
Item {
id: root
required property int radius
implicitHeight: layout.implicitHeight
ServiceRef {
service: Cpu
}
ServiceRef {
service: Memory
}
RowLayout {
id: layout
anchors.fill: parent
spacing: Tokens.spacing.medium
UsageCard {
accent: Colors.palette.m3primary
buffer: ResourceUsage.cpuBuffer
icon: "memory"
label: qsTr("CPU")
values: ResourceUsage.cpuInfo
}
UsageCard {
accent: Colors.palette.m3tertiary
buffer: ResourceUsage.memoryBuffer
icon: "memory_alt"
label: qsTr("Memory")
values: ResourceUsage.memoryInfo
}
}
component UsageCard: CustomRect {
id: card
property color accent
required property var buffer
property string icon
property string label
property list<var> values
color: Colors.tPalette.m3surfaceContainer
radius: root.radius
Layout.fillWidth: true
Layout.fillHeight: true
implicitHeight: mainLayout.implicitHeight + mainLayout.anchors.margins * 2
ColumnLayout {
id: mainLayout
anchors.fill: parent
anchors.margins: Tokens.padding.large
spacing: 0
RowLayout {
spacing: Tokens.spacing.small
MaterialIcon {
color: card.accent
text: card.icon
}
CustomText {
text: card.label
}
}
Item {
id: graphWrapper
Layout.fillHeight: true
Layout.fillWidth: true
implicitHeight: infoColumn.implicitHeight
Item {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: infoColumn.left
SparklineItem {
id: sparkline
anchors.fill: parent
historyLength: ResourceUsage.historyLength
line1: card.buffer
line1Color: card.accent
line1FillAlpha: 0.15
maxValue: 1.0
slideProgress: 1
Connections {
function onValuesChanged(): void {
slideAnim.restart();
}
target: card.buffer
}
NumberAnimation {
id: slideAnim
duration: Config.dashboard.resourceUpdateInterval
easing.type: Easing.Linear
from: 0
property: "slideProgress"
target: sparkline
to: 1
}
}
// "Collecting data" placeholder
CustomText {
anchors.centerIn: parent
color: Colors.palette.m3outline
text: qsTr("Collecting data...")
visible: card.buffer.count < 2
}
}
ColumnLayout {
id: infoColumn
anchors.right: parent.right
anchors.bottom: parent.bottom
width: {
const prefWidth = graphWrapper.width / 3;
const minWidth = graphWrapper.width / 4;
if (prefWidth < implicitWidth)
return implicitWidth;
else if (prefWidth >= implicitWidth * 2)
return minWidth;
else
return prefWidth;
}
Repeater {
model: card.values
Item {
id: moreInfo
required property var modelData
Layout.leftMargin: Tokens.spacing.medium
Layout.fillWidth: true
implicitWidth: label.implicitWidth + value.implicitWidth + label.anchors.margins * 2
implicitHeight: label.implicitHeight
CustomText {
id: label
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
color: Colors.palette.m3onSurfaceVariant
text: moreInfo.modelData.label
}
CustomText {
id: value
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
color: card.accent
text: moreInfo.modelData.value
}
}
}
}
}
}
}
}
+54
View File
@@ -0,0 +1,54 @@
import QtQuick
import Quickshell
import ZShell.Config
import ZShell.Services
import qs.Modules.Dashboard.Processes
import qs.Components
import qs.Services
Item {
id: root
required property int radius
implicitWidth: Tokens.sizes.dashboard.processesWidth
implicitHeight: Tokens.sizes.dashboard.processesHeight
ServiceRef {
service: Processes
}
ProcessesHeader {
id: header
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
headerHeight: Tokens.sizes.dashboard.processesHeaderHeight
pList: processList
}
ProcessesList {
id: processList
anchors.top: header.bottom
anchors.bottom: sysInfo.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: Tokens.spacing.small
anchors.bottomMargin: Tokens.spacing.small
columnHeader: header
rowHeight: Tokens.sizes.dashboard.processesRowHeight
}
SystemInfo {
id: sysInfo
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
radius: root.radius
}
}
+90 -144
View File
@@ -1,23 +1,24 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import qs.Components
import ZShell.Config
import qs.Helpers
import qs.Modules
import Quickshell
import Quickshell.Widgets
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Layouts
import QtQuick.Templates
import Quickshell
import ZShell.Config
import qs.Components
import qs.Services import qs.Services
Item { Item {
id: root id: root
readonly property alias count: bar.count
required property real nonAnimWidth required property real nonAnimWidth
required property PersistentProperties state required property ScreenState screenState
required property var tabs
implicitHeight: bar.implicitHeight + indicator.implicitHeight + indicator.anchors.topMargin + separator.implicitHeight readonly property alias count: bar.count
readonly property int tabIndicatorSpacing: Tokens.spacing.small
implicitHeight: bar.implicitHeight + bar.anchors.topMargin + indicator.implicitHeight + indicator.anchors.topMargin + separator.implicitHeight
TabBar { TabBar {
id: bar id: bar
@@ -25,85 +26,99 @@ Item {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: root.tabIndicatorSpacing
currentIndex: root.screenState.dashboardTab
onCurrentIndexChanged: root.screenState.dashboardTab = currentIndex
implicitHeight: contentHeight
background: null background: null
currentIndex: root.state.currentTab contentItem: RowLayout {
spacing: 0
onCurrentIndexChanged: root.state.currentTab = currentIndex Repeater {
model: bar.contentModel
Tab { }
iconName: "dashboard"
text: qsTr("Dashboard")
} }
Tab { Repeater {
iconName: "queue_music" model: ScriptModel {
text: qsTr("Media") values: root.tabs
} }
Tab { delegate: Tab {
iconName: "speed" required property var modelData
text: qsTr("Performance")
}
Tab { iconName: modelData.iconName
iconName: "cloud" text: modelData.text
text: qsTr("Weather") }
} }
// Tab {
// iconName: "workspaces"
// text: qsTr("Workspaces")
// }
} }
Item { Item {
id: indicator id: indicator
anchors.top: bar.bottom anchors.top: bar.bottom
clip: true anchors.topMargin: root.tabIndicatorSpacing
implicitHeight: 40
implicitWidth: bar.currentItem.implicitWidth implicitWidth: {
const tab = bar.currentItem;
if (tab)
return tab.implicitWidth;
const width = (root.nonAnimWidth - bar.spacing * (bar.count - 1)) / bar.count;
return width;
}
implicitHeight: 3
x: { x: {
const tab = bar.currentItem; const tab = bar.currentItem;
const width = (root.nonAnimWidth - bar.spacing * (bar.count - 1)) / bar.count; const width = (root.nonAnimWidth - bar.spacing * (bar.count - 1)) / bar.count;
return width * tab.TabBar.index + (width - tab.implicitWidth) / 2; const tabWidth = tab?.implicitWidth ?? width;
return width * bar.currentIndex + (width - tabWidth) / 2;
}
clip: true
CustomRect {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: parent.implicitHeight * 2
color: Colors.palette.m3primary
radius: Tokens.rounding.full
}
Behavior on x {
Anim {}
} }
Behavior on implicitWidth { Behavior on implicitWidth {
Anim { Anim {}
}
}
Behavior on x {
Anim {
}
}
CustomRect {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
color: Colors.palette.m3primary
implicitHeight: parent.implicitHeight * 2
radius: Tokens.rounding.full
} }
} }
CustomRect { CustomRect {
id: separator id: separator
anchors.top: indicator.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: indicator.bottom
color: Colors.palette.m3outlineVariant
implicitHeight: 1 implicitHeight: 1
color: Colors.palette.m3outlineVariant
} }
component Tab: TabButton { component Tab: TabButton {
id: tab id: tab
readonly property bool current: TabBar.tabBar.currentItem === this
required property string iconName required property string iconName
readonly property bool current: TabBar.tabBar.currentItem === this
Layout.fillWidth: true
Layout.preferredWidth: 1 // Uniform width across all tabs
implicitWidth: implicitContentWidth
implicitHeight: implicitContentHeight
background: null background: null
contentItem: CustomMouseArea { contentItem: CustomMouseArea {
@@ -111,117 +126,47 @@ Item {
function onWheel(event: WheelEvent): void { function onWheel(event: WheelEvent): void {
if (event.angleDelta.y < 0) if (event.angleDelta.y < 0)
root.state.currentTab = Math.min(root.state.currentTab + 1, bar.count - 1); root.screenState.dashboardTab = Math.min(root.screenState.dashboardTab + 1, bar.count - 1);
else if (event.angleDelta.y > 0) else if (event.angleDelta.y > 0)
root.state.currentTab = Math.max(root.state.currentTab - 1, 0); root.screenState.dashboardTab = Math.max(root.screenState.dashboardTab - 1, 0);
} }
cursorShape: Qt.PointingHandCursor
implicitHeight: icon.height + label.height
implicitWidth: Math.max(icon.width, label.width) implicitWidth: Math.max(icon.width, label.width)
implicitHeight: icon.height + label.height
onPressed: event => { hoverEnabled: true
root.state.currentTab = tab.TabBar.index; cursorShape: Qt.PointingHandCursor
const stateY = stateWrapper.y; onPressed: root.screenState.dashboardTab = tab.TabBar.index
rippleAnim.x = event.x;
rippleAnim.y = event.y - stateY;
const dist = (ox, oy) => ox * ox + oy * oy; StateLayer {
rippleAnim.radius = Math.sqrt(Math.max(dist(event.x, event.y + stateY), dist(event.x, stateWrapper.height - event.y), dist(width - event.x, event.y + stateY), dist(width - event.x, stateWrapper.height - event.y))); id: stateLayer
rippleAnim.restart();
}
SequentialAnimation {
id: rippleAnim
property real radius
property real x
property real y
PropertyAction {
property: "x"
target: ripple
value: rippleAnim.x
}
PropertyAction {
property: "y"
target: ripple
value: rippleAnim.y
}
PropertyAction {
property: "opacity"
target: ripple
value: 0.08
}
Anim {
from: 0
properties: "implicitWidth,implicitHeight"
target: ripple
to: rippleAnim.radius * 2
}
Anim {
easing.type: Easing.BezierSpline
property: "opacity"
target: ripple
to: 0
}
}
ClippingRectangle {
id: stateWrapper
anchors.fill: undefined
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: "transparent" implicitHeight: parent.height + root.tabIndicatorSpacing * 2
implicitHeight: parent.height + 8 * 2
radius: Tokens.rounding.small
CustomRect { radius: Tokens.rounding.medium
id: stateLayer color: tab.current ? Colors.palette.m3primary : Colors.palette.m3onSurface
onClicked: root.screenState.dashboardTab = tab.TabBar.index
anchors.fill: parent
color: tab.current ? Colors.palette.m3primary : Colors.palette.m3onSurface
opacity: mouse.pressed ? 0.1 : tab.hovered ? 0.08 : 0
Behavior on opacity {
Anim {
}
}
}
CustomRect {
id: ripple
color: tab.current ? Colors.palette.m3primary : Colors.palette.m3onSurface
opacity: 0
radius: Tokens.rounding.full
transform: Translate {
x: -ripple.width / 2
y: -ripple.height / 2
}
}
} }
MaterialIcon { MaterialIcon {
id: icon id: icon
anchors.bottom: label.top
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: label.top
text: tab.iconName
color: tab.current ? Colors.palette.m3primary : Colors.palette.m3onSurfaceVariant color: tab.current ? Colors.palette.m3primary : Colors.palette.m3onSurfaceVariant
fill: tab.current ? 1 : 0 fill: tab.current ? 1 : 0
font.pointSize: 18 fontStyle: Tokens.font.icon.medium
text: tab.iconName
Behavior on fill { Behavior on fill {
Anim { Anim {
type: Anim.DefaultEffects
} }
} }
} }
@@ -229,10 +174,11 @@ Item {
CustomText { CustomText {
id: label id: label
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
color: tab.current ? Colors.palette.m3primary : Colors.palette.m3onSurfaceVariant anchors.bottom: parent.bottom
text: tab.text text: tab.text
color: tab.current ? Colors.palette.m3primary : Colors.palette.m3onSurfaceVariant
} }
} }
} }
+145
View File
@@ -0,0 +1,145 @@
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import qs.Components
import qs.Services
import qs.Helpers
RowLayout {
id: root
spacing: Tokens.spacing.largeIncreased
readonly property list<var> forecastToday: Weather.hourlyForecast.slice(0, 25)
property var now: new Date()
property bool useTwelveHourClock: Config.services.useTwelveHourClock
property bool curveSmoothing: true
property real smoothing: 2
property real markerGap: Math.min(Math.max(Tokens.spacing.medium / 2, 0), 6)
readonly property real markerX: {
const d = root.now;
return d.getHours() + d.getMinutes() / 60;
}
function formatHour(h) {
if (root.useTwelveHourClock) {
const hr = h % 12 === 0 ? 12 : h % 12;
return hr + (h < 12 ? " AM" : " PM");
}
return h;
}
Timer {
interval: 60000
repeat: true
running: true
onTriggered: root.now = new Date()
}
ColumnLayout {
Layout.fillWidth: true
Layout.preferredWidth: root.width / 2
RowLayout {
Layout.fillWidth: true
MaterialIcon {
text: "device_thermostat"
fontStyle: Tokens.font.icon.medium
color: Colors.palette.m3secondary
}
CustomText {
text: qsTr("Temperature")
}
}
Item {
id: temperature
implicitHeight: 150
Layout.fillWidth: true
LineGraph {
id: tempGraph
anchors.fill: parent
values: root.forecastToday.map(e => e.tempC)
verticalLineStep: 3
xSteps: 24
horizontalLineStep: 10
minHorizontalLines: 4
yPaddingTop: horizontalLineStep
yPaddingBottom: horizontalLineStep
xLabel: i => root.formatHour(i)
yLabel: v => Math.round(v) + "°"
markerX: root.markerX
smoothing: root.smoothing
curveSmoothing: root.curveSmoothing
markerGap: root.markerGap
lineColor: Colors.palette.m3secondary
gridColor: Colors.palette.m3onSurface
markerColor: Colors.palette.m3secondary
labelColor: Colors.palette.m3onSurfaceVariant
}
}
}
ColumnLayout {
Layout.fillWidth: true
Layout.preferredWidth: root.width / 2
RowLayout {
Layout.fillWidth: true
MaterialIcon {
text: "water_drop"
fontStyle: Tokens.font.icon.medium
color: Colors.palette.m3tertiary
}
CustomText {
text: qsTr("Chance of precipitation")
}
}
Item {
id: precipitation
Layout.fillWidth: true
implicitHeight: 150
LineGraph {
id: graph
anchors.fill: parent
values: root.forecastToday.map(e => e?.precipChance ?? null)
xSteps: 24
yMin: 0
yMax: 100
yPaddingTop: 20
verticalLineStep: 3
lastXLine: false
horizontalLineStep: 20
xLabel: i => root.formatHour(i)
yLabel: v => v + "%"
markerX: root.markerX
smoothing: root.smoothing
curveSmoothing: root.curveSmoothing
markerGap: root.markerGap
lineColor: Colors.palette.m3tertiary
gridColor: Colors.palette.m3onSurface
markerColor: Colors.palette.m3tertiary
labelColor: Colors.palette.m3onSurfaceVariant
}
}
}
}
+263
View File
@@ -0,0 +1,263 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import qs.Components
import qs.Modules.Dashboard.Weather
import qs.Services
import qs.Helpers
Item {
id: root
required property int radius
readonly property var today: Weather.forecast && Weather.forecast.length > 0 ? Weather.forecast[0] : null
implicitWidth: layout.implicitWidth > 800 ? layout.implicitWidth : 840
implicitHeight: layout.implicitHeight
Component.onCompleted: Weather.reload()
ColumnLayout {
id: layout
anchors.fill: parent
spacing: Tokens.spacing.medium
RowLayout {
Layout.leftMargin: Tokens.padding.large
Layout.rightMargin: Tokens.padding.large
Layout.fillWidth: true
Column {
spacing: Tokens.spacing.extraSmall
CustomText {
text: Weather.city || qsTr("Loading...")
font: Tokens.font.body.builders.large.size(24).weight(Font.DemiBold).build()
color: Colors.palette.m3onSurface
}
CustomText {
text: new Date().toLocaleDateString(Qt.locale("en_US"), "dddd, MMMM d")
font: Tokens.font.body.small
color: Colors.palette.m3onSurfaceVariant
}
}
Item {
Layout.fillWidth: true
}
Row {
spacing: Tokens.spacing.largeIncreased
WeatherStat {
icon: "wb_twilight"
label: qsTr("Sunrise")
value: Weather.sunrise
colour: Colors.palette.m3tertiary
}
WeatherStat {
icon: "bedtime"
label: qsTr("Sunset")
value: Weather.sunset
colour: Colors.palette.m3tertiary
}
}
}
CustomRect {
Layout.fillWidth: true
implicitHeight: bigInfoRow.implicitHeight + bigInfoRow.anchors.margins * 2
radius: Tokens.rounding.large
color: Colors.tPalette.m3surfaceContainer
Graphs {
id: bigInfoRow
anchors.fill: parent
anchors.margins: Tokens.padding.medium
}
}
RowLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.medium
DetailCard {
icon: Weather.icon
label: qsTr("Temperature")
value: Weather.temp
colour: Colors.palette.m3primary
}
DetailCard {
icon: "thermostat"
label: qsTr("Feels like", "apparent temperature")
value: Weather.feelsLike
colour: Colors.palette.m3primary
}
DetailCard {
icon: "water_drop"
label: qsTr("Humidity")
value: Strings.percent(Weather.humidity)
colour: Colors.palette.m3secondary
}
DetailCard {
icon: "air"
label: qsTr("Wind")
value: Weather.windSpeed ? qsTr("%1 km/h").arg(Weather.windSpeed) : "--"
colour: Colors.palette.m3tertiary
}
}
CustomText {
Layout.topMargin: Tokens.spacing.medium
Layout.leftMargin: Tokens.padding.medium
visible: forecastRepeater.count > 0
text: qsTr("7-day forecast")
font: Tokens.font.body.builders.medium.weight(Font.DemiBold).build()
color: Colors.palette.m3onSurface
}
RowLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.medium
Repeater {
id: forecastRepeater
model: Weather.forecast
CustomRect {
id: forecastItem
required property int index
required property var modelData
Layout.fillWidth: true
implicitHeight: forecastItemColumn.implicitHeight + Tokens.padding.medium * 2
radius: root.radius
color: Colors.tPalette.m3surfaceContainer
ColumnLayout {
id: forecastItemColumn
anchors.centerIn: parent
spacing: Tokens.spacing.small
CustomText {
Layout.alignment: Qt.AlignHCenter
text: forecastItem.index === 0 ? qsTr("Today", "forecast column") : new Date(forecastItem.modelData.date).toLocaleDateString(Qt.locale("en_US"), "ddd")
font: Tokens.font.body.builders.medium.weight(Font.DemiBold).build()
color: Colors.palette.m3primary
}
CustomText {
Layout.topMargin: -Tokens.spacing.extraSmall
Layout.alignment: Qt.AlignHCenter
text: new Date(forecastItem.modelData.date).toLocaleDateString(Qt.locale("en_US"), "MMM d")
font: Tokens.font.body.small
opacity: 0.7
color: Colors.palette.m3onSurfaceVariant
}
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
text: forecastItem.modelData.icon
fontStyle: Tokens.font.icon.extraLarge
color: Colors.palette.m3secondary
}
CustomText {
Layout.alignment: Qt.AlignHCenter
text: {
const min = Weather.formatTemp(forecastItem.modelData.minTempC, true);
const max = Weather.formatTemp(forecastItem.modelData.maxTempC, true);
return qsTr("%1 / %2", "min/max temperature").arg(min).arg(max);
}
font: Tokens.font.body.builders.small.weight(Font.DemiBold).build()
color: Colors.palette.m3tertiary
}
}
}
}
}
}
component DetailCard: CustomRect {
id: detailRoot
property string icon
property string label
property string value
property color colour
Layout.fillWidth: true
Layout.preferredHeight: 60
radius: Tokens.rounding.medium
color: Colors.tPalette.m3surfaceContainer
Row {
anchors.centerIn: parent
spacing: Tokens.spacing.medium
MaterialIcon {
text: detailRoot.icon
color: detailRoot.colour
fontStyle: Tokens.font.icon.large
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 0
CustomText {
text: detailRoot.label
font: Tokens.font.body.small
opacity: 0.7
horizontalAlignment: Text.AlignLeft
}
CustomText {
text: detailRoot.value
font: Tokens.font.body.builders.small.weight(Font.DemiBold).build()
horizontalAlignment: Text.AlignLeft
}
}
}
}
component WeatherStat: RowLayout {
id: weatherStat
property string icon
property string label
property string value
property color colour
spacing: Tokens.spacing.small
MaterialIcon {
text: weatherStat.icon
fontStyle: Tokens.font.icon.extraLarge
color: weatherStat.colour
}
Column {
Layout.alignment: Qt.AlignVCenter
CustomText {
text: weatherStat.label
font: Tokens.font.body.small
color: Colors.palette.m3onSurfaceVariant
}
CustomText {
text: weatherStat.value
font: Tokens.font.body.builders.small.weight(Font.DemiBold).build()
color: Colors.palette.m3onSurface
}
}
}
}
+7 -22
View File
@@ -8,24 +8,13 @@ import ZShell.Config
Item { Item {
id: root id: root
readonly property PersistentProperties dashState: PersistentProperties {
property date currentDate: new Date()
property int currentTab
reloadableId: "dashboardState"
}
readonly property real nonAnimHeight: state === "visible" ? (content.item?.nonAnimHeight ?? 0) : 0 readonly property real nonAnimHeight: state === "visible" ? (content.item?.nonAnimHeight ?? 0) : 0
required property real offsetScale required property real offsetScale
required property string position readonly property bool shouldBeActive: root.screenState.dashboard && Config.dashboard.enabled
readonly property bool shouldBeActive: root.visibilities.dashboard && Config.dashboard.enabled required property ScreenState screenState
required property PersistentProperties visibilities
anchors.bottom: position === "bottom" ? parent.bottom : undefined anchors.top: parent.top
anchors.bottomMargin: position === "bottom" ? (-implicitHeight - 5) * offsetScale : 0 anchors.topMargin: (-implicitHeight - 5) * offsetScale
anchors.left: position === "left" ? parent.left : undefined
anchors.leftMargin: position === "left" ? (-implicitWidth - 5) * offsetScale : 0
anchors.top: position === "top" ? parent.top : undefined
anchors.topMargin: position === "top" ? (-implicitHeight - 5) * offsetScale : 0
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth || 854 implicitWidth: content.implicitWidth || 854
opacity: 1 - offsetScale opacity: 1 - offsetScale
@@ -35,15 +24,11 @@ Item {
id: content id: content
active: root.shouldBeActive || root.visible active: root.shouldBeActive || root.visible
anchors.bottom: root.position === "top" ? parent.bottom : undefined anchors.bottom: parent.bottom
anchors.horizontalCenter: root.position !== "left" ? parent.horizontalCenter : undefined anchors.horizontalCenter: parent.horizontalCenter
anchors.right: root.position !== "left" ? undefined : parent.right
anchors.top: root.position === "bottom" ? parent.top : undefined
anchors.verticalCenter: root.position === "left" ? undefined : parent.verticalCenter
sourceComponent: Content { sourceComponent: Content {
dashState: root.dashState screenState: root.screenState
visibilities: root.visibilities
} }
} }
} }
+163 -142
View File
@@ -8,177 +8,198 @@ import ZShell.Config
import qs.Paths import qs.Paths
import qs.Helpers import qs.Helpers
import qs.Services import qs.Services
import qs.Services
Item { Item {
id: root id: root
anchors.fill: parent anchors.fill: parent
z: 998 z: 998
visible: false visible: false
property real menuX: 0 property real menuX: 0
property real menuY: 0 property real menuY: 0
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: root.close() onClicked: root.close()
} }
CustomClippingRect { CustomClippingRect {
id: popupBackground id: popupBackground
readonly property real padding: 4 readonly property real padding: 4
x: root.menuX x: root.menuX
y: root.menuY y: root.menuY
color: Colors.tPalette.m3surface color: Colors.tPalette.m3surface
radius: Tokens.rounding.medium radius: Tokens.rounding.medium
implicitWidth: menuLayout.implicitWidth + padding * 2 implicitWidth: menuLayout.implicitWidth + padding * 2
implicitHeight: menuLayout.implicitHeight + padding * 2 implicitHeight: menuLayout.implicitHeight + padding * 2
Behavior on opacity { Anim {} } Behavior on opacity {
opacity: root.visible ? 1 : 0 Anim {}
}
opacity: root.visible ? 1 : 0
ColumnLayout { ColumnLayout {
id: menuLayout id: menuLayout
anchors.centerIn: parent anchors.centerIn: parent
spacing: 0 spacing: 0
CustomRect { CustomRect {
Layout.preferredWidth: 200 Layout.preferredWidth: 200
radius: popupBackground.radius - popupBackground.padding radius: popupBackground.radius - popupBackground.padding
implicitHeight: openTerminalRow.implicitHeight + Tokens.padding.extraSmall * 2 implicitHeight: openTerminalRow.implicitHeight + Tokens.padding.extraSmall * 2
RowLayout { RowLayout {
id: openTerminalRow id: openTerminalRow
spacing: 8 spacing: 8
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: Tokens.padding.small anchors.leftMargin: Tokens.padding.small
MaterialIcon { text: "terminal"; font.pointSize: 20 } MaterialIcon {
CustomText { text: "Open terminal"; Layout.fillWidth: true } text: "terminal"
} fontStyle: Tokens.font.icon.medium
}
CustomText {
text: "Open terminal"
Layout.fillWidth: true
}
}
StateLayer { StateLayer {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
Quickshell.execDetached([Config.general.apps.terminal, "--working-directory", FileUtils.trimFileProtocol(Paths.desktop)]) Quickshell.execDetached([Config.general.apps.terminal, "--working-directory", FileUtils.trimFileProtocol(Paths.desktop)]);
root.close() root.close();
} }
} }
} }
CustomRect { CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: 1 implicitHeight: 1
color: Colors.palette.m3outlineVariant color: Colors.palette.m3outlineVariant
Layout.topMargin: 4 Layout.topMargin: 4
Layout.bottomMargin: 4 Layout.bottomMargin: 4
} }
CustomRect { CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
radius: popupBackground.radius - popupBackground.padding radius: popupBackground.radius - popupBackground.padding
implicitHeight: settingsRow.implicitHeight + Tokens.padding.extraSmall * 2 implicitHeight: settingsRow.implicitHeight + Tokens.padding.extraSmall * 2
RowLayout { RowLayout {
id: settingsRow id: settingsRow
spacing: 8 spacing: 8
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: Tokens.padding.small anchors.leftMargin: Tokens.padding.small
MaterialIcon { text: "settings"; font.pointSize: 20 } MaterialIcon {
CustomText { text: "ZShell settings"; Layout.fillWidth: true } text: "settings"
} fontStyle: Tokens.font.icon.medium
}
CustomText {
text: "ZShell settings"
Layout.fillWidth: true
}
}
StateLayer { StateLayer {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
const visibilities = Visibilities.getForActive(); const state = ShellState.forActive();
visibilities.settings = true; state.settings = true;
root.close() root.close();
} }
} }
} }
CustomRect { CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: 1 implicitHeight: 1
color: Colors.palette.m3outlineVariant color: Colors.palette.m3outlineVariant
Layout.topMargin: 4 Layout.topMargin: 4
Layout.bottomMargin: 4 Layout.bottomMargin: 4
} }
CustomRect { CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
radius: popupBackground.radius - popupBackground.padding radius: popupBackground.radius - popupBackground.padding
implicitHeight: logoutRow.implicitHeight + Tokens.padding.extraSmall * 2 implicitHeight: logoutRow.implicitHeight + Tokens.padding.extraSmall * 2
RowLayout { RowLayout {
id: logoutRow id: logoutRow
spacing: 8 spacing: 8
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: Tokens.padding.small anchors.leftMargin: Tokens.padding.small
MaterialIcon { text: "logout"; font.pointSize: 20 } MaterialIcon {
CustomText { text: "Logout"; Layout.fillWidth: true } text: "logout"
} fontStyle: Tokens.font.icon.medium
}
CustomText {
text: "Logout"
Layout.fillWidth: true
}
}
StateLayer { StateLayer {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
Hyprland.dispatch("global quickshell:sessionOpen") Hyprland.dispatch("global quickshell:sessionOpen");
root.close() root.close();
} }
} }
} }
// CustomRect { // CustomRect {
// Layout.fillWidth: true // Layout.fillWidth: true
// implicitHeight: 1 // implicitHeight: 1
// color: Colors.palette.m3outlineVariant // color: Colors.palette.m3outlineVariant
// Layout.topMargin: 4 // Layout.topMargin: 4
// Layout.bottomMargin: 4 // Layout.bottomMargin: 4
// } // }
// //
// CustomRect { // CustomRect {
// Layout.fillWidth: true // Layout.fillWidth: true
// radius: popupBackground.radius - popupBackground.padding // radius: popupBackground.radius - popupBackground.padding
// implicitHeight: desktopIconsRow.implicitHeight + Tokens.padding.extraSmall * 2 // implicitHeight: desktopIconsRow.implicitHeight + Tokens.padding.extraSmall * 2
// //
// RowLayout { // RowLayout {
// id: desktopIconsRow // id: desktopIconsRow
// spacing: 8 // spacing: 8
// anchors.fill: parent // anchors.fill: parent
// anchors.leftMargin: Tokens.padding.small // anchors.leftMargin: Tokens.padding.small
// //
// MaterialIcon { text: Config.options.background.showDesktopIcons ? "visibility_off" : "visibility"; font.pointSize: 20 } // MaterialIcon { text: Config.options.background.showDesktopIcons ? "visibility_off" : "visibility"; font.pointSize: 20 }
// CustomText { text: Config.options.background.showDesktopIcons ? "Hide icons" : "Show icons"; Layout.fillWidth: true } // CustomText { text: Config.options.background.showDesktopIcons ? "Hide icons" : "Show icons"; Layout.fillWidth: true }
// } // }
// //
// StateLayer { // StateLayer {
// anchors.fill: parent // anchors.fill: parent
// //
// onClicked: { // onClicked: {
// Config.options.background.showDesktopIcons = !Config.options.background.showDesktopIcons // Config.options.background.showDesktopIcons = !Config.options.background.showDesktopIcons
// root.close() // root.close()
// } // }
// } // }
// } // }
} }
} }
function openAt(mouseX, mouseY, parentW, parentH) { function openAt(mouseX, mouseY, parentW, parentH) {
menuX = Math.floor(Math.min(mouseX, parentW - popupBackground.implicitWidth)) menuX = Math.floor(Math.min(mouseX, parentW - popupBackground.implicitWidth));
menuY = Math.floor(Math.min(mouseY, parentH - popupBackground.implicitHeight)) menuY = Math.floor(Math.min(mouseY, parentH - popupBackground.implicitHeight));
visible = true visible = true;
} }
function close() { function close() {
visible = false visible = false;
} }
} }
@@ -54,8 +54,7 @@ Item {
y: contextMenu.menuY y: contextMenu.menuY
Behavior on opacity { Behavior on opacity {
Anim { Anim {}
}
} }
ColumnLayout { ColumnLayout {
@@ -77,7 +76,7 @@ Item {
spacing: 8 spacing: 8
MaterialIcon { MaterialIcon {
font.pointSize: 20 fontStyle: Tokens.font.icon.medium
text: "open_in_new" text: "open_in_new"
} }
@@ -120,7 +119,7 @@ Item {
spacing: 8 spacing: 8
MaterialIcon { MaterialIcon {
font.pointSize: 20 fontStyle: Tokens.font.icon.medium
text: contextMenu.targetIsDir ? "terminal" : "apps" text: contextMenu.targetIsDir ? "terminal" : "apps"
} }
@@ -165,7 +164,7 @@ Item {
spacing: 8 spacing: 8
MaterialIcon { MaterialIcon {
font.pointSize: 20 fontStyle: Tokens.font.icon.medium
text: "content_copy" text: "content_copy"
} }
@@ -199,7 +198,7 @@ Item {
spacing: 8 spacing: 8
MaterialIcon { MaterialIcon {
font.pointSize: 20 fontStyle: Tokens.font.icon.medium
text: "edit" text: "edit"
} }
@@ -241,7 +240,7 @@ Item {
MaterialIcon { MaterialIcon {
color: deleteButton.hovered ? Colors.palette.m3onError : Colors.palette.m3error color: deleteButton.hovered ? Colors.palette.m3onError : Colors.palette.m3error
font.pointSize: 20 fontStyle: Tokens.font.icon.medium
text: "delete" text: "delete"
} }
+3 -3
View File
@@ -27,7 +27,7 @@ Item {
property var pendingCommitIds: [] property var pendingCommitIds: []
readonly property int rounding: Tokens.rounding.largeIncreased readonly property int rounding: Tokens.rounding.largeIncreased
required property ShellScreen screen required property ShellScreen screen
required property PersistentProperties visibilities required property ScreenState screenState
property var visualIds: [] property var visualIds: []
function beginVisualDrag(appId, modelData, item) { function beginVisualDrag(appId, modelData, item) {
@@ -198,7 +198,7 @@ Item {
anchors.centerIn: parent anchors.centerIn: parent
appListRoot: root appListRoot: root
appToplevel: modelData appToplevel: modelData
visibilities: root.visibilities screenState: root.screenState
visible: root.draggedAppId !== slot.appId visible: root.draggedAppId !== slot.appId
} }
@@ -314,7 +314,7 @@ Item {
appListRoot: root appListRoot: root
appToplevel: root.draggedModelData appToplevel: root.draggedModelData
enabled: false enabled: false
visibilities: root.visibilities screenState: root.screenState
} }
} }
} }
+3 -3
View File
@@ -19,7 +19,7 @@ CustomRect {
property real iconSize: implicitHeight - 20 property real iconSize: implicitHeight - 20
readonly property bool isSeparator: appToplevel?.appId === "__dock_separator__" readonly property bool isSeparator: appToplevel?.appId === "__dock_separator__"
property int lastFocused: -1 property int lastFocused: -1
required property PersistentProperties visibilities required property ScreenState screenState
implicitHeight: Config.dock.height implicitHeight: Config.dock.height
implicitWidth: isSeparator ? 1 : implicitHeight implicitWidth: isSeparator ? 1 : implicitHeight
@@ -62,12 +62,12 @@ CustomRect {
onClicked: { onClicked: {
if (appToplevel?.toplevels.length === 0) { if (appToplevel?.toplevels.length === 0) {
root.desktopEntry?.execute(); root.desktopEntry?.execute();
root.visibilities.dock = false; root.screenState.dock = false;
return; return;
} }
lastFocused = (lastFocused + 1) % appToplevel?.toplevels.length; lastFocused = (lastFocused + 1) % appToplevel?.toplevels.length;
appToplevel?.toplevels[lastFocused].activate(); appToplevel?.toplevels[lastFocused].activate();
root.visibilities.dock = false; root.screenState.dock = false;
} }
} }
+3 -3
View File
@@ -12,8 +12,8 @@ Item {
property real offsetScale: shouldBeActive ? 0 : 1 property real offsetScale: shouldBeActive ? 0 : 1
required property var panels required property var panels
required property ShellScreen screen required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.dock && Config.dock.enable readonly property bool shouldBeActive: screenState.dock && Config.dock.enable
required property PersistentProperties visibilities required property ScreenState screenState
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
@@ -39,7 +39,7 @@ Item {
sourceComponent: Content { sourceComponent: Content {
panels: root.panels panels: root.panels
screen: root.screen screen: root.screen
visibilities: root.visibilities screenState: root.screenState
} }
} }
} }
+10 -12
View File
@@ -12,7 +12,7 @@ Item {
readonly property var colors1: ["#ef4444", "#f97316", "#eab308", "#22c55e", "#06b6d4"] readonly property var colors1: ["#ef4444", "#f97316", "#eab308", "#22c55e", "#06b6d4"]
readonly property var colors2: ["#3b82f6", "#a855f7", "#ec4899", "#ffffff", "#000000"] readonly property var colors2: ["#3b82f6", "#a855f7", "#ec4899", "#ffffff", "#000000"]
required property var drawing required property var drawing
required property var visibilities required property ScreenState screenState
required property Wrapper wrapper required property Wrapper wrapper
function syncFromPenColor() { function syncFromPenColor() {
@@ -20,17 +20,17 @@ Item {
return; return;
if (!saturationSlider.pressed) if (!saturationSlider.pressed)
saturationSlider.value = drawing.drawingState.penColor.hsvSaturation; saturationSlider.value = drawing.screenState.penColor.hsvSaturation;
if (!brightnessSlider.pressed) if (!brightnessSlider.pressed)
brightnessSlider.value = drawing.drawingState.penColor.hsvValue; brightnessSlider.value = drawing.screenState.penColor.hsvValue;
} }
function updatePenColorFromHsv() { function updatePenColorFromHsv() {
if (!drawing) if (!drawing)
return; return;
drawing.drawingState.penColor = Qt.hsva(huePicker.currentHue, saturationSlider.value, brightnessSlider.value, drawing.drawingState.penColor.a); drawing.screenState.penColor = Qt.hsva(huePicker.currentHue, saturationSlider.value, brightnessSlider.value, drawing.screenState.penColor.a);
} }
implicitHeight: column.height + Tokens.padding.small * 2 implicitHeight: column.height + Tokens.padding.small * 2
@@ -43,7 +43,7 @@ Item {
root.syncFromPenColor(); root.syncFromPenColor();
} }
target: root.drawing.drawingState target: root.drawing.screenState
} }
Column { Column {
@@ -133,9 +133,9 @@ Item {
multiplier: 1 multiplier: 1
orientation: Qt.Horizontal orientation: Qt.Horizontal
to: 45 to: 45
value: root.drawing.drawingState.penWidth value: root.drawing.screenState.penWidth
onMoved: root.drawing.drawingState.penWidth = value onMoved: root.drawing.screenState.penWidth = value
} }
ButtonRow { ButtonRow {
@@ -145,7 +145,6 @@ Item {
IconTextButton { IconTextButton {
fillWidth: true fillWidth: true
font.pointSize: Tokens.font.size.normal
icon: "close" icon: "close"
inactiveColor: Colors.palette.m3error inactiveColor: Colors.palette.m3error
inactiveOnColor: Colors.palette.m3onError inactiveOnColor: Colors.palette.m3onError
@@ -153,12 +152,11 @@ Item {
shapeMorph: true shapeMorph: true
text: "Exit" text: "Exit"
onClicked: root.visibilities.isDrawing = false onClicked: root.screenState.isDrawing = false
} }
IconTextButton { IconTextButton {
fillWidth: true fillWidth: true
font.pointSize: Tokens.font.size.normal
icon: "ink_eraser" icon: "ink_eraser"
inactiveColor: Colors.layer(Colors.palette.m3surfaceContainerHighest, 2) inactiveColor: Colors.layer(Colors.palette.m3surfaceContainerHighest, 2)
inactiveOnColor: Colors.palette.m3onSurfaceVariant inactiveOnColor: Colors.palette.m3onSurfaceVariant
@@ -193,7 +191,7 @@ Item {
required property ButtonRow row required property ButtonRow row
fillWidth: false fillWidth: false
font.pointSize: Tokens.font.size.normal font: Tokens.font.body.medium
icon: "" icon: ""
implicitHeight: buttonSize implicitHeight: buttonSize
implicitWidth: buttonSize implicitWidth: buttonSize
@@ -203,6 +201,6 @@ Item {
shapeMorph: true shapeMorph: true
shapeMorphExpansion: pressed ? 12 : 0 shapeMorphExpansion: pressed ? 12 : 0
onClicked: root.drawing.drawingState.penColor = modelData onClicked: root.drawing.screenState.penColor = modelData
} }
} }
+6 -8
View File
@@ -15,8 +15,8 @@ Item {
property real offsetScale: shouldBeActive ? 0 : 1 property real offsetScale: shouldBeActive ? 0 : 1
property bool pinned: false property bool pinned: false
required property ShellScreen screen required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.isDrawing readonly property bool shouldBeActive: screenState.isDrawing
required property var visibilities required property ScreenState screenState
function collapse(): void { function collapse(): void {
if (pinned) if (pinned)
@@ -74,11 +74,10 @@ Item {
opacity: root.expanded ? 0 : 1 opacity: root.expanded ? 0 : 1
Behavior on opacity { Behavior on opacity {
Anim { Anim {}
}
} }
sourceComponent: MaterialIcon { sourceComponent: MaterialIcon {
font.pointSize: Tokens.font.size.larger fontStyle: Tokens.font.icon.medium
text: "arrow_forward_ios" text: "arrow_forward_ios"
} }
} }
@@ -93,12 +92,11 @@ Item {
opacity: root.expanded ? 1 : 0 opacity: root.expanded ? 1 : 0
Behavior on opacity { Behavior on opacity {
Anim { Anim {}
}
} }
sourceComponent: Content { sourceComponent: Content {
drawing: root.drawing drawing: root.drawing
visibilities: root.visibilities screenState: root.screenState
wrapper: root wrapper: root
} }
} }
+5 -5
View File
@@ -15,7 +15,7 @@ CustomListView {
property string displayText property string displayText
readonly property string requestedState: stateForText(search.text) readonly property string requestedState: stateForText(search.text)
required property SearchBar search required property SearchBar search
required property PersistentProperties visibilities required property ScreenState screenState
function resultsForText(text: string): var { function resultsForText(text: string): var {
switch (stateForText(text)) { switch (stateForText(text)) {
@@ -44,7 +44,7 @@ CustomListView {
} }
function syncDisplayText(): void { function syncDisplayText(): void {
if (visibilities.launcher && requestedState === displayState) if (screenState.launcher && requestedState === displayState)
displayText = search.text; displayText = search.text;
} }
@@ -55,7 +55,7 @@ CustomListView {
preferredHighlightBegin: 0 preferredHighlightBegin: 0
preferredHighlightEnd: height preferredHighlightEnd: height
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
state: visibilities.launcher ? requestedState : displayState state: screenState.launcher ? requestedState : displayState
verticalLayoutDirection: ListView.BottomToTop verticalLayoutDirection: ListView.BottomToTop
CustomScrollBar.vertical: CustomScrollBar { CustomScrollBar.vertical: CustomScrollBar {
@@ -236,7 +236,7 @@ CustomListView {
id: appItem id: appItem
AppItem { AppItem {
visibilities: root.visibilities screenState: root.screenState
} }
} }
@@ -277,6 +277,6 @@ CustomListView {
root.syncDisplayText(); root.syncDisplayText();
} }
target: root.visibilities target: root.screenState
} }
} }
+3 -3
View File
@@ -13,14 +13,14 @@ CarouselView {
readonly property var screen: QsWindow.window?.screen readonly property var screen: QsWindow.window?.screen
required property SearchBar search required property SearchBar search
readonly property var values: Wallpapers.query(root.search.text.split(" ").slice(1).join(" ")) readonly property var values: Wallpapers.query(root.search.text.split(" ").slice(1).join(" "))
required property var visibilities required property ScreenState screenState
currentIndex: { currentIndex: {
const idx = Wallpapers.list.findIndex(w => w.path === Wallpapers.actualCurrent); const idx = Wallpapers.list.findIndex(w => w.path === Wallpapers.actualCurrent);
return idx >= 0 ? idx : 0; return idx >= 0 ? idx : 0;
} }
delegateProperties: ({ delegateProperties: ({
visibilities: root.visibilities screenState: root.screenState
}) })
focalWidth: Config.launcher.sizes.wallpaperWidth focalWidth: Config.launcher.sizes.wallpaperWidth
glideDuration: 250 glideDuration: 250
@@ -32,7 +32,7 @@ CarouselView {
return 0; return 0;
const barMargins = Config.bar.border * 2; const barMargins = Config.bar.border * 2;
let margins = Math.round(screen.width / 6); let margins = Math.round(screen.width / 6);
if (visibilities.utilities || visibilities.sidebar) if (screenState.utilities || screenState.sidebar)
margins = Config.sidebar.sizes.width; margins = Config.sidebar.sizes.width;
return screen.width - (barMargins + margins) * 2 - Config.bar.rounding * 4; return screen.width - (barMargins + margins) * 2 - Config.bar.rounding * 4;
} }
+7 -7
View File
@@ -15,7 +15,7 @@ Item {
readonly property int padding: Tokens.padding.largeIncreased / 4 readonly property int padding: Tokens.padding.largeIncreased / 4
required property var panels required property var panels
readonly property int rounding: Tokens.rounding.largeIncreased readonly property int rounding: Tokens.rounding.largeIncreased
required property PersistentProperties visibilities required property ScreenState screenState
implicitHeight: search.height + listWrapper.height + padding * 2 implicitHeight: search.height + listWrapper.height + padding * 2
implicitWidth: listWrapper.width + padding * 2 implicitWidth: listWrapper.width + padding * 2
@@ -38,7 +38,7 @@ Item {
panels: root.panels panels: root.panels
rounding: root.rounding rounding: root.rounding
search: search search: search
visibilities: root.visibilities screenState: root.screenState
} }
} }
@@ -55,7 +55,7 @@ Item {
Component.onCompleted: forceActiveFocus() Component.onCompleted: forceActiveFocus()
Keys.onDownPressed: list.currentList?.decrementCurrentIndex() Keys.onDownPressed: list.currentList?.decrementCurrentIndex()
Keys.onEscapePressed: root.visibilities.launcher = false Keys.onEscapePressed: root.screenState.launcher = false
Keys.onPressed: event => { Keys.onPressed: event => {
if (event.modifiers & Qt.ControlModifier) { if (event.modifiers & Qt.ControlModifier) {
if (event.key === Qt.Key_J) { if (event.key === Qt.Key_J) {
@@ -81,7 +81,7 @@ Item {
if (Colors.scheme === "dynamic" && currentItem.modelData.path !== Wallpapers.actualCurrent) if (Colors.scheme === "dynamic" && currentItem.modelData.path !== Wallpapers.actualCurrent)
Wallpapers.previewColorLock = true; Wallpapers.previewColorLock = true;
Wallpapers.setWallpaper(currentItem.modelData.path); Wallpapers.setWallpaper(currentItem.modelData.path);
root.visibilities.launcher = false; root.screenState.launcher = false;
} else if (text.startsWith(Config.launcher.actionPrefix)) { } else if (text.startsWith(Config.launcher.actionPrefix)) {
if (text.startsWith(`${Config.launcher.actionPrefix}calc `)) if (text.startsWith(`${Config.launcher.actionPrefix}calc `))
currentItem.onClicked(); currentItem.onClicked();
@@ -89,18 +89,18 @@ Item {
currentItem.modelData.onClicked(list.currentList); currentItem.modelData.onClicked(list.currentList);
} else { } else {
Apps.launch(currentItem.modelData); Apps.launch(currentItem.modelData);
root.visibilities.launcher = false; root.screenState.launcher = false;
} }
} }
} }
Connections { Connections {
function onLauncherChanged(): void { function onLauncherChanged(): void {
if (!root.visibilities.launcher) if (!root.screenState.launcher)
search.text = ""; search.text = "";
} }
target: root.visibilities target: root.screenState
} }
} }
} }
+11 -15
View File
@@ -20,7 +20,7 @@ Item {
required property int rounding required property int rounding
required property SearchBar search required property SearchBar search
readonly property bool showWallpapers: search.text.startsWith(`${Config.launcher.actionPrefix}wallpaper `) readonly property bool showWallpapers: search.text.startsWith(`${Config.launcher.actionPrefix}wallpaper `)
required property PersistentProperties visibilities required property ScreenState screenState
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -28,7 +28,7 @@ Item {
state: showWallpapers ? "wallpapers" : "apps" state: showWallpapers ? "wallpapers" : "apps"
Behavior on implicitHeight { Behavior on implicitHeight {
enabled: root.visibilities.launcher enabled: root.screenState.launcher
Anim { Anim {
duration: Tokens.anim.durations.small duration: Tokens.anim.durations.small
@@ -36,7 +36,7 @@ Item {
} }
} }
Behavior on implicitWidth { Behavior on implicitWidth {
enabled: root.visibilities.launcher enabled: root.screenState.launcher
Anim { Anim {
duration: Tokens.anim.durations.small duration: Tokens.anim.durations.small
@@ -53,8 +53,7 @@ Item {
to: 0 to: 0
} }
PropertyAction { PropertyAction {}
}
Anim { Anim {
duration: Tokens.anim.durations.small duration: Tokens.anim.durations.small
@@ -99,7 +98,7 @@ Item {
sourceComponent: AppList { sourceComponent: AppList {
search: root.search search: root.search
visibilities: root.visibilities screenState: root.screenState
} }
} }
@@ -115,7 +114,7 @@ Item {
sourceComponent: CarouselView { sourceComponent: CarouselView {
panels: root.panels panels: root.panels
search: root.search search: root.search
visibilities: root.visibilities screenState: root.screenState
} }
} }
@@ -130,18 +129,16 @@ Item {
spacing: Tokens.spacing.medium spacing: Tokens.spacing.medium
Behavior on opacity { Behavior on opacity {
Anim { Anim {}
}
} }
Behavior on scale { Behavior on scale {
Anim { Anim {}
}
} }
MaterialIcon { MaterialIcon {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: Colors.palette.m3onSurfaceVariant color: Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.extraLarge fontStyle: Tokens.font.icon.extraLarge
text: root.state === "wallpapers" ? "wallpaper_slideshow" : "manage_search" text: root.state === "wallpapers" ? "wallpaper_slideshow" : "manage_search"
} }
@@ -150,14 +147,13 @@ Item {
CustomText { CustomText {
color: Colors.palette.m3onSurfaceVariant color: Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.larger font: Tokens.font.body.builders.large.weight(Font.Medium).build()
font.weight: 500
text: root.state === "wallpapers" ? qsTr("No wallpapers found") : qsTr("No results") text: root.state === "wallpapers" ? qsTr("No wallpapers found") : qsTr("No results")
} }
CustomText { CustomText {
color: Colors.palette.m3onSurfaceVariant color: Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.normal font: Tokens.font.body.medium
text: root.state === "wallpapers" && Wallpapers.list.length === 0 ? qsTr("Try putting some wallpapers in %1").arg(Paths.shortenHome(Paths.wallsdir)) : qsTr("Try searching for something else") text: root.state === "wallpapers" && Wallpapers.list.length === 0 ? qsTr("Try putting some wallpapers in %1").arg(Paths.shortenHome(Paths.wallsdir)) : qsTr("Try searching for something else")
} }
} }
+2 -3
View File
@@ -33,7 +33,7 @@ Item {
id: icon id: icon
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
font.pointSize: Tokens.font.size.extraLarge fontStyle: Tokens.font.icon.large
text: root.modelData?.icon ?? "" text: root.modelData?.icon ?? ""
} }
@@ -47,7 +47,6 @@ Item {
CustomText { CustomText {
id: name id: name
font.pointSize: Tokens.font.size.normal
text: root.modelData?.name ?? "" text: root.modelData?.name ?? ""
} }
@@ -57,7 +56,7 @@ Item {
anchors.top: name.bottom anchors.top: name.bottom
color: Colors.palette.m3outline color: Colors.palette.m3outline
elide: Text.ElideRight elide: Text.ElideRight
font.pointSize: Tokens.font.size.small font: Tokens.font.body.small
text: root.modelData?.desc ?? "" text: root.modelData?.desc ?? ""
width: root.width - icon.width - Tokens.rounding.medium * 2 width: root.width - icon.width - Tokens.rounding.medium * 2
} }
+3 -4
View File
@@ -12,7 +12,7 @@ Item {
id: root id: root
required property DesktopEntry modelData required property DesktopEntry modelData
required property PersistentProperties visibilities required property ScreenState screenState
anchors.left: parent?.left anchors.left: parent?.left
anchors.right: parent?.right anchors.right: parent?.right
@@ -23,7 +23,7 @@ Item {
onClicked: { onClicked: {
Apps.launch(root.modelData); Apps.launch(root.modelData);
root.visibilities.launcher = false; root.screenState.launcher = false;
} }
} }
@@ -51,7 +51,6 @@ Item {
CustomText { CustomText {
id: name id: name
font.pointSize: Tokens.font.size.normal
text: root.modelData?.name ?? "" text: root.modelData?.name ?? ""
} }
@@ -61,7 +60,7 @@ Item {
anchors.top: name.bottom anchors.top: name.bottom
color: Colors.palette.m3outline color: Colors.palette.m3outline
elide: Text.ElideRight elide: Text.ElideRight
font.pointSize: Tokens.font.size.small font: Tokens.font.body.small
text: (root.modelData?.comment || root.modelData?.genericName || root.modelData?.name) ?? "" text: (root.modelData?.comment || root.modelData?.genericName || root.modelData?.name) ?? ""
width: root.width - icon.width - Tokens.rounding.medium - Tokens.padding.medium * 2 width: root.width - icon.width - Tokens.rounding.medium - Tokens.padding.medium * 2
} }
+5 -5
View File
@@ -16,7 +16,7 @@ Item {
function onClicked(): void { function onClicked(): void {
Quickshell.execDetached(["wl-copy", Qalculator.eval(math, false)]); Quickshell.execDetached(["wl-copy", Qalculator.eval(math, false)]);
root.list.visibilities.launcher = false; root.list.screenState.launcher = false;
} }
anchors.left: parent?.left anchors.left: parent?.left
@@ -40,7 +40,7 @@ Item {
MaterialIcon { MaterialIcon {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
font.pointSize: Tokens.font.size.extraLarge fontStyle: Tokens.font.icon.large
text: "function" text: "function"
} }
@@ -81,7 +81,7 @@ Item {
onClicked: { onClicked: {
Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.terminal, "fish", "-C", `exec qalc -i '${root.math}'`]); Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.terminal, "fish", "-C", `exec qalc -i '${root.math}'`]);
root.list.visibilities.launcher = false; root.list.screenState.launcher = false;
} }
} }
@@ -92,7 +92,7 @@ Item {
anchors.rightMargin: Tokens.spacing.small anchors.rightMargin: Tokens.spacing.small
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: Colors.palette.m3onTertiary color: Colors.palette.m3onTertiary
font.pointSize: Tokens.font.size.normal font: Tokens.font.body.medium
opacity: stateLayer.containsMouse ? 1 : 0 opacity: stateLayer.containsMouse ? 1 : 0
text: qsTr("Open in calculator") text: qsTr("Open in calculator")
@@ -109,7 +109,7 @@ Item {
anchors.rightMargin: Tokens.padding.small anchors.rightMargin: Tokens.padding.small
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: Colors.palette.m3onTertiary color: Colors.palette.m3onTertiary
font.pointSize: Tokens.font.size.large fontStyle: Tokens.font.icon.medium
text: "open_in_new" text: "open_in_new"
} }
} }
+141
View File
@@ -0,0 +1,141 @@
import QtQuick
import Quickshell
import Quickshell.Widgets
import ZShell.Config
import qs.Components
import qs.Helpers
import qs.Modules.Launcher.Services
import qs.Services
Item {
id: root
required property var list
required property var modelData
anchors.left: parent?.left
anchors.right: parent?.right
implicitHeight: Config.launcher.sizes.itemHeight
function onClicked(): void {
root.list.screenState.launcher = false;
if (root.modelData?.path)
Files.launch(["xdg-open", root.modelData.path]);
}
StateLayer {
id: sLayer
radius: Tokens.rounding.medium
manualHoverOverride: openLocation.hovered
onClicked: {
root.onClicked();
}
}
IconButton {
id: openLocation
anchors.right: parent.right
anchors.rightMargin: Tokens.padding.medium
anchors.verticalCenter: parent.verticalCenter
icon: "open_in_new"
isRound: true
scale: sLayer.containsMouse || sLayer.manualHoverOverride ? 1 : 0.4
opacity: sLayer.containsMouse || sLayer.manualHoverOverride ? 1 : 0
visible: opacity > 0
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
Behavior on scale {
Anim {
type: Anim.DefaultEffects
}
}
onClicked: {
root.list.screenState.launcher = false;
if (root.modelData?.path)
Files.showInFolder(root.modelData.path);
}
}
Item {
anchors.fill: parent
anchors.leftMargin: Tokens.padding.small
anchors.margins: Tokens.padding.small
anchors.rightMargin: Tokens.padding.small
IconImage {
id: icon
anchors.verticalCenter: parent.verticalCenter
implicitSize: Tokens.font.icon.extraLarge.pointSize
source: Quickshell.iconPath(root.modelData?.icon) ?? ""
visible: !root.modelData?.isImage
}
Loader {
active: opacity > 0
anchors.verticalCenter: parent.verticalCenter
asynchronous: true
opacity: root.modelData?.isImage && image.status === Image.Loading ? 1 : 0
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
sourceComponent: LoadingIndicator {
color: Colors.palette.m3primaryContainer
implicitSize: Tokens.font.icon.extraLarge.pointSize
}
}
FadeImage {
id: image
property int implicitSize: Tokens.font.icon.extraLarge.pointSize
width: implicitSize
fillMode: Image.PreserveAspectFit
anchors.verticalCenter: parent.verticalCenter
source: root.modelData?.isImage ? root.modelData?.path ?? "" : ""
visible: root.modelData?.isImage
}
Item {
anchors.left: icon.right
anchors.leftMargin: Tokens.spacing.medium
anchors.verticalCenter: icon.verticalCenter
implicitHeight: name.implicitHeight + desc.implicitHeight
implicitWidth: parent.width - icon.width
CustomText {
id: name
font: Tokens.font.body.medium
text: root.modelData?.name ?? ""
elide: Text.ElideRight
width: root.width - icon.width - openLocation.implicitWidth - openLocation.anchors.rightMargin - Tokens.spacing.medium - Tokens.rounding.medium * 2
}
CustomText {
id: desc
anchors.top: name.bottom
color: Colors.palette.m3outline
elide: Text.ElideRight
font: Tokens.font.body.small
text: root.modelData?.path ?? ""
width: root.width - icon.width - openLocation.implicitWidth - openLocation.anchors.rightMargin - Tokens.spacing.medium - Tokens.rounding.medium * 2
}
}
}
}
+3 -4
View File
@@ -32,7 +32,7 @@ Item {
id: icon id: icon
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
font.pointSize: Tokens.font.size.extraLarge fontStyle: Tokens.font.icon.large
text: root.modelData?.icon ?? "" text: root.modelData?.icon ?? ""
} }
@@ -44,7 +44,6 @@ Item {
width: parent.width - icon.width - anchors.leftMargin - (current.active ? current.width + Tokens.spacing.medium : 0) width: parent.width - icon.width - anchors.leftMargin - (current.active ? current.width + Tokens.spacing.medium : 0)
CustomText { CustomText {
font.pointSize: Tokens.font.size.normal
text: root.modelData?.name ?? "" text: root.modelData?.name ?? ""
} }
@@ -53,7 +52,7 @@ Item {
anchors.right: parent.right anchors.right: parent.right
color: Colors.palette.m3outline color: Colors.palette.m3outline
elide: Text.ElideRight elide: Text.ElideRight
font.pointSize: Tokens.font.size.small font: Tokens.font.body.small
text: root.modelData?.description ?? "" text: root.modelData?.description ?? ""
} }
} }
@@ -67,7 +66,7 @@ Item {
sourceComponent: MaterialIcon { sourceComponent: MaterialIcon {
color: Colors.palette.m3onSurfaceVariant color: Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.large fontStyle: Tokens.font.icon.large
text: "check" text: "check"
} }
} }

Some files were not shown because too many files have changed in this diff Show More