feat: dashboard show on hover, media and weather tabs added
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,8 +17,12 @@ 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
|
property bool shouldBeActive
|
||||||
implicitWidth: Tokens.padding.extraSmall * 2
|
property bool inverted
|
||||||
|
|
||||||
|
enabled: !ShellState.forActive().isDrawing
|
||||||
|
implicitWidth: size === 1 ? 0 : isHorizontal ? 0 : Tokens.padding.extraSmall * 2
|
||||||
|
implicitHeight: size === 1 ? 0 : isHorizontal ? Tokens.padding.extraSmall * 2 : 0
|
||||||
|
|
||||||
contentItem: Item {
|
contentItem: Item {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.size.small
|
||||||
|
|
||||||
|
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
|
||||||
@@ -212,12 +217,10 @@ TextFieldBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Behavior on strokeColor {
|
Behavior on strokeColor {
|
||||||
CAnim {
|
CAnim {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Behavior on strokeWidth {
|
Behavior on strokeWidth {
|
||||||
Anim {
|
Anim {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PathLine {
|
PathLine {
|
||||||
@@ -292,8 +295,7 @@ TextFieldBase {
|
|||||||
implicitHeight: root.activeFocus ? 2 : 1
|
implicitHeight: root.activeFocus ? 2 : 1
|
||||||
|
|
||||||
Behavior on implicitHeight {
|
Behavior on implicitHeight {
|
||||||
Anim {
|
Anim {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ 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
|
import qs.Services
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
@@ -52,7 +52,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 {
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+75
-55
@@ -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,48 +76,60 @@ 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) {
|
// if (root.geometry.insetTop(root.borderThickness) > pressY) {
|
||||||
if (dragY > 20) {
|
// if (dragY > 20) {
|
||||||
root.visibilities.settings = true;
|
// root.screenState.settings = true;
|
||||||
root.singleGestureTriggered = true;
|
// root.singleGestureTriggered = true;
|
||||||
} else if (dragY < -20) {
|
// } else if (dragY < -20) {
|
||||||
root.visibilities.settings = false;
|
// root.screenState.settings = 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 (pressY > root.screen.height - root.geometry.insetBottom(root.borderThickness) && pressX < root.screen.width / 5 && dragY < -50)
|
||||||
root.visibilities.clipboard = true;
|
root.screenState.clipboard = 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 (!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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (root.inTopPanel(root.panels.dashboardWrapper, pressX, pressY) && root.withinPanelWidth(root.panels.dashboardWrapper, x, y)) {
|
||||||
|
if (dragY > Config.dashboard.dragThreshold) {
|
||||||
|
root.screenState.dashboard = true;
|
||||||
|
root.singleGestureTriggered = true;
|
||||||
|
} else if (dragY < -Config.dashboard.dragThreshold) {
|
||||||
|
root.screenState.dashboard = false;
|
||||||
|
root.singleGestureTriggered = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +139,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 +190,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 +205,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 +225,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 +243,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 +251,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 +266,16 @@ 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.popouts.hasCurrent = false;
|
root.popouts.hasCurrent = false;
|
||||||
} else {
|
} else {
|
||||||
root.dashboardShortcutActive = false;
|
root.dashboardShortcutActive = false;
|
||||||
@@ -263,12 +283,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 +296,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 +323,32 @@ 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.currentName.startsWith("audio") || root.popouts.currentName.startsWith("updates"))) {
|
||||||
root.popouts.hasCurrent = false;
|
root.popouts.hasCurrent = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.visibilities.resources)
|
if (root.screenState.resources)
|
||||||
root.visibilities.settings = false;
|
root.screenState.settings = 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-19
@@ -42,7 +42,7 @@ Item {
|
|||||||
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
|
required property ScreenState screenState
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ Item {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
screen: root.screen
|
screen: root.screen
|
||||||
sidebarOrSessionVisible: sidebar.visible
|
sidebarOrSessionVisible: sidebar.visible
|
||||||
visibilities: root.visibilities
|
screenState: root.screenState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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 {
|
||||||
@@ -122,7 +122,7 @@ Item {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
panels: root
|
panels: root
|
||||||
sidebarPanel: sidebar
|
sidebarPanel: sidebar
|
||||||
visibilities: root.visibilities
|
screenState: root.screenState
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@@ -142,7 +142,7 @@ Item {
|
|||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
popouts: popouts
|
popouts: popouts
|
||||||
sidebar: sidebar
|
sidebar: sidebar
|
||||||
visibilities: root.visibilities
|
screenState: root.screenState
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -150,13 +150,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.top: parent.top
|
||||||
anchors.left: root.geometry.barOnLeft ? parent.left : undefined
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
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 +167,7 @@ Item {
|
|||||||
id: dashboard
|
id: dashboard
|
||||||
|
|
||||||
offsetScale: dashboardWrapper.offsetScale
|
offsetScale: dashboardWrapper.offsetScale
|
||||||
position: root.geometry.position
|
screenState: root.screenState
|
||||||
visibilities: root.visibilities
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +178,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 +194,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 +205,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 +214,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-53
@@ -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,26 @@ 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))
|
active: root.screenState.dock || root.screenState.resources || root.screenState.launcher || root.screenState.sidebar || (!Config.dashboard.showOnHover && root.screenState.dashboard) || root.screenState.settings || root.screenState.clipboard || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu") && (!Config.bar.tray.showOnHover || (panels.popouts.current as StackView)?.depth > 1))
|
||||||
windows: [root]
|
windows: [root]
|
||||||
|
|
||||||
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 +331,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 {
|
||||||
@@ -389,7 +372,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 +382,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 +425,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+28
-7
@@ -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();
|
||||||
|
|||||||
+6
-5
@@ -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) {
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import qs.Services
|
|||||||
WidgetBase {
|
WidgetBase {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property color contentColor: visibilities.dashboard ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
readonly property color contentColor: screenState.dashboard ? Colors.palette.m3onPrimary : 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 +44,10 @@ 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
|
color: screenState.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
|
||||||
@@ -151,7 +151,7 @@ WidgetBase {
|
|||||||
color: root.contentColor
|
color: root.contentColor
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.visibilities.dashboard = !root.visibilities.dashboard;
|
root.screenState.dashboard = !root.screenState.dashboard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,17 +11,17 @@ 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
|
font.pointSize: Tokens.font.size.larger
|
||||||
text: NotifServer.list.length ? "\uf4fe" : "\ue7f4"
|
text: NotifServer.list.length ? "\uf4fe" : "\ue7f4"
|
||||||
|
|
||||||
@@ -36,10 +36,10 @@ WidgetBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+137
-38
@@ -5,78 +5,177 @@ 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 * 2
|
||||||
|
|
||||||
|
required property ScreenState screenState
|
||||||
|
|
||||||
|
readonly property var dashboardTabs: {
|
||||||
|
const allTabs = [
|
||||||
|
{
|
||||||
|
component: dashComponent,
|
||||||
|
iconName: "dashboard",
|
||||||
|
text: qsTr("Dashboard"),
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: weatherComponent,
|
||||||
|
iconName: "cloud",
|
||||||
|
text: qsTr("Weather"),
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: mediaComponent,
|
||||||
|
iconName: "queue_music",
|
||||||
|
text: qsTr("Media"),
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// component: testingComponent,
|
||||||
|
// iconName: "speed",
|
||||||
|
// text: qsTr("Testing"),
|
||||||
|
// enabled: true
|
||||||
|
// },
|
||||||
|
];
|
||||||
|
return allTabs.filter(tab => tab.enabled);
|
||||||
|
}
|
||||||
|
|
||||||
implicitHeight: nonAnimHeight
|
|
||||||
implicitWidth: nonAnimWidth
|
implicitWidth: nonAnimWidth
|
||||||
|
implicitHeight: nonAnimHeight
|
||||||
|
|
||||||
Behavior on implicitHeight {
|
Behavior on implicitHeight {
|
||||||
Anim {
|
Anim {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Behavior on implicitWidth {
|
Behavior on implicitWidth {
|
||||||
Anim {
|
Anim {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Tabs {
|
||||||
|
id: tabs
|
||||||
|
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.topMargin: ZUtils.clamp(anchors.margins - Config.bar.border, 0, anchors.margins)
|
||||||
|
anchors.margins: Tokens.padding.large
|
||||||
|
|
||||||
|
nonAnimWidth: root.nonAnimWidth - anchors.margins * 2
|
||||||
|
screenState: root.screenState
|
||||||
|
tabs: root.dashboardTabs
|
||||||
}
|
}
|
||||||
|
|
||||||
ClippingRectangle {
|
ClippingRectangle {
|
||||||
id: viewWrapper
|
id: viewWrapper
|
||||||
|
|
||||||
anchors.bottom: parent.bottom
|
anchors.top: tabs.bottom
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.margins: Tokens.padding.small
|
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.top: parent.top
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.margins: Tokens.padding.small
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
radius: Tokens.rounding.extraLarge - anchors.margins
|
radius: Tokens.rounding.extraLarge - anchors.margins
|
||||||
|
|
||||||
Item {
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: weatherComponent
|
||||||
|
|
||||||
|
WeatherTab {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: mediaComponent
|
||||||
|
|
||||||
|
Media {
|
||||||
|
screenState: root.screenState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Component {
|
||||||
|
// id: testingComponent
|
||||||
|
//
|
||||||
|
// WeatherTest {}
|
||||||
|
// }
|
||||||
|
|
||||||
|
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);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
property int radius: Tokens.rounding.medium
|
property int radius: Tokens.rounding.medium
|
||||||
required property PersistentProperties visibilities
|
required property ScreenState screenState
|
||||||
|
|
||||||
columnSpacing: Tokens.spacing.small
|
columnSpacing: Tokens.spacing.small
|
||||||
rowSpacing: Tokens.spacing.small
|
rowSpacing: Tokens.spacing.small
|
||||||
@@ -61,7 +60,7 @@ GridLayout {
|
|||||||
User {
|
User {
|
||||||
id: user
|
id: user
|
||||||
|
|
||||||
dashState: root.dashState
|
screenState: root.screenState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,8 +71,7 @@ GridLayout {
|
|||||||
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
implicitWidth: 1000
|
||||||
|
implicitHeight: 320
|
||||||
|
|
||||||
|
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"
|
||||||
|
font.pointSize: Tokens.font.size.large
|
||||||
|
color: Colors.palette.m3onPrimaryContainer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: qsTr("Nothing playing")
|
||||||
|
font.pointSize: Tokens.font.size.normal
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
text: qsTr("Play something for it to show up here!")
|
||||||
|
color: Colors.palette.m3onSurfaceVariant
|
||||||
|
font.pointSize: Tokens.font.size.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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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.pointSize: Tokens.font.size.large
|
||||||
|
elide: Text.ElideRight
|
||||||
|
animate: true
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: Players.active?.trackArtist || qsTr("Unknown artist")
|
||||||
|
color: Colors.palette.m3onSurfaceVariant
|
||||||
|
font.pointSize: Tokens.font.size.normal
|
||||||
|
elide: Text.ElideRight
|
||||||
|
animate: true
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: Players.active?.trackAlbum || qsTr("Unknown album")
|
||||||
|
color: Colors.palette.m3secondary
|
||||||
|
font.pointSize: Tokens.font.size.normal
|
||||||
|
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.pointSize: Tokens.font.size.normal
|
||||||
|
}
|
||||||
|
|
||||||
|
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.pointSize: Tokens.font.size.large
|
||||||
|
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.pointSize: Tokens.font.size.extraLarge
|
||||||
|
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.pointSize: Tokens.font.size.extraLarge
|
||||||
|
enabled: Players.active?.canTogglePlaying
|
||||||
|
onClicked: Players.active?.togglePlaying()
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButton {
|
||||||
|
id: nextBtn
|
||||||
|
|
||||||
|
type: IconButton.Tonal
|
||||||
|
icon: "skip_next"
|
||||||
|
isRound: true
|
||||||
|
shapeMorph: true
|
||||||
|
font.pointSize: Tokens.font.size.extraLarge
|
||||||
|
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.pointSize: Tokens.font.size.large
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.pointSize: Tokens.font.size.normal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
font.pointSize: Tokens.font.size.extraLarge
|
||||||
|
color: Colors.palette.m3outline
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
text: qsTr("No lyrics found")
|
||||||
|
color: Colors.palette.m3outline
|
||||||
|
font.pointSize: Tokens.font.size.normal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.pointSize: Tokens.font.size.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"
|
||||||
|
font.pointSize: Tokens.font.size.large
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: qsTr("Lyrics")
|
||||||
|
font.pointSize: Tokens.font.size.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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.pointSize: Tokens.font.size.normal
|
||||||
|
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"
|
||||||
|
font.pointSize: Tokens.font.size.large
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+90
-144
@@ -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
|
font.pointSize: Tokens.font.size.large
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
font.pointSize: Tokens.font.size.large
|
||||||
|
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"
|
||||||
|
font.pointSize: Tokens.font.size.large
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
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.pointSize: Tokens.font.size.large
|
||||||
|
color: Colors.palette.m3onSurface
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
text: new Date().toLocaleDateString(Qt.locale("en_US"), "dddd, MMMM d")
|
||||||
|
font.pointSize: Tokens.font.size.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.pointSize: Tokens.font.size.normal
|
||||||
|
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: Tokens.rounding.large
|
||||||
|
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.pointSize: Tokens.font.size.small
|
||||||
|
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.pointSize: Tokens.font.size.small
|
||||||
|
opacity: 0.7
|
||||||
|
color: Colors.palette.m3onSurfaceVariant
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: forecastItem.modelData.icon
|
||||||
|
font.pointSize: Tokens.font.size.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.pointSize: Tokens.font.size.small
|
||||||
|
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
|
||||||
|
font.pointSize: Tokens.font.size.large
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
text: detailRoot.label
|
||||||
|
font.pointSize: Tokens.font.size.small
|
||||||
|
opacity: 0.7
|
||||||
|
horizontalAlignment: Text.AlignLeft
|
||||||
|
}
|
||||||
|
CustomText {
|
||||||
|
text: detailRoot.value
|
||||||
|
font.pointSize: Tokens.font.size.small
|
||||||
|
horizontalAlignment: Text.AlignLeft
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component WeatherStat: Row {
|
||||||
|
id: weatherStat
|
||||||
|
|
||||||
|
property string icon
|
||||||
|
property string label
|
||||||
|
property string value
|
||||||
|
property color colour
|
||||||
|
|
||||||
|
spacing: Tokens.spacing.small
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
text: weatherStat.icon
|
||||||
|
font.pointSize: Tokens.font.size.extraLarge
|
||||||
|
color: weatherStat.colour
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
CustomText {
|
||||||
|
text: weatherStat.label
|
||||||
|
font.pointSize: Tokens.font.size.small
|
||||||
|
color: Colors.palette.m3onSurfaceVariant
|
||||||
|
}
|
||||||
|
CustomText {
|
||||||
|
text: weatherStat.value
|
||||||
|
font.pointSize: Tokens.font.size.small
|
||||||
|
color: Colors.palette.m3onSurface
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ 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
|
||||||
@@ -97,8 +98,8 @@ Item {
|
|||||||
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -153,7 +153,7 @@ Item {
|
|||||||
shapeMorph: true
|
shapeMorph: true
|
||||||
text: "Exit"
|
text: "Exit"
|
||||||
|
|
||||||
onClicked: root.visibilities.isDrawing = false
|
onClicked: root.screenState.isDrawing = false
|
||||||
}
|
}
|
||||||
|
|
||||||
IconTextButton {
|
IconTextButton {
|
||||||
@@ -203,6 +203,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -98,7 +98,7 @@ Item {
|
|||||||
}
|
}
|
||||||
sourceComponent: Content {
|
sourceComponent: Content {
|
||||||
drawing: root.drawing
|
drawing: root.drawing
|
||||||
visibilities: root.visibilities
|
screenState: root.screenState
|
||||||
wrapper: root
|
wrapper: root
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -99,7 +99,7 @@ Item {
|
|||||||
|
|
||||||
sourceComponent: AppList {
|
sourceComponent: AppList {
|
||||||
search: root.search
|
search: root.search
|
||||||
visibilities: root.visibilities
|
screenState: root.screenState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ Item {
|
|||||||
sourceComponent: CarouselView {
|
sourceComponent: CarouselView {
|
||||||
panels: root.panels
|
panels: root.panels
|
||||||
search: root.search
|
search: root.search
|
||||||
visibilities: root.visibilities
|
screenState: root.screenState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.size.extraLarge
|
||||||
|
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.size.extraLarge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FadeImage {
|
||||||
|
id: image
|
||||||
|
|
||||||
|
property int implicitSize: Tokens.font.size.extraLarge
|
||||||
|
|
||||||
|
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.pointSize: Tokens.font.size.normal
|
||||||
|
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.pointSize: Tokens.font.size.small
|
||||||
|
text: root.modelData?.path ?? ""
|
||||||
|
width: root.width - icon.width - openLocation.implicitWidth - openLocation.anchors.rightMargin - Tokens.spacing.medium - Tokens.rounding.medium * 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ Item {
|
|||||||
required property var modelData // FileSystemEntry-like {path, relativePath}
|
required property var modelData // FileSystemEntry-like {path, relativePath}
|
||||||
required property real tileHeight
|
required property real tileHeight
|
||||||
required property real tileWidth
|
required property real tileWidth
|
||||||
required property PersistentProperties visibilities
|
required property ScreenState screenState
|
||||||
|
|
||||||
signal requestActivate
|
signal requestActivate
|
||||||
|
|
||||||
|
|||||||
@@ -42,10 +42,10 @@ Searcher {
|
|||||||
if (command[0] === "autocomplete" && command.length > 1) {
|
if (command[0] === "autocomplete" && command.length > 1) {
|
||||||
list.search.text = `${Config.launcher.actionPrefix}${command[1]} `;
|
list.search.text = `${Config.launcher.actionPrefix}${command[1]} `;
|
||||||
} else if (command[0] === "setMode" && command.length > 1) {
|
} else if (command[0] === "setMode" && command.length > 1) {
|
||||||
list.visibilities.launcher = false;
|
list.screenState.launcher = false;
|
||||||
Colors.setMode(command[1]);
|
Colors.setMode(command[1]);
|
||||||
} else {
|
} else {
|
||||||
list.visibilities.launcher = false;
|
list.screenState.launcher = false;
|
||||||
Quickshell.execDetached(command);
|
Quickshell.execDetached(command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ Searcher {
|
|||||||
required property string variant
|
required property string variant
|
||||||
|
|
||||||
function onClicked(list: AppList): void {
|
function onClicked(list: AppList): void {
|
||||||
list.visibilities.launcher = false;
|
list.screenState.launcher = false;
|
||||||
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--scheme", variant]);
|
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--scheme", variant]);
|
||||||
Config.colors.schemeType = variant;
|
Config.colors.schemeType = variant;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Item {
|
|||||||
property int contentHeight
|
property int contentHeight
|
||||||
readonly property real maxHeight: {
|
readonly property real maxHeight: {
|
||||||
let max = screen.height - Tokens.spacing.largeIncreased * 2;
|
let max = screen.height - Tokens.spacing.largeIncreased * 2;
|
||||||
if (visibilities.resources && panels.resourcesWrapper.x + panels.resourcesWrapper.width > root.x)
|
if (screenState.resources && panels.resourcesWrapper.x + panels.resourcesWrapper.width > root.x)
|
||||||
max -= panels.resources.nonAnimHeight;
|
max -= panels.resources.nonAnimHeight;
|
||||||
if (panels.popouts.hasCurrent)
|
if (panels.popouts.hasCurrent)
|
||||||
if (panels.popouts.current?.x + panels.popouts.current?.width > root.x && panels.popouts.current?.x < root.x + root.width)
|
if (panels.popouts.current?.x + panels.popouts.current?.width > root.x && panels.popouts.current?.x < root.x + root.width)
|
||||||
@@ -22,8 +22,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.launcher
|
readonly property bool shouldBeActive: screenState.launcher
|
||||||
required property PersistentProperties visibilities
|
required property ScreenState screenState
|
||||||
|
|
||||||
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
|
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
|
||||||
implicitHeight: content.implicitHeight
|
implicitHeight: content.implicitHeight
|
||||||
@@ -57,7 +57,7 @@ Item {
|
|||||||
sourceComponent: Content {
|
sourceComponent: Content {
|
||||||
maxHeight: root.maxHeight
|
maxHeight: root.maxHeight
|
||||||
panels: root.panels
|
panels: root.panels
|
||||||
visibilities: root.visibilities
|
screenState: root.screenState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ CustomRect {
|
|||||||
|
|
||||||
MaterialShape {
|
MaterialShape {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
color: Qt.alpha(Colors.palette.m3primary, hour.index === 0 ? 1 : 0)
|
color: Qt.alpha(Colors.palette.m3primary, hour.index === Weather.currentHourIndex ? 1 : 0)
|
||||||
implicitSize: temp.implicitHeight + Tokens.padding.small * 2
|
implicitSize: temp.implicitHeight + Tokens.padding.small * 2
|
||||||
shape: MaterialShape.Cookie4Sided
|
shape: MaterialShape.Cookie4Sided
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ CustomRect {
|
|||||||
id: temp
|
id: temp
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
color: hour.index === 0 ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
color: hour.index === Weather.currentHourIndex ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
|
||||||
font.pointSize: Tokens.font.size.normal
|
font.pointSize: Tokens.font.size.normal
|
||||||
text: Weather.formatTemp(hour.cond.tempC).slice(0, -1) // Remove C/F
|
text: Weather.formatTemp(hour.cond.tempC).slice(0, -1) // Remove C/F
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ Item {
|
|||||||
|
|
||||||
readonly property int padding: Tokens.padding.smaller
|
readonly property int padding: Tokens.padding.smaller
|
||||||
required property Item panels
|
required property Item panels
|
||||||
required property PersistentProperties visibilities
|
required property ScreenState screenState
|
||||||
|
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
@@ -27,16 +27,16 @@ Item {
|
|||||||
for (let i = 0; i < count; i++)
|
for (let i = 0; i < count; i++)
|
||||||
height += (list.itemAtIndex(i) as NotifWrapper)?.nonAnimHeight ?? 0;
|
height += (list.itemAtIndex(i) as NotifWrapper)?.nonAnimHeight ?? 0;
|
||||||
|
|
||||||
if (panels.popouts.hasCurrent && (panels.popouts.currentCenter + (panels.popouts.current?.width / 2)) > panels.notifications.x || visibilities.dashboard)
|
if (panels.popouts.hasCurrent && (panels.popouts.currentCenter + (panels.popouts.current?.width / 2)) > panels.notifications.x || screenState.dashboard)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (visibilities.osd) {
|
if (screenState.osd) {
|
||||||
const h = panels.osd.y - Tokens.spacing.small * 2 - padding * 2;
|
const h = panels.osd.y - Tokens.spacing.small * 2 - padding * 2;
|
||||||
if (height > h)
|
if (height > h)
|
||||||
height = h;
|
height = h;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (visibilities.session) {
|
if (screenState.session) {
|
||||||
const h = panels.session.y - Tokens.spacing.small * 2 - padding * 2;
|
const h = panels.session.y - Tokens.spacing.small * 2 - padding * 2;
|
||||||
if (height > h)
|
if (height > h)
|
||||||
height = h;
|
height = h;
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import qs.Services
|
|||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property Props props
|
required property ScreenState screenState
|
||||||
required property var visibilities
|
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: layout
|
id: layout
|
||||||
@@ -23,8 +22,7 @@ Item {
|
|||||||
radius: Tokens.rounding.small
|
radius: Tokens.rounding.small
|
||||||
|
|
||||||
NotifDock {
|
NotifDock {
|
||||||
props: root.props
|
screenState: root.screenState
|
||||||
visibilities: root.visibilities
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ CustomRect {
|
|||||||
required property bool expanded
|
required property bool expanded
|
||||||
required property NotifServer.Notif modelData
|
required property NotifServer.Notif modelData
|
||||||
readonly property real nonAnimHeight: expanded ? summary.implicitHeight + expandedContent.implicitHeight + expandedContent.anchors.topMargin + Tokens.padding.medium * 2 : summaryHeightMetrics.height
|
readonly property real nonAnimHeight: expanded ? summary.implicitHeight + expandedContent.implicitHeight + expandedContent.anchors.topMargin + Tokens.padding.medium * 2 : summaryHeightMetrics.height
|
||||||
required property Props props
|
required property ScreenState screenState
|
||||||
required property PersistentProperties visibilities
|
|
||||||
|
|
||||||
color: {
|
color: {
|
||||||
const c = root.modelData?.urgency === "critical" ? Colors.palette.m3secondaryContainer : Colors.layer(Colors.palette.m3surfaceContainerHigh, 2);
|
const c = root.modelData?.urgency === "critical" ? Colors.palette.m3secondaryContainer : Colors.layer(Colors.palette.m3surfaceContainerHigh, 2);
|
||||||
@@ -137,7 +136,7 @@ CustomRect {
|
|||||||
|
|
||||||
onLinkActivated: link => {
|
onLinkActivated: link => {
|
||||||
Qt.openUrlExternally(link);
|
Qt.openUrlExternally(link);
|
||||||
root.visibilities.sidebar = false;
|
root.screenState.sidebar = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ Item {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property int notifCount: NotifServer.list.reduce((acc, n) => n.closed ? acc : acc + 1, 0)
|
readonly property int notifCount: NotifServer.list.reduce((acc, n) => n.closed ? acc : acc + 1, 0)
|
||||||
required property Props props
|
required property ScreenState screenState
|
||||||
required property var visibilities
|
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Tokens.padding.small
|
anchors.margins: Tokens.padding.small
|
||||||
@@ -120,8 +119,7 @@ Item {
|
|||||||
id: notifList
|
id: notifList
|
||||||
|
|
||||||
container: view
|
container: view
|
||||||
props: root.props
|
screenState: root.screenState
|
||||||
visibilities: root.visibilities
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ LazyListView {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property Flickable container
|
required property Flickable container
|
||||||
required property Props props
|
required property ScreenState screenState
|
||||||
required property PersistentProperties visibilities
|
|
||||||
|
|
||||||
anchors.left: parent?.left
|
anchors.left: parent?.left
|
||||||
anchors.right: parent?.right
|
anchors.right: parent?.right
|
||||||
@@ -118,8 +117,7 @@ LazyListView {
|
|||||||
|
|
||||||
container: root.container
|
container: root.container
|
||||||
modelData: notif.modelData
|
modelData: notif.modelData
|
||||||
props: root.props
|
screenState: root.screenState
|
||||||
visibilities: root.visibilities
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ CustomRect {
|
|||||||
readonly property list<var> activeNotifs: notifs.filter(n => !n.closed)
|
readonly property list<var> activeNotifs: notifs.filter(n => !n.closed)
|
||||||
readonly property string appIcon: activeNotifs.find(n => n.appIcon.length > 0)?.appIcon ?? ""
|
readonly property string appIcon: activeNotifs.find(n => n.appIcon.length > 0)?.appIcon ?? ""
|
||||||
required property Flickable container
|
required property Flickable container
|
||||||
readonly property bool expanded: props.expandedNotifs.includes(modelData)
|
readonly property bool expanded: screenState.expandedNotifs.includes(modelData)
|
||||||
readonly property string image: activeNotifs.find(n => n.image.length > 0)?.image ?? ""
|
readonly property string image: activeNotifs.find(n => n.image.length > 0)?.image ?? ""
|
||||||
required property string modelData
|
required property string modelData
|
||||||
readonly property int nonAnimHeight: {
|
readonly property int nonAnimHeight: {
|
||||||
@@ -26,7 +26,7 @@ CustomRect {
|
|||||||
}
|
}
|
||||||
readonly property int notifCount: activeNotifs.length
|
readonly property int notifCount: activeNotifs.length
|
||||||
readonly property list<var> notifs: NotifServer.list.filter(n => n.appName === modelData)
|
readonly property list<var> notifs: NotifServer.list.filter(n => n.appName === modelData)
|
||||||
required property Props props
|
required property ScreenState screenState
|
||||||
readonly property int urgency: {
|
readonly property int urgency: {
|
||||||
if (activeNotifs.find(n => n.urgency === NotificationUrgency.Critical))
|
if (activeNotifs.find(n => n.urgency === NotificationUrgency.Critical))
|
||||||
return NotificationUrgency.Critical;
|
return NotificationUrgency.Critical;
|
||||||
@@ -34,14 +34,13 @@ CustomRect {
|
|||||||
return NotificationUrgency.Normal;
|
return NotificationUrgency.Normal;
|
||||||
return NotificationUrgency.Low;
|
return NotificationUrgency.Low;
|
||||||
}
|
}
|
||||||
required property PersistentProperties visibilities
|
|
||||||
|
|
||||||
function toggleExpand(expand: bool): void {
|
function toggleExpand(expand: bool): void {
|
||||||
if (expand) {
|
if (expand) {
|
||||||
if (!expanded)
|
if (!expanded)
|
||||||
props.expandedNotifs.push(modelData);
|
screenState.expandedNotifs.push(modelData);
|
||||||
} else if (expanded) {
|
} else if (expanded) {
|
||||||
props.expandedNotifs.splice(props.expandedNotifs.indexOf(modelData), 1);
|
screenState.expandedNotifs.splice(screenState.expandedNotifs.indexOf(modelData), 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +58,7 @@ CustomRect {
|
|||||||
|
|
||||||
Component.onDestruction: {
|
Component.onDestruction: {
|
||||||
if (notifCount === 0 && expanded)
|
if (notifCount === 0 && expanded)
|
||||||
props.expandedNotifs.splice(props.expandedNotifs.indexOf(modelData), 1);
|
screenState.expandedNotifs.splice(screenState.expandedNotifs.indexOf(modelData), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
@@ -233,8 +232,7 @@ CustomRect {
|
|||||||
container: root.container
|
container: root.container
|
||||||
expanded: root.expanded
|
expanded: root.expanded
|
||||||
notifs: root.notifs
|
notifs: root.notifs
|
||||||
props: root.props
|
screenState: root.screenState
|
||||||
visibilities: root.visibilities
|
|
||||||
|
|
||||||
onRequestToggleExpand: expand => root.toggleExpand(expand)
|
onRequestToggleExpand: expand => root.toggleExpand(expand)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ LazyListView {
|
|||||||
required property Flickable container
|
required property Flickable container
|
||||||
required property bool expanded
|
required property bool expanded
|
||||||
required property list<var> notifs
|
required property list<var> notifs
|
||||||
required property Props props
|
required property ScreenState screenState
|
||||||
required property PersistentProperties visibilities
|
|
||||||
|
|
||||||
signal requestToggleExpand(expand: bool)
|
signal requestToggleExpand(expand: bool)
|
||||||
|
|
||||||
@@ -122,8 +121,7 @@ LazyListView {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
expanded: root.expanded
|
expanded: root.expanded
|
||||||
modelData: notif.modelData
|
modelData: notif.modelData
|
||||||
props: root.props
|
screenState: root.screenState
|
||||||
visibilities: root.visibilities
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import Quickshell
|
|
||||||
|
|
||||||
PersistentProperties {
|
|
||||||
property list<string> expandedNotifs: []
|
|
||||||
|
|
||||||
reloadableId: "sidebar"
|
|
||||||
}
|
|
||||||
@@ -11,8 +11,8 @@ import qs.Services
|
|||||||
CustomRect {
|
CustomRect {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property var props
|
required property ScreenState screenState
|
||||||
required property PersistentProperties visibilities
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
color: Colors.tPalette.m3surfaceContainer
|
color: Colors.tPalette.m3surfaceContainer
|
||||||
@@ -72,7 +72,7 @@ CustomRect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SplitButton {
|
SplitButton {
|
||||||
active: menuItems.find(m => root.props.recordingMode === m.icon + m.text) ?? menuItems[0]
|
active: menuItems.find(m => root.screenState.recordingMode === m.icon + m.text) ?? menuItems[0]
|
||||||
enabled: !Recorder.running
|
enabled: !Recorder.running
|
||||||
menuOnTop: true
|
menuOnTop: true
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ CustomRect {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
menu.onItemSelected: item => root.props.recordingMode = item.icon + item.text
|
menu.onItemSelected: item => root.screenState.recordingMode = item.icon + item.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,8 +190,7 @@ CustomRect {
|
|||||||
id: recordingList
|
id: recordingList
|
||||||
|
|
||||||
RecordingList {
|
RecordingList {
|
||||||
props: root.props
|
screenState: root.screenState
|
||||||
visibilities: root.visibilities
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ import qs.Services
|
|||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property var props
|
required property ScreenState screenState
|
||||||
required property PersistentProperties visibilities
|
|
||||||
|
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
@@ -23,7 +22,7 @@ ColumnLayout {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
onClicked: root.props.recordingListExpanded = !root.props.recordingListExpanded
|
onClicked: root.screenState.recordingListExpanded = !root.screenState.recordingListExpanded
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
spacing: Tokens.spacing.small
|
spacing: Tokens.spacing.small
|
||||||
@@ -42,11 +41,11 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
IconButton {
|
IconButton {
|
||||||
icon: root.props.recordingListExpanded ? "unfold_less" : "unfold_more"
|
icon: root.screenState.recordingListExpanded ? "unfold_less" : "unfold_more"
|
||||||
label.animate: true
|
label.animate: true
|
||||||
type: IconButton.Text
|
type: IconButton.Text
|
||||||
|
|
||||||
onClicked: root.props.recordingListExpanded = !root.props.recordingListExpanded
|
onClicked: root.screenState.recordingListExpanded = !root.screenState.recordingListExpanded
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,7 +56,7 @@ ColumnLayout {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.rightMargin: -Tokens.spacing.small
|
Layout.rightMargin: -Tokens.spacing.small
|
||||||
clip: true
|
clip: true
|
||||||
implicitHeight: (Tokens.font.size.larger + Tokens.padding.extraSmall) * (root.props.recordingListExpanded ? 10 : 3)
|
implicitHeight: (Tokens.font.size.larger + Tokens.padding.extraSmall) * (root.screenState.recordingListExpanded ? 10 : 3)
|
||||||
|
|
||||||
CustomScrollBar.vertical: CustomScrollBar {
|
CustomScrollBar.vertical: CustomScrollBar {
|
||||||
flickable: list
|
flickable: list
|
||||||
@@ -109,8 +108,11 @@ ColumnLayout {
|
|||||||
type: IconButton.Text
|
type: IconButton.Text
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.visibilities.sidebar = false;
|
root.screenState.sidebar = false;
|
||||||
Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.playback, recording.modelData.path]);
|
if (Config.launcher.uwsm)
|
||||||
|
Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.playback, recording.modelData.path]);
|
||||||
|
else
|
||||||
|
Quickshell.execDetached([...Config.general.apps.playback, recording.modelData.path]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,8 +121,11 @@ ColumnLayout {
|
|||||||
type: IconButton.Text
|
type: IconButton.Text
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.visibilities.sidebar = false;
|
root.screenState.sidebar = false;
|
||||||
Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.explorer, recording.modelData.path]);
|
if (Config.launcher.uwsm)
|
||||||
|
Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.explorer, recording.modelData.path]);
|
||||||
|
else
|
||||||
|
Quickshell.execDetached([...Config.general.apps.explorer, recording.modelData.path]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,11 +175,11 @@ ColumnLayout {
|
|||||||
|
|
||||||
MaterialIcon {
|
MaterialIcon {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.preferredHeight: root.props.recordingListExpanded ? implicitHeight : 0
|
Layout.preferredHeight: root.screenState.recordingListExpanded ? implicitHeight : 0
|
||||||
color: Colors.palette.m3outline
|
color: Colors.palette.m3outline
|
||||||
font.pointSize: Tokens.font.size.extraLarge
|
font.pointSize: Tokens.font.size.extraLarge
|
||||||
opacity: root.props.recordingListExpanded ? 1 : 0
|
opacity: root.screenState.recordingListExpanded ? 1 : 0
|
||||||
scale: root.props.recordingListExpanded ? 1 : 0
|
scale: root.screenState.recordingListExpanded ? 1 : 0
|
||||||
text: "scan_delete"
|
text: "scan_delete"
|
||||||
|
|
||||||
Behavior on Layout.preferredHeight {
|
Behavior on Layout.preferredHeight {
|
||||||
@@ -196,10 +201,10 @@ ColumnLayout {
|
|||||||
|
|
||||||
MaterialIcon {
|
MaterialIcon {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.preferredWidth: !root.props.recordingListExpanded ? implicitWidth : 0
|
Layout.preferredWidth: !root.screenState.recordingListExpanded ? implicitWidth : 0
|
||||||
color: Colors.palette.m3outline
|
color: Colors.palette.m3outline
|
||||||
opacity: !root.props.recordingListExpanded ? 1 : 0
|
opacity: !root.screenState.recordingListExpanded ? 1 : 0
|
||||||
scale: !root.props.recordingListExpanded ? 1 : 0
|
scale: !root.screenState.recordingListExpanded ? 1 : 0
|
||||||
text: "scan_delete"
|
text: "scan_delete"
|
||||||
|
|
||||||
Behavior on Layout.preferredWidth {
|
Behavior on Layout.preferredWidth {
|
||||||
|
|||||||
@@ -12,13 +12,11 @@ import qs.Services
|
|||||||
import qs.Helpers
|
import qs.Helpers
|
||||||
import qs.Daemons
|
import qs.Daemons
|
||||||
import qs.Modules.Settings
|
import qs.Modules.Settings
|
||||||
import qs.Modules.Bar.Popouts as BarPopouts
|
|
||||||
|
|
||||||
CustomRect {
|
CustomRect {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property bool needExtraRow: quickToggles.length > 6
|
readonly property bool needExtraRow: quickToggles.length > 6
|
||||||
required property BarPopouts.Wrapper popouts
|
|
||||||
readonly property var quickToggles: {
|
readonly property var quickToggles: {
|
||||||
const seenIds = new Set();
|
const seenIds = new Set();
|
||||||
|
|
||||||
@@ -40,8 +38,8 @@ CustomRect {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
required property ScreenState screenState
|
||||||
readonly property int splitIndex: Math.ceil(quickToggles.length / 2)
|
readonly property int splitIndex: Math.ceil(quickToggles.length / 2)
|
||||||
required property PersistentProperties visibilities
|
|
||||||
|
|
||||||
color: Colors.tPalette.m3surfaceContainer
|
color: Colors.tPalette.m3surfaceContainer
|
||||||
implicitHeight: layout.implicitHeight + Tokens.padding.extraLargeIncreased
|
implicitHeight: layout.implicitHeight + Tokens.padding.extraLargeIncreased
|
||||||
@@ -168,8 +166,8 @@ CustomRect {
|
|||||||
isToggle: false
|
isToggle: false
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.visibilities.sidebar = false;
|
root.screenState.sidebar = false;
|
||||||
root.visibilities.settings = true;
|
root.screenState.settings = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import qs.Modules.Bar.Popouts as BarPopouts
|
import qs.Components
|
||||||
import qs.Modules.Notifications.Sidebar.Utils.Cards
|
import qs.Modules.Notifications.Sidebar.Utils.Cards
|
||||||
import ZShell.Config
|
import ZShell.Config
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property BarPopouts.Wrapper popouts
|
required property ScreenState screenState
|
||||||
required property PersistentProperties props
|
|
||||||
required property var visibilities
|
|
||||||
|
|
||||||
implicitHeight: layout.implicitHeight
|
implicitHeight: layout.implicitHeight
|
||||||
implicitWidth: layout.implicitWidth
|
implicitWidth: layout.implicitWidth
|
||||||
@@ -25,15 +23,13 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Record {
|
Record {
|
||||||
props: root.props
|
screenState: root.screenState
|
||||||
visibilities: root.visibilities
|
|
||||||
z: 1
|
z: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
Toggles {
|
Toggles {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
popouts: root.popouts
|
screenState: root.screenState
|
||||||
visibilities: root.visibilities
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,17 +10,9 @@ Item {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property real offsetScale: shouldBeActive ? 0 : 1
|
property real offsetScale: shouldBeActive ? 0 : 1
|
||||||
required property BarPopouts.Wrapper popouts
|
required property ScreenState screenState
|
||||||
readonly property PersistentProperties props: PersistentProperties {
|
readonly property bool shouldBeActive: screenState.sidebar && !sidebar.chatActive
|
||||||
property string recordingConfirmDelete
|
|
||||||
property bool recordingListExpanded: false
|
|
||||||
property string recordingMode
|
|
||||||
|
|
||||||
reloadableId: "utilities"
|
|
||||||
}
|
|
||||||
readonly property bool shouldBeActive: visibilities.sidebar
|
|
||||||
required property Item sidebar
|
required property Item sidebar
|
||||||
required property var visibilities
|
|
||||||
|
|
||||||
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
|
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
|
||||||
implicitHeight: content.implicitHeight + 8 * 2
|
implicitHeight: content.implicitHeight + 8 * 2
|
||||||
@@ -45,9 +37,7 @@ Item {
|
|||||||
|
|
||||||
sourceComponent: Content {
|
sourceComponent: Content {
|
||||||
implicitWidth: root.implicitWidth - 8 * 2
|
implicitWidth: root.implicitWidth - 8 * 2
|
||||||
popouts: root.popouts
|
screenState: root.screenState
|
||||||
props: root.props
|
|
||||||
visibilities: root.visibilities
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,11 @@ import QtQuick
|
|||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
readonly property bool chatActive: screenState.sidebarTab === 1
|
||||||
property real offsetScale: shouldBeActive ? 0 : 1
|
property real offsetScale: shouldBeActive ? 0 : 1
|
||||||
required property var panels
|
required property var panels
|
||||||
readonly property Props props: Props {
|
required property ScreenState screenState
|
||||||
}
|
readonly property bool shouldBeActive: root.screenState.sidebar && Config.sidebar.enabled
|
||||||
readonly property bool shouldBeActive: root.visibilities.sidebar && Config.sidebar.enabled
|
|
||||||
required property var visibilities
|
|
||||||
|
|
||||||
anchors.rightMargin: (-implicitWidth - 5) * offsetScale
|
anchors.rightMargin: (-implicitWidth - 5) * offsetScale
|
||||||
implicitWidth: Config.sidebar.sizes.width
|
implicitWidth: Config.sidebar.sizes.width
|
||||||
@@ -38,8 +37,7 @@ Item {
|
|||||||
|
|
||||||
sourceComponent: Content {
|
sourceComponent: Content {
|
||||||
implicitWidth: Config.sidebar.sizes.width - Tokens.padding.small * 2
|
implicitWidth: Config.sidebar.sizes.width - Tokens.padding.small * 2
|
||||||
props: root.props
|
screenState: root.screenState
|
||||||
visibilities: root.visibilities
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Item {
|
|||||||
|
|
||||||
required property Item panels
|
required property Item panels
|
||||||
required property Item sidebarPanel
|
required property Item sidebarPanel
|
||||||
required property var visibilities
|
required property ScreenState screenState
|
||||||
|
|
||||||
implicitHeight: content.implicitHeight
|
implicitHeight: content.implicitHeight
|
||||||
implicitWidth: content.implicitWidth
|
implicitWidth: content.implicitWidth
|
||||||
@@ -17,6 +17,6 @@ Item {
|
|||||||
id: content
|
id: content
|
||||||
|
|
||||||
panels: root.panels
|
panels: root.panels
|
||||||
visibilities: root.visibilities
|
screenState: root.screenState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ Item {
|
|||||||
required property bool muted
|
required property bool muted
|
||||||
required property bool sourceMuted
|
required property bool sourceMuted
|
||||||
required property real sourceVolume
|
required property real sourceVolume
|
||||||
required property var visibilities
|
required property ScreenState screenState
|
||||||
required property real volume
|
required property real volume
|
||||||
|
|
||||||
implicitHeight: layout.implicitHeight + Tokens.padding.smaller * 2
|
implicitHeight: layout.implicitHeight + Tokens.padding.smaller * 2
|
||||||
@@ -53,7 +53,7 @@ Item {
|
|||||||
|
|
||||||
// Microphone volume
|
// Microphone volume
|
||||||
WrappedLoader {
|
WrappedLoader {
|
||||||
shouldBeActive: Config.osd.enableMicrophone && (!Config.osd.enableBrightness || !root.visibilities.session)
|
shouldBeActive: Config.osd.enableMicrophone && (!Config.osd.enableBrightness || !root.screenState.session)
|
||||||
|
|
||||||
sourceComponent: CustomMouseArea {
|
sourceComponent: CustomMouseArea {
|
||||||
function onWheel(event: WheelEvent) {
|
function onWheel(event: WheelEvent) {
|
||||||
|
|||||||
@@ -16,16 +16,16 @@ Item {
|
|||||||
property bool muted
|
property bool muted
|
||||||
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.osd && Config.osd.enabled && !(visibilities.utilities && Config.utilities.enabled)
|
readonly property bool shouldBeActive: screenState.osd && Config.osd.enabled && !(screenState.utilities && Config.utilities.enabled)
|
||||||
property real sidebarOffset: sidebarOrSessionVisible ? 12 : 0
|
property real sidebarOffset: sidebarOrSessionVisible ? 12 : 0
|
||||||
required property bool sidebarOrSessionVisible
|
required property bool sidebarOrSessionVisible
|
||||||
property bool sourceMuted
|
property bool sourceMuted
|
||||||
property real sourceVolume
|
property real sourceVolume
|
||||||
required property var visibilities
|
required property ScreenState screenState
|
||||||
property real volume
|
property real volume
|
||||||
|
|
||||||
function show(): void {
|
function show(): void {
|
||||||
visibilities.osd = true;
|
screenState.osd = true;
|
||||||
timer.restart();
|
timer.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ Item {
|
|||||||
|
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (!root.hovered)
|
if (!root.hovered)
|
||||||
root.visibilities.osd = false;
|
root.screenState.osd = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ Item {
|
|||||||
muted: root.muted
|
muted: root.muted
|
||||||
sourceMuted: root.sourceMuted
|
sourceMuted: root.sourceMuted
|
||||||
sourceVolume: root.sourceVolume
|
sourceVolume: root.sourceVolume
|
||||||
visibilities: root.visibilities
|
screenState: root.screenState
|
||||||
volume: root.volume
|
volume: root.volume
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ Item {
|
|||||||
readonly property real nonAnimHeight: content.item?.nonAnimHeight ?? 0
|
readonly property real nonAnimHeight: content.item?.nonAnimHeight ?? 0
|
||||||
property real offsetScale: shouldBeActive ? 0 : 1
|
property real offsetScale: shouldBeActive ? 0 : 1
|
||||||
required property string position
|
required property string position
|
||||||
readonly property bool shouldBeActive: root.visibilities.resources
|
readonly property bool shouldBeActive: root.screenState.resources
|
||||||
required property PersistentProperties visibilities
|
required property ScreenState screenState
|
||||||
|
|
||||||
anchors.bottom: position === "bottom" ? parent.bottom : undefined
|
anchors.bottom: position === "bottom" ? parent.bottom : undefined
|
||||||
anchors.bottomMargin: position === "bottom" ? (-implicitHeight - 5) * offsetScale : 0
|
anchors.bottomMargin: position === "bottom" ? (-implicitHeight - 5) * offsetScale : 0
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import ZShell.Config
|
||||||
|
import qs.Components
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
|
ConnectedRect {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property alias label: label.text
|
||||||
|
property string subtext
|
||||||
|
property string value
|
||||||
|
property alias placeholderText: input.placeholderText
|
||||||
|
property string errorText
|
||||||
|
property alias maximumLength: input.maximumLength
|
||||||
|
property alias validate: input.validate
|
||||||
|
property alias validator: input.validator
|
||||||
|
property bool smallField
|
||||||
|
readonly property alias field: input
|
||||||
|
|
||||||
|
signal valueEdited(value: string)
|
||||||
|
signal editingFinished(value: string)
|
||||||
|
|
||||||
|
function clear(): void {
|
||||||
|
input.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onDestruction: {
|
||||||
|
if (value !== input.text)
|
||||||
|
editingFinished(input.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: rowLayout.implicitHeight + Tokens.padding.medium + Math.max(0, Tokens.padding.large - input.verticalPadding) * 2
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: rowLayout
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.leftMargin: Tokens.padding.largeIncreased
|
||||||
|
anchors.rightMargin: Tokens.padding.largeIncreased
|
||||||
|
spacing: Tokens.spacing.medium
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
id: label
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
font: Tokens.font.body.small
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: root.subtext
|
||||||
|
text: input.isError && root.errorText ? root.errorText : root.subtext
|
||||||
|
color: input.isError && root.errorText ? Colors.palette.m3error : Colors.palette.m3outline
|
||||||
|
font.pointSize: Tokens.font.size.small
|
||||||
|
elide: Text.ElideRight
|
||||||
|
animate: root.errorText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomTextField {
|
||||||
|
id: input
|
||||||
|
|
||||||
|
Layout.preferredWidth: root.smallField ? Tokens.sizes.nexus.smallTextFieldWidth : Tokens.sizes.nexus.textFieldWidth
|
||||||
|
Layout.maximumWidth: root.width / 2
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
verticalPadding: Tokens.padding.small
|
||||||
|
|
||||||
|
text: root.value
|
||||||
|
|
||||||
|
onTextEdited: root.valueEdited(text)
|
||||||
|
onEditingFinished: root.editingFinished(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,5 +33,33 @@ PageBase {
|
|||||||
|
|
||||||
onToggled: Config.dashboard.enabled = checked
|
onToggled: Config.dashboard.enabled = checked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Behavior
|
||||||
|
SectionHeader {
|
||||||
|
text: qsTr("Behavior")
|
||||||
|
}
|
||||||
|
|
||||||
|
ToggleRow {
|
||||||
|
checked: Config.dashboard.showOnHover
|
||||||
|
first: true
|
||||||
|
settingAnchor: "dashboard-showonhover"
|
||||||
|
text: qsTr("Show on hover")
|
||||||
|
subtext: qsTr("Dashboard %1 be shown when hovering the middle-top part of the shell").arg(checked ? "will" : "will not")
|
||||||
|
|
||||||
|
onToggled: Config.dashboard.showOnHover = checked
|
||||||
|
}
|
||||||
|
|
||||||
|
SpinRow {
|
||||||
|
from: 30
|
||||||
|
settingAnchor: "dashboard-drag-threshold"
|
||||||
|
stepSize: 20
|
||||||
|
enabled: !Config.dashboard.showOnHover
|
||||||
|
subtext: qsTr("How far you need to drag before the panel opens")
|
||||||
|
text: qsTr("Drag threshold")
|
||||||
|
to: 100
|
||||||
|
value: Config.dashboard.dragThreshold
|
||||||
|
|
||||||
|
onMoved: v => Config.dashboard.dragThreshold = v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ Item {
|
|||||||
required property var panels
|
required property var panels
|
||||||
property string queuedMode: "wallpaper"
|
property string queuedMode: "wallpaper"
|
||||||
required property ShellScreen screen
|
required property ShellScreen screen
|
||||||
readonly property bool shouldBeActive: visibilities.settings
|
readonly property bool shouldBeActive: screenState.settings
|
||||||
required property PersistentProperties visibilities
|
required property ScreenState screenState
|
||||||
|
|
||||||
implicitHeight: content.implicitHeight
|
implicitHeight: content.implicitHeight
|
||||||
implicitWidth: content.implicitWidth
|
implicitWidth: content.implicitWidth
|
||||||
|
|||||||
+13
-12
@@ -3,6 +3,7 @@ import Quickshell
|
|||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import qs.Components
|
import qs.Components
|
||||||
import qs.Helpers
|
import qs.Helpers
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
Scope {
|
Scope {
|
||||||
id: root
|
id: root
|
||||||
@@ -17,8 +18,8 @@ Scope {
|
|||||||
onPressed: {
|
onPressed: {
|
||||||
root.launcherInterrupted = false;
|
root.launcherInterrupted = false;
|
||||||
if (!root.launcherInterrupted && !root.hasFullscreen) {
|
if (!root.launcherInterrupted && !root.hasFullscreen) {
|
||||||
const visibilities = Visibilities.getForActive();
|
const state = ShellState.forActive();
|
||||||
visibilities.launcher = !visibilities.launcher;
|
state.launcher = !state.launcher;
|
||||||
}
|
}
|
||||||
root.launcherInterrupted = false;
|
root.launcherInterrupted = false;
|
||||||
}
|
}
|
||||||
@@ -28,8 +29,8 @@ Scope {
|
|||||||
name: "toggle-drawing"
|
name: "toggle-drawing"
|
||||||
|
|
||||||
onPressed: {
|
onPressed: {
|
||||||
const visibilities = Visibilities.getForActive();
|
const state = ShellState.forActive();
|
||||||
visibilities.isDrawing = !visibilities.isDrawing;
|
state.isDrawing = !state.isDrawing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,8 +38,8 @@ Scope {
|
|||||||
name: "toggle-nc"
|
name: "toggle-nc"
|
||||||
|
|
||||||
onPressed: {
|
onPressed: {
|
||||||
const visibilities = Visibilities.getForActive();
|
const state = ShellState.forActive();
|
||||||
visibilities.sidebar = !visibilities.sidebar;
|
state.sidebar = !state.sidebar;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,8 +47,8 @@ Scope {
|
|||||||
name: "show-osd"
|
name: "show-osd"
|
||||||
|
|
||||||
onPressed: {
|
onPressed: {
|
||||||
const visibilities = Visibilities.getForActive();
|
const state = ShellState.forActive();
|
||||||
visibilities.osd = !visibilities.osd;
|
state.osd = !state.osd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,8 +56,8 @@ Scope {
|
|||||||
name: "toggle-settings"
|
name: "toggle-settings"
|
||||||
|
|
||||||
onPressed: {
|
onPressed: {
|
||||||
const visibilities = Visibilities.getForActive();
|
const state = ShellState.forActive();
|
||||||
visibilities.settings = !visibilities.settings;
|
state.settings = !state.settings;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,8 +65,8 @@ Scope {
|
|||||||
name: "toggle-clipboard"
|
name: "toggle-clipboard"
|
||||||
|
|
||||||
onPressed: {
|
onPressed: {
|
||||||
const visibilities = Visibilities.getForActive();
|
const state = ShellState.forActive();
|
||||||
visibilities.clipboard = !visibilities.clipboard;
|
state.clipboard = !state.clipboard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
qml_module(ZShell-config
|
qml_module(ZShell-config
|
||||||
URI ZShell.Config
|
URI ZShell.Config
|
||||||
|
INCLUDE_PREFIX config
|
||||||
SOURCES
|
SOURCES
|
||||||
confignode.hpp confignode.cpp
|
confignode.hpp confignode.cpp
|
||||||
configobject.hpp configobject.cpp
|
configobject.hpp configobject.cpp
|
||||||
@@ -14,6 +15,7 @@ qml_module(ZShell-config
|
|||||||
colors.hpp
|
colors.hpp
|
||||||
dashboard.hpp
|
dashboard.hpp
|
||||||
dock.hpp
|
dock.hpp
|
||||||
|
enums.hpp
|
||||||
general.hpp
|
general.hpp
|
||||||
launcher.hpp
|
launcher.hpp
|
||||||
lock.hpp
|
lock.hpp
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ class Dashboard : public ConfigObject {
|
|||||||
QML_ANONYMOUS
|
QML_ANONYMOUS
|
||||||
|
|
||||||
CFG_PROPERTY(bool, enabled, true)
|
CFG_PROPERTY(bool, enabled, true)
|
||||||
|
CFG_PROPERTY(bool, showOnHover, true)
|
||||||
|
CFG_PROPERTY(int, dragThreshold, 50)
|
||||||
CFG_PROPERTY(int, mediaUpdateInterval, 2000)
|
CFG_PROPERTY(int, mediaUpdateInterval, 2000)
|
||||||
CONFIG_SUBOBJECT(Performance, performance)
|
CONFIG_SUBOBJECT(Performance, performance)
|
||||||
CFG_PROPERTY(int, resourceUpdateInterval, 1000)
|
CFG_PROPERTY(int, resourceUpdateInterval, 1000)
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qqmlintegration.h>
|
||||||
|
|
||||||
|
namespace ZShell::config {
|
||||||
|
|
||||||
|
#define ENUM(Name, ...) \
|
||||||
|
namespace Name { \
|
||||||
|
\
|
||||||
|
Q_NAMESPACE \
|
||||||
|
QML_ELEMENT \
|
||||||
|
\
|
||||||
|
enum Enum : quint8 { __VA_ARGS__ }; \
|
||||||
|
Q_ENUM_NS(Enum) \
|
||||||
|
};
|
||||||
|
|
||||||
|
ENUM(BarWorkspaceDisplay, Shapes, Text, Icons)
|
||||||
|
ENUM(BarWorkspaceCapitalisation, Preserve, Upper, Lower)
|
||||||
|
ENUM(LyricsBackend, Auto, Local, LRCLIB, NetEase)
|
||||||
|
ENUM(GpuType, Auto, Nvidia, Generic, None)
|
||||||
|
ENUM(NotifsFullscreen, On, Off)
|
||||||
|
ENUM(TemperatureUnit, Auto, Celsius, Fahrenheit, Kelvin)
|
||||||
|
ENUM(DataUnit, Binary, Decimal)
|
||||||
|
ENUM(ClockFormat, Auto, TwelveHour, TwentyFourHour)
|
||||||
|
|
||||||
|
#undef ENUM
|
||||||
|
|
||||||
|
} // namespace ZShell::config
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "configlist.hpp"
|
#include "configlist.hpp"
|
||||||
#include "configobject.hpp"
|
#include "configobject.hpp"
|
||||||
|
#include <QStandardPaths>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
|
||||||
namespace ZShell::config {
|
namespace ZShell::config {
|
||||||
@@ -141,6 +142,10 @@ class General : public ConfigObject {
|
|||||||
CFG_PROPERTY(QString, dateFormat, QStringLiteral("ddd d MMM - hh:mm:ss"))
|
CFG_PROPERTY(QString, dateFormat, QStringLiteral("ddd d MMM - hh:mm:ss"))
|
||||||
CFG_PROPERTY(bool, desktopIcons, true)
|
CFG_PROPERTY(bool, desktopIcons, true)
|
||||||
CONFIG_SUBOBJECT(Idle, idle)
|
CONFIG_SUBOBJECT(Idle, idle)
|
||||||
|
CFG_PROPERTY(
|
||||||
|
QString,
|
||||||
|
lyricsDir,
|
||||||
|
QStandardPaths::writableLocation(QStandardPaths::MusicLocation) + QStringLiteral("/Lyrics/"))
|
||||||
CFG_PROPERTY(QString, logo, QString())
|
CFG_PROPERTY(QString, logo, QString())
|
||||||
CFG_PROPERTY(bool, showOverFullscreen, true)
|
CFG_PROPERTY(bool, showOverFullscreen, true)
|
||||||
CFG_PROPERTY(
|
CFG_PROPERTY(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "configlist.hpp"
|
#include "configlist.hpp"
|
||||||
#include "configobject.hpp"
|
#include "configobject.hpp"
|
||||||
|
#include "enums.hpp"
|
||||||
|
|
||||||
namespace ZShell::config {
|
namespace ZShell::config {
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ class Services : public ConfigObject {
|
|||||||
CFG_PROPERTY(bool, ddcutilService, true)
|
CFG_PROPERTY(bool, ddcutilService, true)
|
||||||
CFG_PROPERTY(QString, defaultPlayer, QStringLiteral("Spotify"))
|
CFG_PROPERTY(QString, defaultPlayer, QStringLiteral("Spotify"))
|
||||||
CFG_PROPERTY(QString, gpuType, QStringLiteral("NVIDIA"))
|
CFG_PROPERTY(QString, gpuType, QStringLiteral("NVIDIA"))
|
||||||
|
CFG_PROPERTY(LyricsBackend::Enum, lyricsBackend, LyricsBackend::Auto)
|
||||||
CFG_PROPERTY(qreal, maxVolume, 1)
|
CFG_PROPERTY(qreal, maxVolume, 1)
|
||||||
CFG_PROPERTY(qreal, minBrightness, 0.01)
|
CFG_PROPERTY(qreal, minBrightness, 0.01)
|
||||||
CONFIG_LIST(
|
CONFIG_LIST(
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
#include "sparklineitem.hpp"
|
#include "sparklineitem.hpp"
|
||||||
|
|
||||||
#include <qmath.h>
|
#include <qmath.h>
|
||||||
|
#include <qnumeric.h>
|
||||||
#include <qpainter.h>
|
#include <qpainter.h>
|
||||||
#include <qpainterpath.h>
|
#include <qpainterpath.h>
|
||||||
#include <qpen.h>
|
#include <qpen.h>
|
||||||
|
#include <qtypes.h>
|
||||||
|
|
||||||
namespace ZShell::internal {
|
namespace ZShell::internal {
|
||||||
|
|
||||||
@@ -35,9 +37,19 @@ void SparklineItem::drawLine(
|
|||||||
const int len = buffer->count();
|
const int len = buffer->count();
|
||||||
if (len < 2 || m_maxValue <= 0.0) return;
|
if (len < 2 || m_maxValue <= 0.0) return;
|
||||||
|
|
||||||
const qreal stepX = w / static_cast<qreal>(m_historyLength - 1);
|
qreal stepX = 0.0;
|
||||||
const qreal startX =
|
|
||||||
w - (len - 1) * stepX - stepX * m_slideProgress + stepX;
|
if (m_sliding)
|
||||||
|
stepX = w / static_cast<qreal>(m_historyLength - 1);
|
||||||
|
else
|
||||||
|
stepX = w / static_cast<qreal>(len - 1);
|
||||||
|
|
||||||
|
qreal startX = 0.0;
|
||||||
|
|
||||||
|
if (m_sliding)
|
||||||
|
startX = w - (len - 1) * stepX - stepX * m_slideProgress + stepX;
|
||||||
|
else
|
||||||
|
startX = 0;
|
||||||
|
|
||||||
const qreal strokePad = qCeil(m_lineWidth / 2);
|
const qreal strokePad = qCeil(m_lineWidth / 2);
|
||||||
const qreal curvePad = 3.0;
|
const qreal curvePad = 3.0;
|
||||||
@@ -52,24 +64,75 @@ void SparklineItem::drawLine(
|
|||||||
points.reserve(len);
|
points.reserve(len);
|
||||||
|
|
||||||
for (int i = 0; i < len; ++i) {
|
for (int i = 0; i < len; ++i) {
|
||||||
|
qreal value = 0.0;
|
||||||
|
|
||||||
|
if (m_smoothing > 0.0) {
|
||||||
|
const int radius = qRound(m_smoothing);
|
||||||
|
qreal sum = 0.0;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for (int j = -radius; j <= radius; ++j) {
|
||||||
|
const int index = i + j;
|
||||||
|
|
||||||
|
if (index < 0 || index >= len) continue;
|
||||||
|
|
||||||
|
sum += buffer->at(index);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = sum / static_cast<qreal>(count);
|
||||||
|
} else {
|
||||||
|
value = buffer->at(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
value = qBound<qreal>(0.0, value, m_maxValue);
|
||||||
|
|
||||||
const qreal x = startX + i * stepX;
|
const qreal x = startX + i * stepX;
|
||||||
const qreal value = qBound<qreal>(0.0, buffer->at(i), m_maxValue);
|
|
||||||
const qreal y = plotTop + (1.0 - (value / m_maxValue)) * plotH;
|
const qreal y = plotTop + (1.0 - (value / m_maxValue)) * plotH;
|
||||||
|
|
||||||
points.append(QPointF(x, y));
|
points.append(QPointF(x, y));
|
||||||
}
|
}
|
||||||
|
|
||||||
QPainterPath linePath;
|
QPainterPath linePath;
|
||||||
linePath.moveTo(points[0]);
|
linePath.moveTo(points[0]);
|
||||||
|
|
||||||
for (int i = 0; i < points.size() - 1; ++i) {
|
if (m_curveSmoothing && points.size() > 2) {
|
||||||
const QPointF& p0 = points[i];
|
// 1.0 = standard uniform Catmull-Rom. Lower (e.g. 0.5) = tighter, less overshoot.
|
||||||
const QPointF& p1 = points[i + 1];
|
const qreal tension = 1.0;
|
||||||
|
const qreal k =
|
||||||
|
tension / 6.0; // Catmull-Rom -> Bezier factor (1/6 for uniform)
|
||||||
|
|
||||||
const qreal ctrlX = (p0.x() + p1.x()) * 0.5;
|
const int n = points.size();
|
||||||
const QPointF c1(ctrlX, p0.y());
|
|
||||||
const QPointF c2(ctrlX, p1.y());
|
|
||||||
|
|
||||||
linePath.cubicTo(c1, c2, p1);
|
for (int i = 0; i < n - 1; ++i) {
|
||||||
|
// Duplicate endpoints so the first/last segments are well-defined
|
||||||
|
const QPointF& p0 = points[qMax(i - 1, 0)];
|
||||||
|
const QPointF& p1 = points[i];
|
||||||
|
const QPointF& p2 = points[i + 1];
|
||||||
|
const QPointF& p3 = points[qMin(i + 2, n - 1)];
|
||||||
|
|
||||||
|
QPointF c1 = p1 + (p2 - p0) * k;
|
||||||
|
QPointF c2 = p2 - (p3 - p1) * k;
|
||||||
|
|
||||||
|
// Keep control points inside the vertical range of the segment's
|
||||||
|
// endpoints so we never overshoot (no dips below zero, no
|
||||||
|
// bulges above a peak). Remember: smaller y = higher value.
|
||||||
|
const qreal segMinY = qMin(p1.y(), p2.y());
|
||||||
|
const qreal segMaxY = qMax(p1.y(), p2.y());
|
||||||
|
c1.setY(qBound(segMinY, c1.y(), segMaxY));
|
||||||
|
c2.setY(qBound(segMinY, c2.y(), segMaxY));
|
||||||
|
|
||||||
|
linePath.cubicTo(c1, c2, p2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Existing horizontal-tangent (smoothstep-style) curve
|
||||||
|
for (int i = 0; i < points.size() - 1; ++i) {
|
||||||
|
const QPointF& p0 = points[i];
|
||||||
|
const QPointF& p1 = points[i + 1];
|
||||||
|
|
||||||
|
const qreal ctrlX = (p0.x() + p1.x()) * 0.5;
|
||||||
|
linePath.cubicTo(QPointF(ctrlX, p0.y()), QPointF(ctrlX, p1.y()), p1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QPen pen(color, m_lineWidth);
|
QPen pen(color, m_lineWidth);
|
||||||
@@ -224,4 +287,36 @@ void SparklineItem::setLineWidth(qreal width) {
|
|||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SparklineItem::setSliding(bool sliding) {
|
||||||
|
if (m_sliding == sliding) return;
|
||||||
|
m_sliding = sliding;
|
||||||
|
emit slidingChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SparklineItem::sliding() const {
|
||||||
|
return m_sliding;
|
||||||
|
};
|
||||||
|
|
||||||
|
void SparklineItem::setSmoothing(qreal smoothing) {
|
||||||
|
smoothing = qMax<qreal>(0.0, smoothing);
|
||||||
|
|
||||||
|
if (qFuzzyCompare(m_smoothing, smoothing)) return;
|
||||||
|
m_smoothing = smoothing;
|
||||||
|
emit smoothingChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal SparklineItem::smoothing() const {
|
||||||
|
return m_smoothing;
|
||||||
|
};
|
||||||
|
|
||||||
|
void SparklineItem::setCurveSmoothing(bool sliding) {
|
||||||
|
if (m_curveSmoothing == sliding) return;
|
||||||
|
m_curveSmoothing = sliding;
|
||||||
|
emit curveSmoothingChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SparklineItem::curveSmoothing() const {
|
||||||
|
return m_curveSmoothing;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace ZShell::internal
|
} // namespace ZShell::internal
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qqmlintegration.h>
|
#include <qqmlintegration.h>
|
||||||
#include <qquickpainteditem.h>
|
#include <qquickpainteditem.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
#include "circularbuffer.hpp"
|
#include "circularbuffer.hpp"
|
||||||
|
|
||||||
@@ -40,6 +41,13 @@ class SparklineItem : public QQuickPaintedItem {
|
|||||||
Q_PROPERTY(
|
Q_PROPERTY(
|
||||||
qreal lineWidth READ lineWidth WRITE setLineWidth NOTIFY
|
qreal lineWidth READ lineWidth WRITE setLineWidth NOTIFY
|
||||||
lineWidthChanged)
|
lineWidthChanged)
|
||||||
|
Q_PROPERTY(bool sliding READ sliding WRITE setSliding NOTIFY slidingChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
qreal smoothing READ smoothing WRITE setSmoothing NOTIFY
|
||||||
|
smoothingChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
bool curveSmoothing READ curveSmoothing WRITE setCurveSmoothing NOTIFY
|
||||||
|
curveSmoothingChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit SparklineItem(QQuickItem* parent = nullptr);
|
explicit SparklineItem(QQuickItem* parent = nullptr);
|
||||||
@@ -76,6 +84,15 @@ class SparklineItem : public QQuickPaintedItem {
|
|||||||
[[nodiscard]] qreal lineWidth() const;
|
[[nodiscard]] qreal lineWidth() const;
|
||||||
void setLineWidth(qreal width);
|
void setLineWidth(qreal width);
|
||||||
|
|
||||||
|
[[nodiscard]] bool sliding() const;
|
||||||
|
void setSliding(bool sliding);
|
||||||
|
|
||||||
|
[[nodiscard]] qreal smoothing() const;
|
||||||
|
void setSmoothing(qreal smoothing);
|
||||||
|
|
||||||
|
[[nodiscard]] bool curveSmoothing() const;
|
||||||
|
void setCurveSmoothing(bool curveSmoothing);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void line1Changed();
|
void line1Changed();
|
||||||
void line2Changed();
|
void line2Changed();
|
||||||
@@ -87,6 +104,9 @@ class SparklineItem : public QQuickPaintedItem {
|
|||||||
void slideProgressChanged();
|
void slideProgressChanged();
|
||||||
void historyLengthChanged();
|
void historyLengthChanged();
|
||||||
void lineWidthChanged();
|
void lineWidthChanged();
|
||||||
|
void slidingChanged();
|
||||||
|
void smoothingChanged();
|
||||||
|
void curveSmoothingChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void drawLine(
|
void drawLine(
|
||||||
@@ -106,6 +126,9 @@ class SparklineItem : public QQuickPaintedItem {
|
|||||||
qreal m_slideProgress = 0.0;
|
qreal m_slideProgress = 0.0;
|
||||||
int m_historyLength = 30;
|
int m_historyLength = 30;
|
||||||
qreal m_lineWidth = 2.0;
|
qreal m_lineWidth = 2.0;
|
||||||
|
bool m_sliding = true;
|
||||||
|
qreal m_smoothing = 0.0;
|
||||||
|
bool m_curveSmoothing = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::internal
|
} // namespace ZShell::internal
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ qml_module(ZShell-services
|
|||||||
memory.hpp memory.cpp
|
memory.hpp memory.cpp
|
||||||
diskinfo.hpp diskinfo.cpp
|
diskinfo.hpp diskinfo.cpp
|
||||||
storage.hpp storage.cpp
|
storage.hpp storage.cpp
|
||||||
|
lyriccandidate.hpp lyriccandidate.cpp
|
||||||
|
lyrics.hpp lyrics.cpp
|
||||||
LIBRARIES
|
LIBRARIES
|
||||||
Qt6::Core
|
Qt6::Core
|
||||||
Qt6::Qml
|
Qt6::Qml
|
||||||
@@ -25,4 +27,7 @@ qml_module(ZShell-services
|
|||||||
PkgConfig::Aubio
|
PkgConfig::Aubio
|
||||||
PkgConfig::Cava
|
PkgConfig::Cava
|
||||||
Sensors::Sensors
|
Sensors::Sensors
|
||||||
)
|
ZShell-config
|
||||||
|
DEPENDENCIES
|
||||||
|
ZShell.Config
|
||||||
|
)
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
#include "lyriccandidate.hpp"
|
||||||
|
|
||||||
|
namespace ZShell::services {
|
||||||
|
|
||||||
|
LyricCandidate::LyricCandidate(
|
||||||
|
LyricsBackend backend,
|
||||||
|
QString id,
|
||||||
|
QString title,
|
||||||
|
QString artist,
|
||||||
|
QString album,
|
||||||
|
qreal duration)
|
||||||
|
: m_backend(backend)
|
||||||
|
, m_id(std::move(id))
|
||||||
|
, m_title(std::move(title))
|
||||||
|
, m_artist(std::move(artist))
|
||||||
|
, m_album(std::move(album))
|
||||||
|
, m_duration(duration) {}
|
||||||
|
|
||||||
|
LyricsBackend LyricCandidate::backend() const {
|
||||||
|
return m_backend;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LyricCandidate::id() const {
|
||||||
|
return m_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LyricCandidate::title() const {
|
||||||
|
return m_title;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LyricCandidate::artist() const {
|
||||||
|
return m_artist;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LyricCandidate::album() const {
|
||||||
|
return m_album;
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal LyricCandidate::duration() const {
|
||||||
|
return m_duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LyricCandidate::isValid() const {
|
||||||
|
return !m_id.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LyricCandidate::operator==(const LyricCandidate& o) const noexcept {
|
||||||
|
return m_backend == o.m_backend && m_id == o.m_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LyricCandidate::operator!=(const LyricCandidate& o) const noexcept {
|
||||||
|
return !(*this == o);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ZShell::services
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qqmlintegration.h>
|
||||||
|
|
||||||
|
#include "config/enums.hpp"
|
||||||
|
|
||||||
|
namespace ZShell::services {
|
||||||
|
|
||||||
|
using LyricsBackend = config::LyricsBackend::Enum;
|
||||||
|
|
||||||
|
class LyricCandidate {
|
||||||
|
Q_GADGET
|
||||||
|
QML_VALUE_TYPE(lyricCandidate)
|
||||||
|
|
||||||
|
Q_PROPERTY(ZShell::config::LyricsBackend::Enum backend READ backend)
|
||||||
|
Q_PROPERTY(QString id READ id)
|
||||||
|
Q_PROPERTY(QString title READ title)
|
||||||
|
Q_PROPERTY(QString artist READ artist)
|
||||||
|
Q_PROPERTY(QString album READ album)
|
||||||
|
Q_PROPERTY(qreal duration READ duration)
|
||||||
|
|
||||||
|
public:
|
||||||
|
LyricCandidate() = default;
|
||||||
|
LyricCandidate(
|
||||||
|
LyricsBackend backend,
|
||||||
|
QString id,
|
||||||
|
QString title,
|
||||||
|
QString artist,
|
||||||
|
QString album = {},
|
||||||
|
qreal duration = 0.0);
|
||||||
|
|
||||||
|
[[nodiscard]] LyricsBackend backend() const;
|
||||||
|
[[nodiscard]] QString id() const;
|
||||||
|
[[nodiscard]] QString title() const;
|
||||||
|
[[nodiscard]] QString artist() const;
|
||||||
|
[[nodiscard]] QString album() const;
|
||||||
|
[[nodiscard]] qreal duration() const;
|
||||||
|
|
||||||
|
[[nodiscard]] bool isValid() const;
|
||||||
|
bool operator==(const LyricCandidate& o) const noexcept;
|
||||||
|
bool operator!=(const LyricCandidate& o) const noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
LyricsBackend m_backend = LyricsBackend::Auto;
|
||||||
|
QString m_id;
|
||||||
|
QString m_title;
|
||||||
|
QString m_artist;
|
||||||
|
QString m_album;
|
||||||
|
qreal m_duration = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ZShell::services
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,162 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qhash.h>
|
||||||
|
#include <qjsonobject.h>
|
||||||
|
#include <qnetworkaccessmanager.h>
|
||||||
|
#include <qnetworkreply.h>
|
||||||
|
#include <qtimer.h>
|
||||||
|
|
||||||
|
#include "lyriccandidate.hpp"
|
||||||
|
|
||||||
|
namespace ZShell::services {
|
||||||
|
|
||||||
|
struct LyricLine {
|
||||||
|
qreal time = 0.0;
|
||||||
|
QString text;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Lyrics : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
QML_SINGLETON
|
||||||
|
|
||||||
|
Q_PROPERTY(QStringList lyrics READ lyrics NOTIFY lyricsChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
ZShell::config::LyricsBackend::Enum backend READ backend NOTIFY
|
||||||
|
backendChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
ZShell::config::LyricsBackend::Enum preferredBackend READ
|
||||||
|
preferredBackend WRITE setPreferredBackend NOTIFY
|
||||||
|
preferredBackendChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
QList<ZShell::services::LyricCandidate> lyricCandidates READ
|
||||||
|
lyricCandidates NOTIFY lyricCandidatesChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
ZShell::services::LyricCandidate selectedCandidate READ selectedCandidate
|
||||||
|
WRITE setSelectedCandidate NOTIFY selectedCandidateChanged)
|
||||||
|
Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged)
|
||||||
|
Q_PROPERTY(bool hasLyrics READ hasLyrics NOTIFY hasLyricsChanged)
|
||||||
|
Q_PROPERTY(qreal offset READ offset WRITE setOffset NOTIFY offsetChanged)
|
||||||
|
Q_PROPERTY(QString trackArtist READ trackArtist NOTIFY trackChanged)
|
||||||
|
Q_PROPERTY(QString trackTitle READ trackTitle NOTIFY trackChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Lyrics(QObject* parent = nullptr);
|
||||||
|
|
||||||
|
[[nodiscard]] QStringList lyrics() const;
|
||||||
|
[[nodiscard]] LyricsBackend backend() const;
|
||||||
|
[[nodiscard]] LyricsBackend preferredBackend() const;
|
||||||
|
void setPreferredBackend(LyricsBackend value);
|
||||||
|
[[nodiscard]] QList<LyricCandidate> lyricCandidates() const;
|
||||||
|
[[nodiscard]] LyricCandidate selectedCandidate() const;
|
||||||
|
void setSelectedCandidate(const LyricCandidate& value);
|
||||||
|
[[nodiscard]] bool loading() const;
|
||||||
|
[[nodiscard]] bool hasLyrics() const;
|
||||||
|
[[nodiscard]] qreal offset() const;
|
||||||
|
void setOffset(qreal value);
|
||||||
|
[[nodiscard]] QString trackArtist() const;
|
||||||
|
[[nodiscard]] QString trackTitle() const;
|
||||||
|
|
||||||
|
[[nodiscard]] Q_INVOKABLE int indexForTime(qreal time) const;
|
||||||
|
[[nodiscard]] Q_INVOKABLE qreal timeForIndex(int index) const;
|
||||||
|
Q_INVOKABLE void setTrack(
|
||||||
|
const QString& artist,
|
||||||
|
const QString& title,
|
||||||
|
const QString& album = {},
|
||||||
|
qreal duration = 0.0);
|
||||||
|
Q_INVOKABLE void clearTrack();
|
||||||
|
Q_INVOKABLE void refresh();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void lyricsChanged();
|
||||||
|
void backendChanged();
|
||||||
|
void preferredBackendChanged();
|
||||||
|
void lyricCandidatesChanged();
|
||||||
|
void selectedCandidateChanged();
|
||||||
|
void loadingChanged();
|
||||||
|
void hasLyricsChanged();
|
||||||
|
void offsetChanged();
|
||||||
|
void trackChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setBackend(LyricsBackend value);
|
||||||
|
void setLoading(bool value);
|
||||||
|
void setLines(QVector<LyricLine> lines, LyricsBackend source);
|
||||||
|
void clearLines();
|
||||||
|
void appendCandidates(const QList<LyricCandidate>& add);
|
||||||
|
void clearCandidates();
|
||||||
|
|
||||||
|
void scheduleLoad();
|
||||||
|
void doLoad();
|
||||||
|
void cancelInFlight();
|
||||||
|
int newRequestId();
|
||||||
|
|
||||||
|
void tryLocal(int reqId);
|
||||||
|
void tryLrclib(int reqId);
|
||||||
|
void tryNetEase(int reqId);
|
||||||
|
void chainNext(LyricsBackend justFailed, int reqId);
|
||||||
|
|
||||||
|
void searchLrclibCandidates(int reqId);
|
||||||
|
void searchNetEaseCandidates(int reqId);
|
||||||
|
|
||||||
|
void fetchLrclibById(const QString& id, int reqId);
|
||||||
|
void fetchNetEaseLyricsById(const QString& id, int reqId);
|
||||||
|
|
||||||
|
QNetworkReply* getJson(
|
||||||
|
const QUrl& url, const QHash<QByteArray, QByteArray>& headers = {});
|
||||||
|
void trackReply(int reqId, QNetworkReply* reply);
|
||||||
|
|
||||||
|
void onPreferredBackendConfigChanged();
|
||||||
|
void onLyricsDirChanged();
|
||||||
|
|
||||||
|
void loadLyricsMap();
|
||||||
|
void persistTrackPrefs();
|
||||||
|
|
||||||
|
[[nodiscard]] static QString lyricsDir();
|
||||||
|
[[nodiscard]] static QString lyricsMapPath();
|
||||||
|
[[nodiscard]] QString trackKey() const;
|
||||||
|
[[nodiscard]] static QString backendKey(LyricsBackend value);
|
||||||
|
[[nodiscard]] static LyricsBackend backendFromKey(const QString& key);
|
||||||
|
|
||||||
|
[[nodiscard]] static const QString& stateDir();
|
||||||
|
[[nodiscard]] static const QString& cacheDir();
|
||||||
|
[[nodiscard]] static QString cachePathFor(
|
||||||
|
LyricsBackend backend, const QString& id);
|
||||||
|
[[nodiscard]] static QString readCachedLrc(
|
||||||
|
LyricsBackend backend, const QString& id);
|
||||||
|
static void writeCachedLrc(
|
||||||
|
LyricsBackend backend, const QString& id, const QString& text);
|
||||||
|
|
||||||
|
[[nodiscard]] static QVector<LyricLine> parseLrc(const QString& text);
|
||||||
|
[[nodiscard]] static QString tryReadLocalLrc(
|
||||||
|
const QString& dir, const QString& artist, const QString& title);
|
||||||
|
[[nodiscard]] static QString findLocalLrcRecursive(
|
||||||
|
const QString& dir, const QString& artist, const QString& title);
|
||||||
|
|
||||||
|
QNetworkAccessManager* m_nam;
|
||||||
|
QTimer* m_loadDebounce;
|
||||||
|
|
||||||
|
QVector<LyricLine> m_lines;
|
||||||
|
QStringList m_lyrics;
|
||||||
|
LyricsBackend m_backend = LyricsBackend::Auto;
|
||||||
|
LyricsBackend m_preferredBackend = LyricsBackend::Auto;
|
||||||
|
QList<LyricCandidate> m_candidates;
|
||||||
|
LyricCandidate m_selected;
|
||||||
|
bool m_loading = false;
|
||||||
|
bool m_hasLyrics = false;
|
||||||
|
qreal m_offset = 0.0;
|
||||||
|
|
||||||
|
QString m_artist;
|
||||||
|
QString m_title;
|
||||||
|
QString m_album;
|
||||||
|
qreal m_duration = 0.0;
|
||||||
|
|
||||||
|
int m_currentRequestId = 0;
|
||||||
|
QHash<int, QList<QPointer<QNetworkReply>>> m_pendingReplies;
|
||||||
|
|
||||||
|
QJsonObject m_lyricsMap;
|
||||||
|
bool m_lyricsMapLoaded = false;
|
||||||
|
bool m_settingFromPrefs = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ZShell::services
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
message(STATUS "QML install dir: ${CMAKE_INSTALL_PREFIX}/${INSTALL_QMLDIR}")
|
||||||
|
|
||||||
|
function(qml_module arg_TARGET)
|
||||||
|
cmake_parse_arguments(PARSE_ARGV 1 arg "" "URI;INCLUDE_PREFIX" "SOURCES;QML_FILES;QML_SINGLETONS;DEPENDENCIES;IMPORTS;OPTIONAL_IMPORTS;DEFAULT_IMPORTS;LIBRARIES")
|
||||||
|
|
||||||
|
set_source_files_properties(${arg_QML_SINGLETONS} PROPERTIES QT_QML_SINGLETON_TYPE TRUE)
|
||||||
|
|
||||||
|
qt_add_qml_module(${arg_TARGET}
|
||||||
|
URI ${arg_URI}
|
||||||
|
SOURCES ${arg_SOURCES}
|
||||||
|
QML_FILES ${arg_QML_FILES} ${arg_QML_SINGLETONS}
|
||||||
|
DEPENDENCIES ${arg_DEPENDENCIES}
|
||||||
|
IMPORTS ${arg_IMPORTS}
|
||||||
|
OPTIONAL_IMPORTS ${arg_OPTIONAL_IMPORTS}
|
||||||
|
DEFAULT_IMPORTS ${arg_DEFAULT_IMPORTS}
|
||||||
|
)
|
||||||
|
|
||||||
|
qt_query_qml_module(${arg_TARGET}
|
||||||
|
URI module_uri
|
||||||
|
PLUGIN_TARGET module_plugin_target
|
||||||
|
TARGET_PATH module_target_path
|
||||||
|
QMLDIR module_qmldir
|
||||||
|
TYPEINFO module_typeinfo
|
||||||
|
)
|
||||||
|
|
||||||
|
message(STATUS "Created QML module: ${module_uri}")
|
||||||
|
|
||||||
|
string(REPLACE "/" ";" uri_parts "${module_target_path}")
|
||||||
|
list(GET uri_parts 0 top_level)
|
||||||
|
set(backing_lib_dir "${INSTALL_QMLDIR}/${top_level}/lib")
|
||||||
|
set(module_dir "${INSTALL_QMLDIR}/${module_target_path}")
|
||||||
|
|
||||||
|
install(TARGETS ${arg_TARGET}
|
||||||
|
LIBRARY DESTINATION "${backing_lib_dir}"
|
||||||
|
RUNTIME DESTINATION "${backing_lib_dir}"
|
||||||
|
)
|
||||||
|
install(TARGETS "${module_plugin_target}"
|
||||||
|
LIBRARY DESTINATION "${module_dir}"
|
||||||
|
RUNTIME DESTINATION "${module_dir}"
|
||||||
|
)
|
||||||
|
install(FILES "${module_qmldir}" DESTINATION "${module_dir}")
|
||||||
|
install(FILES "${module_typeinfo}" DESTINATION "${module_dir}")
|
||||||
|
|
||||||
|
target_link_libraries(${arg_TARGET} PRIVATE zshell-pch Qt::Core Qt::Qml ${arg_LIBRARIES})
|
||||||
|
|
||||||
|
if(arg_INCLUDE_PREFIX)
|
||||||
|
set(include_dir "${CMAKE_CURRENT_BINARY_DIR}/include")
|
||||||
|
file(MAKE_DIRECTORY "${include_dir}")
|
||||||
|
file(CREATE_LINK "${CMAKE_CURRENT_SOURCE_DIR}" "${include_dir}/${arg_INCLUDE_PREFIX}" SYMBOLIC)
|
||||||
|
target_include_directories(${arg_TARGET} PUBLIC "${include_dir}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
file(RELATIVE_PATH plugin_to_lib "/${module_target_path}" "/${top_level}/lib")
|
||||||
|
set_property(TARGET ${module_plugin_target} APPEND PROPERTY INSTALL_RPATH "$ORIGIN/${plugin_to_lib}")
|
||||||
|
endfunction()
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import qs.Components
|
||||||
|
import qs.Helpers
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
function anySidebarOpen(): bool {
|
||||||
|
return states.instances.some(s => s.sidebar);
|
||||||
|
}
|
||||||
|
|
||||||
|
function forActive(): ScreenState {
|
||||||
|
const mon = Hypr.focusedMonitor;
|
||||||
|
for (const s of states.instances)
|
||||||
|
if (Hypr.monitorFor(s.modelData) === mon)
|
||||||
|
return s;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function forScreen(screen: ShellScreen): ScreenState {
|
||||||
|
for (const s of states.instances)
|
||||||
|
if (s.modelData === screen)
|
||||||
|
return s;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Variants {
|
||||||
|
id: states
|
||||||
|
|
||||||
|
model: Quickshell.screens
|
||||||
|
|
||||||
|
ScreenState {}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user