3 Commits
Author SHA1 Message Date
Inorishio 1adcb9841c Added network properties, nicNames, netDevices, networkName < reminder I should use layouts.
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 16s
Python / test (pull_request) Successful in 50s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m52s
2026-06-22 23:34:58 +02:00
Inorishio 0e0f8555ed fixing singleton in helpers for network + getting output from systray 2026-06-15 20:48:01 +02:00
Inorishio 1d63e67554 removed network module and moved it to tray, have a popout window without content! everything still in wi.i.p. 2026-06-13 18:22:03 +02:00
208 changed files with 4373 additions and 11573 deletions
-51
View File
@@ -1,51 +0,0 @@
import QtQuick
import qs.Config
AnchorAnimation {
enum Type {
StandardSmall = 0,
Standard,
StandardLarge,
StandardExtraLarge,
EmphasizedSmall,
Emphasized,
EmphasizedLarge,
EmphasizedExtraLarge,
FastSpatial,
DefaultSpatial,
SlowSpatial
}
property int type: AnchorAnim.DefaultSpatial
duration: {
if (type < AnchorAnim.StandardSmall || type > AnchorAnim.SlowSpatial)
return Appearance.anim.durations.expressiveDefaultSpatial;
if (type == AnchorAnim.FastSpatial)
return Appearance.anim.durations.expressiveFastSpatial;
if (type == AnchorAnim.DefaultSpatial)
return Appearance.anim.durations.expressiveDefaultSpatial;
if (type == AnchorAnim.SlowSpatial)
return Appearance.anim.durations.large;
const types = ["small", "normal", "large", "extraLarge"];
const idx = type % 4;
return Appearance.anim.durations[types[idx]];
}
easing.bezierCurve: {
if (type == AnchorAnim.FastSpatial)
return Appearance.anim.curves.expressiveFastSpatial;
if (type == AnchorAnim.DefaultSpatial)
return Appearance.anim.curves.expressiveDefaultSpatial;
if (type == AnchorAnim.SlowSpatial)
return Appearance.anim.curves.expressiveSlowSpatial;
if (type >= AnchorAnim.StandardSmall && type <= AnchorAnim.StandardExtraLarge)
return Appearance.anim.curves.standard;
if (type >= AnchorAnim.EmphasizedSmall && type <= AnchorAnim.EmphasizedExtraLarge)
return Appearance.anim.curves.emphasized;
return Appearance.anim.curves.expressiveDefaultSpatial;
}
}
+3 -58
View File
@@ -2,62 +2,7 @@ import QtQuick
import qs.Config import qs.Config
NumberAnimation { NumberAnimation {
enum Type { duration: Appearance.anim.durations.normal
StandardSmall = 0, easing.bezierCurve: Appearance.anim.curves.standard
Standard, easing.type: Easing.BezierSpline
StandardLarge,
StandardExtraLarge,
EmphasizedSmall,
Emphasized,
EmphasizedLarge,
EmphasizedExtraLarge,
FastSpatial,
DefaultSpatial,
SlowSpatial,
FastEffects,
DefaultEffects,
SlowEffects
}
property int type: Anim.DefaultSpatial
duration: {
if (type < Anim.StandardSmall || type > Anim.SlowEffects)
return Appearance.anim.durations.normal;
if (type === Anim.FastSpatial)
return Appearance.anim.durations.expressiveFastSpatial;
if (type === Anim.DefaultSpatial)
return Appearance.anim.durations.expressiveDefaultSpatial;
if (type === Anim.SlowSpatial)
return Appearance.anim.durations.large;
if (type === Anim.FastEffects)
return Appearance.anim.durations.expressiveFastEffects;
if (type === Anim.DefaultEffects)
return Appearance.anim.durations.expressiveEffects;
if (type === Anim.SlowEffects)
return Appearance.anim.durations.expressiveSlowEffects;
const types = ["small", "normal", "large", "extraLarge"];
const idx = type % 4;
return Appearance.anim.durations[types[idx]];
}
easing.bezierCurve: {
if (type === Anim.FastSpatial)
return Appearance.anim.curves.expressiveFastSpatial;
if (type === Anim.DefaultSpatial)
return Appearance.anim.curves.expressiveDefaultSpatial;
if (type === Anim.SlowSpatial)
return Appearance.anim.curves.expressiveSlowSpatial;
if (type === Anim.FastEffects)
return Appearance.anim.curves.expressiveFastEffects;
if (type === Anim.DefaultEffects)
return Appearance.anim.curves.expressiveDefaultEffects;
if (type === Anim.SlowEffects)
return Appearance.anim.curves.expressiveSlowEffects;
if (type >= Anim.EmphasizedSmall && type <= Anim.EmphasizedExtraLarge)
return Appearance.anim.curves.emphasized;
return Appearance.anim.curves.standard;
}
} }
+3
View File
@@ -12,8 +12,11 @@ Slider {
readonly property bool isVertical: orientation === Qt.Vertical readonly property bool isVertical: orientation === Qt.Vertical
property real multiplier: 100 property real multiplier: 100
property real oldValue property real oldValue
// Wrapper components can inject their own track visuals here.
property Component trackContent property Component trackContent
// Keep current behavior for existing usages.
orientation: Qt.Vertical orientation: Qt.Vertical
background: CustomRect { background: CustomRect {
-87
View File
@@ -1,87 +0,0 @@
import QtQuick
import qs.Config
CustomRect {
id: root
enum ButtonType {
Filled,
Tonal,
Text
}
property color activeColor
property color activeOnColor
property bool checked
property real checkedRadius: Appearance.rounding.medium
property real defaultRadius: Appearance.rounding.large
property color disabledColor: Qt.alpha(DynamicColors.palette.m3onSurface, 0.1)
property color disabledOnColor: Qt.alpha(DynamicColors.palette.m3onSurface, 0.38)
property bool fillWidth
property font font: ({
family: Appearance.font.family.sans,
pointSize: Appearance.font.size.larger,
bold: false
})
property real horizontalPadding: padding
readonly property alias hovered: stateLayer.containsMouse
required implicitHeight
required implicitWidth
property color inactiveColor
property color inactiveOnColor
property bool internalChecked
property bool isRound
property bool isToggle
readonly property color onColor: !enabled ? disabledOnColor : internalChecked ? activeOnColor : inactiveOnColor
property real padding
readonly property alias pressed: stateLayer.pressed
property real pressedRadius: Appearance.rounding.small
readonly property alias radiusAnim: radiusAnim
property bool radiusMorph: true
property alias shapeMorph: stateLayer.shapeMorph
property real shapeMorphExpansion: shapeMorph && pressed ? 24 : 0
readonly property alias stateLayer: stateLayer
property int type: ButtonBase.Filled
property real verticalPadding: padding
signal clicked
color: type === ButtonBase.Text ? "transparent" : !enabled ? disabledColor : internalChecked ? activeColor : inactiveColor
radius: {
if (radiusMorph && pressed)
return pressedRadius;
if (internalChecked)
return checkedRadius;
if (isRound)
return (height || implicitHeight) / 2 * Math.min(1, Appearance.rounding.scale);
return defaultRadius;
}
Behavior on radius {
Anim {
id: radiusAnim
type: Anim.DefaultEffects
}
}
Behavior on shapeMorphExpansion {
Anim {
type: Anim.FastSpatial
}
}
onCheckedChanged: internalChecked = checked
StateLayer {
id: stateLayer
color: root.internalChecked ? root.activeOnColor : root.inactiveOnColor
enabled: root.enabled
onClicked: {
if (root.isToggle)
root.internalChecked = !root.internalChecked;
root.clicked();
}
}
}
+5 -4
View File
@@ -17,8 +17,8 @@ BusyIndicator {
} }
property int animState property int animState
property color bgColor: DynamicColors.palette.m3secondaryContainer property color bgColour: DynamicColors.palette.m3secondaryContainer
property color fgColor: DynamicColors.palette.m3primary property color fgColour: DynamicColors.palette.m3primary
property real implicitSize: Appearance.font.size.normal * 3 property real implicitSize: Appearance.font.size.normal * 3
property real internalStrokeWidth: strokeWidth property real internalStrokeWidth: strokeWidth
readonly property alias progress: manager.progress readonly property alias progress: manager.progress
@@ -31,8 +31,8 @@ BusyIndicator {
contentItem: CircularProgress { contentItem: CircularProgress {
anchors.fill: parent anchors.fill: parent
bgColor: root.bgColor bgColour: root.bgColour
fgColor: root.fgColor fgColour: root.fgColour
padding: root.padding padding: root.padding
rotation: manager.rotation rotation: manager.rotation
startAngle: manager.startFraction * 360 startAngle: manager.startFraction * 360
@@ -73,6 +73,7 @@ BusyIndicator {
CircularIndicatorManager { CircularIndicatorManager {
id: manager id: manager
} }
NumberAnimation { NumberAnimation {
+39 -89
View File
@@ -1,116 +1,66 @@
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Shapes import QtQuick.Shapes
import ZShell.Components
import qs.Config import qs.Config
Item { Shape {
id: root id: root
readonly property real arcRadius: (size - padding - strokeWidth * (1 + waveAmplitude * 2)) / 2 readonly property real arcRadius: (size - padding - strokeWidth) / 2
property color bgColor: DynamicColors.palette.m3secondaryContainer property color bgColour: DynamicColors.palette.m3secondaryContainer
property real clampedVal: Math.max(1 / 360, Math.min(1, isNaN(value) ? 0 : value)) property color fgColour: DynamicColors.palette.m3primary
readonly property real dotAngleRad: (startAngle + sweepAngle - gapAngle * (sweepAngle < 360 ? 0 : 1)) * Math.PI / 180
property color fgColor: DynamicColors.palette.m3primary
readonly property real gapAngle: ((spacing + strokeWidth) / (arcRadius || 1)) * (180 / Math.PI) readonly property real gapAngle: ((spacing + strokeWidth) / (arcRadius || 1)) * (180 / Math.PI)
property alias hasEndIndicator: dot.active
property real implicitSize
property int padding: 0 property int padding: 0
readonly property real size: Math.min(width, height) readonly property real size: Math.min(width, height)
property int spacing: Appearance.spacing.small property int spacing: Appearance.spacing.small
property int startAngle: -90 property int startAngle: -90
property int strokeWidth: Appearance.padding.small property int strokeWidth: Appearance.padding.smaller
property int sweepAngle: 360 readonly property real vValue: value || 1 / 360
readonly property real thickness: strokeWidth * (1 + waveAmplitude) * 2
property real value property real value
property alias waveAmplitude: wave.amplitudeMultiplier
property alias waveDuration: waveProgAnim.duration
property alias waveFrequency: wave.frequency
property bool wavePaused
property bool wavy: false
implicitHeight: implicitSize asynchronous: true
implicitWidth: implicitSize preferredRendererType: Shape.CurveRenderer
Shape { ShapePath {
asynchronous: true capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
opacity: Math.min(1, remainingArc.sweepAngle) fillColor: "transparent"
preferredRendererType: Shape.CurveRenderer strokeColor: root.bgColour
strokeWidth: root.strokeWidth
ShapePath { Behavior on strokeColor {
capStyle: ShapePath.RoundCap
fillColor: "transparent"
strokeColor: root.bgColor
strokeWidth: Math.min(1, remainingArc.sweepAngle) * root.strokeWidth
Behavior on strokeColor {
CAnim {
}
}
PathAngleArc {
id: remainingArc
centerX: root.size / 2
centerY: root.size / 2
radiusX: root.arcRadius
radiusY: root.arcRadius
startAngle: root.startAngle + root.clampedVal * root.sweepAngle + root.gapAngle
sweepAngle: Math.max(1 / 360, root.sweepAngle * (1 - root.clampedVal) - root.gapAngle * (root.sweepAngle < 360 ? 1 : 2))
}
}
}
WavyLine {
id: wave
amplitudeMultiplier: root.wavy ? 0.5 : 0
anchors.fill: parent
anchors.margins: -lineWidth * amplitudeMultiplier
color: root.fgColor
frequency: 8
fullAngle: root.sweepAngle
lineWidth: root.strokeWidth
pathType: WavyLine.Arc
radius: root.arcRadius
startAngle: root.startAngle
value: root.clampedVal
Behavior on amplitudeMultiplier {
Anim {
type: Anim.DefaultEffects
}
}
Behavior on color {
CAnim { CAnim {
duration: Appearance.anim.durations.large
} }
} }
Anim on waveProgress {
id: waveProgAnim
duration: 2000 PathAngleArc {
easing.type: Easing.Linear centerX: root.size / 2
from: 0 centerY: root.size / 2
loops: Animation.Infinite radiusX: root.arcRadius
paused: root.wavePaused || wave.amplitudeMultiplier === 0 radiusY: root.arcRadius
running: true startAngle: root.startAngle + 360 * root.vValue + root.gapAngle
to: 1 sweepAngle: Math.max(-root.gapAngle, 360 * (1 - root.vValue) - root.gapAngle * 2)
} }
} }
Loader { ShapePath {
id: dot capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
fillColor: "transparent"
strokeColor: root.fgColour
strokeWidth: root.strokeWidth
x: root.size / 2 + root.arcRadius * Math.cos(root.dotAngleRad) - width / 2 Behavior on strokeColor {
y: root.size / 2 + root.arcRadius * Math.sin(root.dotAngleRad) - height / 2 CAnim {
duration: Appearance.anim.durations.large
}
}
sourceComponent: CustomRect { PathAngleArc {
color: root.fgColor centerX: root.size / 2
implicitHeight: Math.min(1, remainingArc.sweepAngle) * Math.min(4, root.strokeWidth) centerY: root.size / 2
implicitWidth: Math.min(1, remainingArc.sweepAngle) * Math.min(4, root.strokeWidth) radiusX: root.arcRadius
opacity: Math.min(1, remainingArc.sweepAngle) radiusY: root.arcRadius
radius: Appearance.rounding.full startAngle: root.startAngle
sweepAngle: 360 * root.vValue
} }
} }
} }
+5 -5
View File
@@ -59,15 +59,15 @@ ColumnLayout {
} }
StateLayer { StateLayer {
function onClicked(): void {
root.toggleRequested();
root.expanded = !root.expanded;
}
anchors.fill: parent anchors.fill: parent
color: DynamicColors.palette.m3onSurface color: DynamicColors.palette.m3onSurface
radius: Appearance.rounding.normal radius: Appearance.rounding.normal
showHoverBackground: false showHoverBackground: false
onClicked: {
root.toggleRequested();
root.expanded = !root.expanded;
}
} }
} }
+5 -28
View File
@@ -45,10 +45,10 @@ Item {
} }
function syncFromPenColor() { function syncFromPenColor() {
if (!drawing.drawingState) if (!drawing)
return; return;
const c = drawing.drawingState.penColor; const c = drawing.penColor;
if (c.hsvSaturation > 0) { if (c.hsvSaturation > 0) {
currentHue = c.hsvHue; currentHue = c.hsvHue;
@@ -85,20 +85,12 @@ 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.penColor = Qt.hsva(currentHue, drawing.penColor.hsvSaturation, drawing.penColor.hsvValue, drawing.penColor.a);
} }
implicitHeight: 180 implicitHeight: 180
implicitWidth: 220 implicitWidth: 220
Behavior on currentHue {
enabled: !root.dragActive
Anim {
type: Anim.StandardLarge
}
}
Component.onCompleted: syncFromPenColor() Component.onCompleted: syncFromPenColor()
onCurrentHueChanged: canvas.requestPaint() onCurrentHueChanged: canvas.requestPaint()
onDrawingChanged: syncFromPenColor() onDrawingChanged: syncFromPenColor()
@@ -111,7 +103,7 @@ Item {
root.syncFromPenColor(); root.syncFromPenColor();
} }
target: root.drawing.drawingState target: root.drawing
} }
Canvas { Canvas {
@@ -149,21 +141,6 @@ Item {
} }
} }
CustomRect {
anchors.centerIn: parent
color: root.drawing?.drawingState.penColor
implicitHeight: implicitWidth
implicitWidth: canvas.height - root.handleSize - Appearance.padding.extraLarge * 2
radius: Appearance.rounding.full
Behavior on color {
enabled: false
CAnim {
}
}
}
Item { Item {
id: handle id: handle
@@ -197,7 +174,7 @@ Item {
Rectangle { Rectangle {
anchors.centerIn: parent anchors.centerIn: parent
color: Qt.hsla(root.currentHue, 1.0, 0.5, 1.0) color: root.drawing ? root.drawing.penColor : Qt.hsla(root.currentHue, 1.0, 0.5, 1.0)
height: width height: width
radius: width / 2 radius: width / 2
width: parent.width - 12 width: parent.width - 12
+3 -3
View File
@@ -23,10 +23,10 @@ Button {
} }
StateLayer { StateLayer {
radius: control.radius function onClicked(): void {
onClicked: {
control.clicked(); control.clicked();
} }
radius: control.radius
} }
} }
-13
View File
@@ -1,13 +0,0 @@
import Quickshell.Widgets
import QtQuick
ClippingWrapperRectangle {
id: root
color: "transparent"
Behavior on color {
CAnim {
}
}
}
-2
View File
@@ -1,10 +1,8 @@
import QtQuick import QtQuick
import qs.Helpers
Flickable { Flickable {
id: root id: root
interactive: !Visibilities.getForActive().isDrawing
maximumFlickVelocity: 3000 maximumFlickVelocity: 3000
rebound: Transition { rebound: Transition {
-20
View File
@@ -1,33 +1,13 @@
import QtQuick import QtQuick
import qs.Helpers
ListView { ListView {
id: root id: root
property bool doneFakeFlick
interactive: !Visibilities.getForActive().isDrawing
maximumFlickVelocity: 3000 maximumFlickVelocity: 3000
rebound: Transition { rebound: Transition {
onRunningChanged: {
if (!running && !root.doneFakeFlick) {
root.doneFakeFlick = true;
root.flick(1, 1);
root.flick(-1, -1);
Qt.callLater(() => root.cancelFlick());
}
}
Anim { Anim {
properties: "x,y" properties: "x,y"
} }
} }
Timer {
interval: 10
running: root.doneFakeFlick
onTriggered: root.doneFakeFlick = false
}
} }
-237
View File
@@ -1,237 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Templates
import ZShell
import ZShell.Components
import ZShell.Internal
import qs.Config
ProgressBar {
id: root
enum IndeterminateAnimState {
Running,
Completing,
Stopped
}
property color bgColor: DynamicColors.palette.m3secondaryContainer
property color fgColor: DynamicColors.palette.m3primary
property int indeterminateAnimState: CustomProgressBar.Stopped
property real waveAmplitude: 0.5
property int waveDuration: 1000
property int waveFrequency: 6
property bool wavePaused
property bool wavy
function toBounds(startFrac: real, endFrac: real, gapSize: real): point {
startFrac = ZUtils.clamp(startFrac, 0, 1);
endFrac = ZUtils.clamp(endFrac, 0, 1);
// Ramp down gap size
const GAP_RAMP_DOWN_THRESHOLD = 0.01;
gapSize += height / 2;
const startGapSize = (gapSize * ZUtils.clamp(startFrac, 0, GAP_RAMP_DOWN_THRESHOLD) / GAP_RAMP_DOWN_THRESHOLD);
const endGapSize = (gapSize * (1 - ZUtils.clamp(endFrac, 1 - GAP_RAMP_DOWN_THRESHOLD, 1)) / GAP_RAMP_DOWN_THRESHOLD);
const start = width * startFrac + startGapSize;
const end = width * endFrac - endGapSize;
return start >= end ? Qt.point(0, 0) : Qt.point(start, end);
}
function updateIAnimState(): void {
if (indeterminate) {
manager.completeEndProgress = 0;
indeterminateAnimState = CustomProgressBar.Running;
} else if (indeterminateAnimState === CustomProgressBar.Running) {
indeterminateAnimState = CustomProgressBar.Completing;
}
}
implicitHeight: 4
implicitWidth: 200
contentItem: Loader {
anchors.fill: parent
asynchronous: true
sourceComponent: root.indeterminate || root.indeterminateAnimState !== CustomProgressBar.Stopped ? indeterminateComp : determinateComp
}
Component.onCompleted: updateIAnimState()
onIndeterminateChanged: updateIAnimState()
LinearIndicatorManager {
id: manager
gap: Appearance.spacing.extraSmall
Anim on completeEndProgress {
duration: manager.completeEndDuration
running: root.indeterminateAnimState === CustomProgressBar.Completing
to: 1
onFinished: {
if (root.indeterminateAnimState === CustomProgressBar.Completing)
root.indeterminateAnimState = CustomProgressBar.Stopped;
}
}
Anim on progress {
duration: manager.duration
easing.type: Easing.Linear
from: 0
loops: Animation.Infinite
running: root.indeterminateAnimState !== CustomProgressBar.Stopped
to: 1
}
}
Component {
id: determinateComp
Item {
Line {
id: remaining
anchors.right: parent.right
implicitWidth: parent.width - wave.implicitWidth - Appearance.spacing.extraSmall
}
Line {
property real implicitSize
anchors.right: parent.right
anchors.rightMargin: (parent.height - implicitHeight) / 2
anchors.verticalCenter: parent.verticalCenter
color: root.fgColor
implicitHeight: implicitSize
implicitWidth: implicitSize
radius: Appearance.rounding.full
Behavior on implicitSize {
Anim {
type: Anim.FastSpatial
}
}
Component.onCompleted: implicitSize = Qt.binding(() => parent.width - wave.implicitWidth < parent.height ? parent.height : 4)
}
Wave {
id: wave
anchors.left: parent.left
Behavior on implicitWidth {
Anim {
}
}
Component.onCompleted: implicitWidth = Qt.binding(() => parent.width * root.visualPosition)
}
}
}
Component {
id: indeterminateComp
Item {
id: content
Line {
bounds: {
const i = manager.activeIndicators[0]; // qmllint disable unresolved-type
return i ? root.toBounds(0, i.startFraction, i.gapSize / 2) : Qt.point(0, 0);
}
}
Line {
bounds: {
const i = manager.activeIndicators[manager.activeIndicators.length - 1]; // qmllint disable unresolved-type
return i ? root.toBounds(i.endFraction, 1, i.gapSize / 2) : Qt.point(0, 0);
}
}
Instantiator {
model: Math.max(manager.activeIndicators.length, 1) - 1 // qmllint disable unresolved-type
delegate: Line {
readonly property LinearIndicatorSegment cur: manager.activeIndicators[index] // qmllint disable unresolved-type
required property int index
readonly property LinearIndicatorSegment next: manager.activeIndicators[index + 1 % manager.activeIndicators.length] // qmllint disable unresolved-type
bounds: root.toBounds(cur.endFraction, next.startFraction, cur.gapSize / 2)
}
onObjectAdded: (_, obj) => content.data.push(obj)
onObjectRemoved: (_, obj) => {
const idx = content.data.indexOf(obj);
if (idx !== -1)
content.data.splice(idx, 1);
}
}
Instantiator {
model: manager.activeIndicators // qmllint disable unresolved-type
delegate: Wave {
readonly property point bounds: root.toBounds(modelData.startFraction, modelData.endFraction, modelData.gapSize / 2)
required property LinearIndicatorSegment modelData
color: root.fgColor
implicitWidth: bounds.y - bounds.x
x: bounds.x
}
onObjectAdded: (_, obj) => content.data.push(obj)
onObjectRemoved: (_, obj) => {
const idx = content.data.indexOf(obj);
if (idx !== -1)
content.data.splice(idx, 1);
}
}
}
}
component Line: CustomRect {
property point bounds
anchors.verticalCenter: parent.verticalCenter
color: root.bgColor
implicitHeight: parent.height
implicitWidth: bounds.y - bounds.x
radius: Appearance.rounding.full
x: bounds.x
}
component Wave: WavyLine {
id: wave
amplitudeMultiplier: root.wavy ? root.waveAmplitude : 0
anchors.verticalCenter: parent.verticalCenter
color: root.fgColor
frequency: root.waveFrequency
fullLength: parent.width
implicitHeight: lineWidth * amplitudeMultiplier * 2 + lineWidth
lineWidth: parent.height
startX: x
Behavior on amplitudeMultiplier {
Anim {
type: Anim.DefaultEffects
}
}
Behavior on color {
CAnim {
}
}
Anim on waveProgress {
duration: root.waveDuration
easing.type: Easing.Linear
from: 0
loops: Animation.Infinite
paused: wave.amplitudeMultiplier === 0 || root.wavePaused
running: true
to: 1
}
}
}
+4 -4
View File
@@ -33,13 +33,13 @@ RadioButton {
} }
StateLayer { StateLayer {
function onClicked(): void {
root.click();
}
anchors.margins: -7 anchors.margins: -7
color: root.checked ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3primary color: root.checked ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3primary
z: -1 z: -1
onClicked: {
root.click();
}
} }
CustomRect { CustomRect {
+133 -106
View File
@@ -1,25 +1,49 @@
import qs.Config
import QtQuick import QtQuick
import QtQuick.Templates import QtQuick.Templates
import qs.Helpers
import qs.Config
ScrollBar { ScrollBar {
id: root id: root
readonly property real effectiveSize: Math.max(nonAnimHeight, root.minimumSize) property bool _updatingFromFlickable: false
readonly property real effectiveTravel: Math.max(0, 1 - root.effectiveSize) property bool _updatingFromUser: false
property bool animating
required property Flickable flickable required property Flickable flickable
readonly property real nonAnimHeight: flickable.height / flickable.contentHeight property real nonAnimPosition
readonly property real nonAnimY: flickable.contentY / flickable.contentHeight
readonly property real rawTravel: Math.max(0, 1 - root.nonAnimHeight)
readonly property bool reversed: flickable instanceof ListView && flickable.verticalLayoutDirection === ListView.BottomToTop
property bool shouldBeActive property bool shouldBeActive
readonly property real travelScale: root.rawTravel > 0 ? root.effectiveTravel / root.rawTravel : 0
enabled: !Visibilities.getForActive().isDrawing implicitWidth: 8
implicitWidth: Appearance.padding.extraSmall * 2
contentItem: Item { contentItem: CustomRect {
anchors.left: parent.left
anchors.right: parent.right
color: DynamicColors.palette.m3secondary
opacity: {
if (root.size === 1)
return 0;
if (fullMouse.pressed)
return 1;
if (mouse.containsMouse)
return 0.8;
if (root.policy === ScrollBar.AlwaysOn || root.shouldBeActive)
return 0.6;
return 0;
}
radius: Appearance.rounding.full
Behavior on opacity {
Anim {
}
}
MouseArea {
id: mouse
acceptedButtons: Qt.NoButton
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
}
} }
Behavior on position { Behavior on position {
enabled: !fullMouse.pressed enabled: !fullMouse.pressed
@@ -28,15 +52,58 @@ ScrollBar {
} }
} }
Component.onCompleted: {
if (flickable) {
const contentHeight = flickable.contentHeight;
const height = flickable.height;
if (contentHeight > height) {
nonAnimPosition = Math.max(0, Math.min(1, flickable.contentY / (contentHeight - height)));
}
}
}
onHoveredChanged: { onHoveredChanged: {
if (hovered) if (hovered)
shouldBeActive = hovered; shouldBeActive = true;
else else
hideDelay.restart(); shouldBeActive = flickable.moving;
}
// Sync nonAnimPosition with Qt's automatic position binding
onPositionChanged: {
if (_updatingFromUser) {
_updatingFromUser = false;
return;
}
if (position === nonAnimPosition) {
animating = false;
return;
}
if (!animating && !_updatingFromFlickable && !fullMouse.pressed) {
nonAnimPosition = position;
}
}
// Sync nonAnimPosition with flickable when not animating
Connections {
function onContentYChanged() {
if (!animating && !fullMouse.pressed) {
_updatingFromFlickable = true;
const contentHeight = flickable.contentHeight;
const height = flickable.height;
if (contentHeight > height) {
nonAnimPosition = Math.max(0, Math.min(1, flickable.contentY / (contentHeight - height)));
} else {
nonAnimPosition = 0;
}
_updatingFromFlickable = false;
}
}
target: flickable
} }
Connections { Connections {
function onMovingChanged() { function onMovingChanged(): void {
if (root.flickable.moving) if (root.flickable.moving)
root.shouldBeActive = true; root.shouldBeActive = true;
else else
@@ -46,57 +113,6 @@ ScrollBar {
target: root.flickable target: root.flickable
} }
CustomClippingRect {
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.top: parent.top
implicitWidth: handle.implicitWidth
radius: Appearance.rounding.full
CustomRect {
id: handle
anchors.right: parent.right
color: DynamicColors.palette.m3secondary
implicitHeight: root.height * root.effectiveSize
implicitWidth: fullMouse.pressed || fullMouse.containsMouse ? Appearance.padding.extraSmall * 2 : Appearance.padding.extraSmall
opacity: {
if (!root.enabled)
return 0;
if (root.size === 1)
return 0;
if (fullMouse.pressed)
return 1;
if (fullMouse.containsMouse)
return 0.8;
if (root.policy === ScrollBar.AlwaysOn || root.shouldBeActive)
return 0.6;
return 0;
}
radius: Appearance.rounding.full
y: root.reversed ? root.height * (1 + root.nonAnimY) * root.travelScale : root.height * root.nonAnimY * root.travelScale
Behavior on implicitWidth {
Anim {
}
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
MouseArea {
id: mouse
acceptedButtons: Qt.NoButton
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
}
}
}
Timer { Timer {
id: hideDelay id: hideDelay
@@ -108,55 +124,66 @@ ScrollBar {
CustomMouseArea { CustomMouseArea {
id: fullMouse id: fullMouse
property real pressOffset: 0 function onWheel(event: WheelEvent): void {
root.animating = true;
function contentYFromThumbTop(thumbTop) { root._updatingFromUser = true;
var visualPos = root.effectiveTravel > 0 ? thumbTop / root.travelScale : 0; let newPos = root.nonAnimPosition;
if (event.angleDelta.y > 0)
return root.reversed ? (visualPos - 1) * root.flickable.contentHeight : visualPos * root.flickable.contentHeight; newPos = Math.max(0, root.nonAnimPosition - 0.1);
} else if (event.angleDelta.y < 0)
newPos = Math.min(1 - root.size, root.nonAnimPosition + 0.1);
function updateFromEvent(event) { root.nonAnimPosition = newPos;
var posInTrack = event.y / root.height; // Update flickable position
var thumbTop = posInTrack - pressOffset; // Map scrollbar position [0, 1-size] to contentY [0, maxContentY]
thumbTop = Math.max(0, Math.min(root.effectiveTravel, thumbTop)); if (root.flickable) {
const contentHeight = root.flickable.contentHeight;
root.flickable.contentY = contentYFromThumbTop(thumbTop); const height = root.flickable.height;
} if (contentHeight > height) {
const maxContentY = contentHeight - height;
function visualThumbTop() { const maxPos = 1 - root.size;
const visualPos = root.reversed ? (1 + root.nonAnimY) : root.nonAnimY; const contentY = maxPos > 0 ? (newPos / maxPos) * maxContentY : 0;
return visualPos * root.travelScale; root.flickable.contentY = Math.max(0, Math.min(maxContentY, contentY));
}
}
} }
anchors.fill: parent anchors.fill: parent
cursorShape: undefined
hoverEnabled: true
preventStealing: true preventStealing: true
onPositionChanged: event => { onPositionChanged: event => {
if (pressed) root._updatingFromUser = true;
updateFromEvent(event); const newPos = Math.max(0, Math.min(1 - root.size, event.y / root.height - root.size / 2));
root.nonAnimPosition = newPos;
// Update flickable position
// Map scrollbar position [0, 1-size] to contentY [0, maxContentY]
if (root.flickable) {
const contentHeight = root.flickable.contentHeight;
const height = root.flickable.height;
if (contentHeight > height) {
const maxContentY = contentHeight - height;
const maxPos = 1 - root.size;
const contentY = maxPos > 0 ? (newPos / maxPos) * maxContentY : 0;
root.flickable.contentY = Math.max(0, Math.min(maxContentY, contentY));
}
}
} }
onPressed: event => { onPressed: event => {
var currentTop = visualThumbTop(); root.animating = true;
var currentBottom = currentTop + root.effectiveSize; root._updatingFromUser = true;
var clickPos = event.y / root.height; const newPos = Math.max(0, Math.min(1 - root.size, event.y / root.height - root.size / 2));
root.nonAnimPosition = newPos;
var clickedInsideThumb = clickPos >= currentTop && clickPos <= currentBottom; // Update flickable position
// Map scrollbar position [0, 1-size] to contentY [0, maxContentY]
if (clickedInsideThumb) { if (root.flickable) {
pressOffset = clickPos - currentTop; const contentHeight = root.flickable.contentHeight;
} else { const height = root.flickable.height;
pressOffset = root.effectiveSize / 2; if (contentHeight > height) {
const maxContentY = contentHeight - height;
const maxPos = 1 - root.size;
const contentY = maxPos > 0 ? (newPos / maxPos) * maxContentY : 0;
root.flickable.contentY = Math.max(0, Math.min(maxContentY, contentY));
}
} }
updateFromEvent(event);
}
onWheel: event => {
var delta = event.angleDelta.y > 0 ? -0.1 : 0.1;
var newPos = Math.max(0, Math.min(1 - root.size, root.position + delta));
root.position = newPos;
} }
} }
} }
+31 -181
View File
@@ -1,201 +1,51 @@
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Templates import QtQuick.Templates
import ZShell.Components
import ZShell
import qs.Components
import qs.Config import qs.Config
Slider { Slider {
id: root id: root
property bool animateWave property color color: DynamicColors.palette.m3primary
property color bgColor: enabled ? DynamicColors.palette.m3secondaryContainer : Qt.alpha(DynamicColors.palette.m3onSurface, 0.1)
property color fgColor: enabled ? DynamicColors.palette.m3primary : Qt.alpha(DynamicColors.palette.m3onSurface, 0.38)
property real filledWidth
property alias inset: inset
property color insetColor: enabled ? (inset.attached ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onPrimary) : DynamicColors.palette.m3onSurface
property string insetIcon: ""
property real pos: visualPosition
property int radius: Appearance.rounding.small
property int waveDuration: 1000
property real waveFrequency: 6
property bool wavy
signal interaction(v: real)
implicitHeight: 12
implicitWidth: 200
contentItem: Item {
anchors.fill: parent
background: Item {
CustomRect { CustomRect {
id: remaining
anchors.left: handle.right
anchors.leftMargin: Appearance.spacing.extraSmall
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
bottomLeftRadius: Appearance.rounding.extraSmall / 2
color: root.bgColor
implicitHeight: parent.height * (parent.height <= 12 ? opacity : Math.min(opacity * 2, 1))
opacity: Math.min(width, 12) / 12
radius: root.radius
topLeftRadius: Appearance.rounding.extraSmall / 2
}
CustomRect {
anchors.right: parent.right
anchors.rightMargin: 4 * remaining.opacity
anchors.verticalCenter: parent.verticalCenter
color: root.fgColor
implicitHeight: 4 * remaining.opacity
implicitWidth: implicitHeight
opacity: remaining.opacity
radius: Appearance.rounding.full
}
CustomRect {
id: handle
anchors.left: filled.right
anchors.leftMargin: Appearance.spacing.extraSmall
anchors.verticalCenter: parent.verticalCenter
color: root.fgColor
implicitHeight: {
const t = ZUtils.clamp((parent.height - 12) / 16, 0, 1);
const lerp = (a, b) => a + (b - a) * t;
return parent.height * (mouse.pressed ? lerp(3.5, 1.5) : lerp(3, 1.2));
}
implicitWidth: 4
radius: Appearance.rounding.full
Behavior on implicitHeight {
Anim {
type: Anim.FastSpatial
}
}
}
Loader {
id: filled
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
asynchronous: true
sourceComponent: root.wavy ? waveComp : lineComp
}
MaterialIcon {
id: inset
readonly property bool attached: root.width ? (root.width - handle.implicitWidth - handle.anchors.leftMargin) * root.pos < inset.paintedWidth + Appearance.spacing.extraSmall * 2 : false
property real dockT: attached ? 1 : 0
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: root.implicitHeight / 6
anchors.left: parent.left
anchors.top: parent.top anchors.top: parent.top
color: root.insetColor anchors.topMargin: root.implicitHeight / 6
font.pixelSize: Appearance.font.size.smaller * 2 bottomRightRadius: root.implicitHeight / 6
text: root.insetIcon color: root.color
verticalAlignment: Text.AlignVCenter implicitWidth: root.handle.x - root.implicitHeight / 6
visible: !root.wavy && root.insetIcon !== "" radius: root.implicitHeight / 6
x: Appearance.spacing.extraSmall + dockT * (handle.x + Appearance.spacing.small) topRightRadius: root.implicitHeight / 6
Behavior on dockT {
Anim {
}
}
} }
Component { CustomRect {
id: lineComp anchors.bottom: parent.bottom
anchors.bottomMargin: root.implicitHeight / 6
CustomRect { anchors.right: parent.right
bottomRightRadius: Appearance.rounding.extraSmall / 2 anchors.top: parent.top
color: root.fgColor anchors.topMargin: root.implicitHeight / 6
implicitHeight: root.height bottomLeftRadius: root.implicitHeight / 6
implicitWidth: root.filledWidth color: DynamicColors.tPalette.m3surfaceContainerHighest
radius: root.radius implicitWidth: parent.width - root.handle.x - root.handle.implicitWidth - root.implicitHeight / 6
topRightRadius: Appearance.rounding.extraSmall / 2 radius: root.implicitHeight / 6
} topLeftRadius: root.implicitHeight / 6
}
Component {
id: waveComp
WavyLine {
color: root.fgColor
frequency: root.waveFrequency
fullLength: root.width - handle.implicitWidth - handle.anchors.leftMargin
implicitHeight: lineWidth * amplitudeMultiplier * 2 + lineWidth
implicitWidth: root.filledWidth
lineWidth: root.height * 0.7
startX: x
Behavior on color {
CAnim {
}
}
Anim on waveProgress {
duration: root.waveDuration
easing.type: Easing.Linear
from: 0
loops: Animation.Infinite
paused: !root.animateWave
running: true
to: 1
}
}
} }
} }
Behavior on filledWidth { handle: CustomRect {
id: widthBehavior
Anim {
}
}
Component.onCompleted: {
filledWidth = Qt.binding(() => (width - handle.implicitWidth - handle.anchors.leftMargin) * pos);
}
Binding {
id: posBinding
property: "pos"
target: root
value: ZUtils.clamp(mouse.pressStartPos + mouse.dragMovement, 0, 1)
when: mouse.pressed
}
MouseArea {
id: mouse
property real dragMovement
property real pressStartPos
property real pressStartX
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
implicitHeight: handle.implicitHeight color: root.color
preventStealing: true implicitHeight: root.implicitHeight
implicitWidth: root.implicitHeight / 4.5
radius: Appearance.rounding.full
x: root.visualPosition * root.availableWidth - implicitWidth / 2
onPositionChanged: e => { MouseArea {
dragMovement = (e.x - pressStartX) / width; acceptedButtons: Qt.NoButton
root.interaction(posBinding.value); anchors.fill: parent
} cursorShape: Qt.PointingHandCursor
onPressed: e => {
widthBehavior.enabled = false;
pressStartX = e.x;
pressStartPos = root.visualPosition;
}
onReleased: e => {
root.interaction(posBinding.value);
widthBehavior.enabled = true;
dragMovement = 0;
} }
} }
} }
+8 -6
View File
@@ -94,9 +94,7 @@ RowLayout {
StateLayer { StateLayer {
id: upState id: upState
color: DynamicColors.palette.m3onPrimary function onClicked(): void {
onClicked: {
let newValue = Math.min(root.max, root.value + root.step); let newValue = Math.min(root.max, root.value + root.step);
// Round to avoid floating point precision errors // Round to avoid floating point precision errors
const decimals = root.step < 1 ? Math.max(1, Math.ceil(-Math.log10(root.step))) : 0; const decimals = root.step < 1 ? Math.max(1, Math.ceil(-Math.log10(root.step))) : 0;
@@ -105,6 +103,9 @@ RowLayout {
root.displayText = newValue.toString(); root.displayText = newValue.toString();
root.valueModified(newValue); root.valueModified(newValue);
} }
color: DynamicColors.palette.m3onPrimary
onPressAndHold: timer.start() onPressAndHold: timer.start()
onReleased: timer.stop() onReleased: timer.stop()
} }
@@ -127,9 +128,7 @@ RowLayout {
StateLayer { StateLayer {
id: downState id: downState
color: DynamicColors.palette.m3onPrimary function onClicked(): void {
onClicked: {
let newValue = Math.max(root.min, root.value - root.step); let newValue = Math.max(root.min, root.value - root.step);
// Round to avoid floating point precision errors // Round to avoid floating point precision errors
const decimals = root.step < 1 ? Math.max(1, Math.ceil(-Math.log10(root.step))) : 0; const decimals = root.step < 1 ? Math.max(1, Math.ceil(-Math.log10(root.step))) : 0;
@@ -138,6 +137,9 @@ RowLayout {
root.displayText = newValue.toString(); root.displayText = newValue.toString();
root.valueModified(newValue); root.valueModified(newValue);
} }
color: DynamicColors.palette.m3onPrimary
onPressAndHold: timer.start() onPressAndHold: timer.start()
onReleased: timer.stop() onReleased: timer.stop()
} }
+57 -27
View File
@@ -1,6 +1,7 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import qs.Config import qs.Config
import qs.Helpers
Row { Row {
id: root id: root
@@ -12,44 +13,61 @@ Row {
property alias active: menu.active property alias active: menu.active
property color color: type == CustomSplitButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondaryContainer property color color: type == CustomSplitButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondaryContainer
property bool disabled
property color disabledColor: Qt.alpha(DynamicColors.palette.m3onSurface, 0.1) property color disabledColor: Qt.alpha(DynamicColors.palette.m3onSurface, 0.1)
property color disabledTextColor: Qt.alpha(DynamicColors.palette.m3onSurface, 0.38) property color disabledTextColor: Qt.alpha(DynamicColors.palette.m3onSurface, 0.38)
readonly property alias expandBtn: expandBtn
property alias expanded: menu.expanded property alias expanded: menu.expanded
property string fallbackIcon property string fallbackIcon
property string fallbackText property string fallbackText
property real horizontalPadding: Appearance.padding.larger property real horizontalPadding: Appearance.padding.normal
readonly property alias iconLabel: iconLabel property alias iconLabel: iconLabel
readonly property alias label: label property alias label: label
readonly property alias menu: menu property alias menu: menu
property alias menuItems: menu.items property alias menuItems: menu.items
property bool menuOnTop property bool menuOnTop
property real minLeftWidth property alias stateLayer: stateLayer
readonly property alias stateLayer: stateLayer
property color textColor: type == CustomSplitButton.Filled ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSecondaryContainer property color textColor: type == CustomSplitButton.Filled ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSecondaryContainer
readonly property alias textRow: textRow
property int type: CustomSplitButton.Filled property int type: CustomSplitButton.Filled
property real verticalPadding: Appearance.padding.small property real verticalPadding: Appearance.padding.smaller
spacing: Math.floor(Appearance.spacing.extraSmall) function closeDropdown(): void {
SettingsDropdowns.close(menu);
}
function openDropdown(): void {
SettingsDropdowns.open(menu, root);
}
function toggleDropdown(): void {
SettingsDropdowns.toggle(menu, root);
}
spacing: Math.floor(Appearance.spacing.small / 2)
onExpandedChanged: {
if (!expanded)
SettingsDropdowns.forget(menu);
}
CustomRect { CustomRect {
bottomRightRadius: Appearance.rounding.small / 2 bottomRightRadius: Appearance.rounding.small / 2
color: !root.enabled ? root.disabledColor : root.color color: !root.enabled ? root.disabledColor : root.color
implicitHeight: expandBtn.implicitHeight implicitHeight: expandBtn.implicitHeight
implicitWidth: Math.max(root.minLeftWidth, textRow.implicitWidth + root.horizontalPadding * 2) implicitWidth: textRow.implicitWidth + root.horizontalPadding * 2
radius: implicitHeight / 2 * Math.min(1, Appearance.rounding.scale) radius: implicitHeight / 2 * Math.min(1, Appearance.rounding.scale)
topRightRadius: Appearance.rounding.small / 2 topRightRadius: Appearance.rounding.small / 2
StateLayer { StateLayer {
id: stateLayer id: stateLayer
bottomRightRadius: parent.bottomRightRadius function onClicked(): void {
color: root.textColor root.active?.clicked();
enabled: root.enabled }
topRightRadius: parent.topRightRadius
onClicked: root.active?.clicked() color: root.textColor
disabled: !root.enabled
rect.bottomRightRadius: parent.bottomRightRadius
rect.topRightRadius: parent.topRightRadius
} }
RowLayout { RowLayout {
@@ -81,7 +99,7 @@ Row {
Behavior on Layout.preferredWidth { Behavior on Layout.preferredWidth {
Anim { Anim {
type: Anim.Emphasized easing.bezierCurve: Appearance.anim.curves.emphasized
} }
} }
} }
@@ -108,12 +126,14 @@ Row {
StateLayer { StateLayer {
id: expandStateLayer id: expandStateLayer
function onClicked(): void {
root.toggleDropdown();
}
color: root.textColor color: root.textColor
enabled: root.enabled disabled: !root.enabled
rect.bottomLeftRadius: parent.bottomLeftRadius rect.bottomLeftRadius: parent.bottomLeftRadius
rect.topLeftRadius: parent.topLeftRadius rect.topLeftRadius: parent.topLeftRadius
onClicked: root.expanded = !root.expanded
} }
MaterialIcon { MaterialIcon {
@@ -134,14 +154,24 @@ Row {
} }
} }
} }
}
Menu { Menu {
id: menu id: menu
attachSideY: root.menuOnTop ? Menu.Top : Menu.Bottom anchors.bottomMargin: Appearance.spacing.small
attachTo: expandBtn anchors.right: parent.right
marginY: Appearance.spacing.small * (root.menuOnTop ? -1 : 1) anchors.top: parent.bottom
thisSideY: root.menuOnTop ? Menu.Bottom : Menu.Top anchors.topMargin: Appearance.spacing.small
states: State {
when: root.menuOnTop
AnchorChanges {
anchors.bottom: expandBtn.top
anchors.top: undefined
target: menu
}
}
}
} }
} }
+6 -3
View File
@@ -9,6 +9,7 @@ Item {
property alias active: splitButton.active property alias active: splitButton.active
property alias buttonAlias: splitButton property alias buttonAlias: splitButton
property bool enabled: true
property alias expanded: splitButton.expanded property alias expanded: splitButton.expanded
property int expandedZ: 100 property int expandedZ: 100
required property string label required property string label
@@ -24,7 +25,7 @@ Item {
implicitHeight: row.implicitHeight + Appearance.padding.smaller * 2 implicitHeight: row.implicitHeight + Appearance.padding.smaller * 2
opacity: shouldBeActive ? 1 : 0 opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8 scale: shouldBeActive ? 1 : 0.8
z: splitButton.menu.implicitHeight > 0 ? expandedZ : 1 z: root.expanded ? expandedZ : -1
Behavior on opacity { Behavior on opacity {
Anim { Anim {
@@ -49,6 +50,7 @@ Item {
color: root.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSurfaceVariant color: root.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.larger font.pointSize: Appearance.font.size.larger
text: root.label text: root.label
z: root.expanded ? root.expandedZ : -1
} }
CustomSplitButton { CustomSplitButton {
@@ -56,13 +58,14 @@ Item {
enabled: root.enabled enabled: root.enabled
type: CustomSplitButton.Filled type: CustomSplitButton.Filled
z: 2 z: root.expanded ? root.expandedZ : -1
menu.onItemSelected: item => { menu.onItemSelected: item => {
root.selected(item); root.selected(item);
splitButton.closeDropdown();
} }
stateLayer.onClicked: { stateLayer.onClicked: {
splitButton.expanded = !splitButton.expanded; splitButton.toggleDropdown();
} }
} }
} }
+12 -14
View File
@@ -1,6 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Shapes
import QtQuick.Templates import QtQuick.Templates
import QtQuick.Shapes
import qs.Config import qs.Config
Switch { Switch {
@@ -13,28 +13,26 @@ Switch {
indicator: CustomRect { indicator: CustomRect {
color: root.checked && root.enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, root.cLayer) color: root.checked && root.enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, root.cLayer)
implicitHeight: Appearance.font.size.medium + Appearance.padding.normal * 2 implicitHeight: 13 + 7 * 2
implicitWidth: implicitHeight * 1.7 implicitWidth: implicitHeight * 1.7
radius: Appearance.rounding.full radius: Appearance.rounding.full
CustomRect { CustomRect {
readonly property real nonAnimWidth: root.pressed ? implicitHeight * 1.2 : implicitHeight readonly property real nonAnimWidth: root.pressed ? implicitHeight * 1.3 : implicitHeight
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: root.checked && root.enabled ? DynamicColors.palette.m3onPrimary : DynamicColors.layer(DynamicColors.palette.m3outline, root.cLayer + 1) color: root.checked && root.enabled ? DynamicColors.palette.m3onPrimary : DynamicColors.layer(DynamicColors.palette.m3outline, root.cLayer + 1)
implicitHeight: parent.implicitHeight - Appearance.padding.extraSmall implicitHeight: parent.implicitHeight - 10
implicitWidth: nonAnimWidth implicitWidth: nonAnimWidth
radius: Appearance.rounding.full radius: Appearance.rounding.full
x: root.checked ? parent.implicitWidth - nonAnimWidth - Appearance.padding.extraSmall / 2 : Appearance.padding.extraSmall / 2 x: root.checked ? parent.implicitWidth - nonAnimWidth - 10 / 2 : 10 / 2
Behavior on implicitWidth { Behavior on implicitWidth {
Anim { Anim {
type: Anim.FastSpatial
} }
} }
Behavior on x { Behavior on x {
Anim { Anim {
type: Anim.FastSpatial
} }
} }
@@ -46,7 +44,6 @@ Switch {
Behavior on opacity { Behavior on opacity {
Anim { Anim {
type: Anim.DefaultEffects
} }
} }
} }
@@ -66,14 +63,14 @@ Switch {
} }
property point end2: { property point end2: {
if (root.pressed) if (root.pressed)
return Qt.point(width * 0.8, height / 2); return Qt.point(width, height / 2);
if (root.checked) if (root.checked)
return Qt.point(width * 0.85, height * 0.2); return Qt.point(width * 0.85, height * 0.2);
return Qt.point(width * 0.85, height * 0.15); return Qt.point(width * 0.85, height * 0.15);
} }
property point start1: { property point start1: {
if (root.pressed) if (root.pressed)
return Qt.point(width * 0.2, height / 2); return Qt.point(width * 0.1, height / 2);
if (root.checked) if (root.checked)
return Qt.point(width * 0.15, height / 2); return Qt.point(width * 0.15, height / 2);
return Qt.point(width * 0.15, height * 0.15); return Qt.point(width * 0.15, height * 0.15);
@@ -91,7 +88,7 @@ Switch {
anchors.centerIn: parent anchors.centerIn: parent
asynchronous: true asynchronous: true
height: parent.implicitHeight - Appearance.padding.larger height: parent.implicitHeight - Appearance.padding.small * 2
preferredRendererType: Shape.CurveRenderer preferredRendererType: Shape.CurveRenderer
width: height width: height
@@ -113,7 +110,7 @@ Switch {
} }
ShapePath { ShapePath {
capStyle: ShapePath.RoundCap capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
fillColor: "transparent" fillColor: "transparent"
startX: icon.start1.x startX: icon.start1.x
startY: icon.start1.y startY: icon.start1.y
@@ -151,7 +148,8 @@ Switch {
} }
component PropAnim: PropertyAnimation { component PropAnim: PropertyAnimation {
duration: Appearance.anim.durations.expressiveFastSpatial duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial easing.bezierCurve: MaterialEasing.expressiveEffects
easing.type: Easing.BezierSpline
} }
} }
+6 -7
View File
@@ -27,25 +27,24 @@ Text {
enabled: root.animate enabled: root.animate
SequentialAnimation { SequentialAnimation {
TAnim { Anim {
target: root easing.bezierCurve: MaterialEasing.standardAccel
to: root.animateFrom to: root.animateFrom
type: Anim.FastEffects
} }
PropertyAction { PropertyAction {
} }
TAnim { Anim {
target: root easing.bezierCurve: MaterialEasing.standardDecel
to: root.animateTo to: root.animateTo
type: Anim.DefaultEffects
} }
} }
} }
component TAnim: Anim { component Anim: NumberAnimation {
duration: root.animateDuration / 2 duration: root.animateDuration / 2
easing.type: Easing.BezierSpline
properties: root.animateProp.split(",").length > 1 ? root.animateProp : "" properties: root.animateProp.split(",").length > 1 ? root.animateProp : ""
property: root.animateProp.split(",").length === 1 ? root.animateProp : "" property: root.animateProp.split(",").length === 1 ? root.animateProp : ""
target: root target: root
-2
View File
@@ -7,8 +7,6 @@ import qs.Config
TextField { TextField {
id: root id: root
property int cursorHeight: root.height
background: null background: null
color: DynamicColors.palette.m3onSurface color: DynamicColors.palette.m3onSurface
cursorVisible: !readOnly cursorVisible: !readOnly
+1 -2
View File
@@ -1,6 +1,6 @@
import qs.Config
import QtQuick import QtQuick
import QtQuick.Effects import QtQuick.Effects
import qs.Config
RectangularShadow { RectangularShadow {
property real dp: [0, 1, 3, 6, 8, 12][level] property real dp: [0, 1, 3, 6, 8, 12][level]
@@ -13,7 +13,6 @@ RectangularShadow {
Behavior on dp { Behavior on dp {
Anim { Anim {
type: Anim.SlowEffects
} }
} }
} }
+56 -23
View File
@@ -1,45 +1,78 @@
import QtQuick
import qs.Config import qs.Config
import QtQuick
ButtonBase { CustomRect {
id: root id: root
property alias icon: label.text enum Type {
readonly property alias label: label Filled,
Tonal,
activeColor: type === IconButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondary Text
activeOnColor: type === IconButton.Filled ? DynamicColors.palette.m3onPrimary : type === IconButton.Tonal ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3primary
implicitHeight: {
const h = label.implicitHeight + padding * 2;
if (h % 2 !== 0)
return h + 1;
return h;
} }
implicitWidth: implicitHeight
inactiveColor: { property color activeColour: type === IconButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondary
if (!isToggle && type === IconButton.Filled) property color activeOnColour: type === IconButton.Filled ? DynamicColors.palette.m3onPrimary : type === IconButton.Tonal ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3primary
property bool checked
property bool disabled
property color disabledColour: Qt.alpha(DynamicColors.palette.m3onSurface, 0.1)
property color disabledOnColour: Qt.alpha(DynamicColors.palette.m3onSurface, 0.38)
property alias font: label.font
property alias icon: label.text
property color inactiveColour: {
if (!toggle && type === IconButton.Filled)
return DynamicColors.palette.m3primary; return DynamicColors.palette.m3primary;
return type === IconButton.Filled ? DynamicColors.tPalette.m3surfaceContainer : DynamicColors.palette.m3secondaryContainer; return type === IconButton.Filled ? DynamicColors.tPalette.m3surfaceContainer : DynamicColors.palette.m3secondaryContainer;
} }
inactiveOnColor: { property color inactiveOnColour: {
if (!isToggle && type === IconButton.Filled) if (!toggle && type === IconButton.Filled)
return DynamicColors.palette.m3onPrimary; return DynamicColors.palette.m3onPrimary;
return type === IconButton.Tonal ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurfaceVariant; return type === IconButton.Tonal ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurfaceVariant;
} }
padding: type === IconButton.Text ? Appearance.padding.extraSmall / 2 : Appearance.padding.small property bool internalChecked
property alias label: label
property real padding: type === IconButton.Text ? 10 / 2 : 7
property alias radiusAnim: radiusAnim
property alias stateLayer: stateLayer
property bool toggle
property int type: IconButton.Filled
signal clicked
color: type === IconButton.Text ? "transparent" : disabled ? disabledColour : internalChecked ? activeColour : inactiveColour
implicitHeight: label.implicitHeight + padding * 2
implicitWidth: implicitHeight
radius: internalChecked ? 6 : (implicitHeight / 2 * Math.min(1, 1)) * Appearance.rounding.scale
Behavior on radius {
Anim {
id: radiusAnim
}
}
onCheckedChanged: internalChecked = checked
StateLayer {
id: stateLayer
function onClicked(): void {
if (root.toggle)
root.internalChecked = !root.internalChecked;
root.clicked();
}
color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour
disabled: root.disabled
}
MaterialIcon { MaterialIcon {
id: label id: label
anchors.centerIn: parent anchors.centerIn: parent
anchors.verticalCenterOffset: 1 color: root.disabled ? root.disabledOnColour : root.internalChecked ? root.activeOnColour : root.inactiveOnColour
color: root.onColor fill: !root.toggle || root.internalChecked ? 1 : 0
fill: !root.isToggle || root.internalChecked ? 1 : 0
font.pointSize: root.font.pointSize
Behavior on fill { Behavior on fill {
Anim { Anim {
type: Anim.DefaultEffects
} }
} }
} }
-70
View File
@@ -1,70 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Config
ButtonBase {
id: root
property alias icon: iconLabel.text
readonly property alias iconLabel: iconLabel
readonly property alias label: label
property alias text: label.text
activeColor: type === TextButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondary
activeOnColor: {
if (type === TextButton.Text)
return DynamicColors.palette.m3primary;
return type === TextButton.Filled ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSecondary;
}
horizontalPadding: Appearance.padding.larger
implicitHeight: row.implicitHeight + verticalPadding * 2
implicitWidth: row.implicitWidth + horizontalPadding * 2
inactiveColor: {
if (!isToggle && type === TextButton.Filled)
return DynamicColors.palette.m3primary;
return type === TextButton.Filled ? DynamicColors.tPalette.m3surfaceContainer : DynamicColors.palette.m3secondaryContainer;
}
inactiveOnColor: {
if (!isToggle && type === TextButton.Filled)
return DynamicColors.palette.m3onPrimary;
if (type === TextButton.Text)
return DynamicColors.palette.m3primary;
return type === TextButton.Filled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSecondaryContainer;
}
verticalPadding: Appearance.padding.small
RowLayout {
id: row
anchors.centerIn: parent
spacing: Appearance.spacing.small
MaterialIcon {
id: iconLabel
Layout.alignment: Qt.AlignVCenter
color: root.onColor
fill: root.internalChecked ? 1 : 0
font: {
const f = Qt.font(root.font);
f.pointSize = Math.round(root.font.pointSize * 1.2);
f.family = "Material Symbols Rounded";
return f;
}
Behavior on fill {
Anim {
type: Anim.DefaultEffects
}
}
}
CustomText {
id: label
Layout.alignment: Qt.AlignVCenter
color: root.onColor
font: root.font
}
}
}
+3 -14
View File
@@ -1,26 +1,15 @@
import qs.Config import qs.Config
CustomText { CustomText {
id: root property real fill
readonly property list<int> allowed: [20, 24, 40, 48]
property real fill: 0
property int grade: DynamicColors.light ? 0 : -25 property int grade: DynamicColors.light ? 0 : -25
readonly property int opsz: clampOpsz(optical)
property int optical: fontInfo.pixelSize
function clampOpsz(value): int {
return allowed.reduce((closest, current) => {
return Math.abs(current - value) < Math.abs(closest - value) ? current : closest;
});
}
font.family: "Material Symbols Rounded" font.family: "Material Symbols Rounded"
font.pointSize: Appearance.font.size.larger font.pointSize: Appearance.font.size.larger
font.variableAxes: ({ font.variableAxes: ({
FILL: fill, FILL: fill.toFixed(1),
GRAD: grade, GRAD: grade,
opsz: opsz, opsz: fontInfo.pixelSize,
wght: fontInfo.weight wght: fontInfo.weight
}) })
} }
+69 -145
View File
@@ -2,185 +2,109 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell
import qs.Config import qs.Config
import qs.Drawers
MouseArea { Elevation {
id: root id: root
enum Side {
Top,
Bottom,
Left,
Right
}
property MenuItem active: items[0] ?? null property MenuItem active: items[0] ?? null
property int attachSideX: Menu.Right
property int attachSideY: Menu.Bottom
required property Item attachTo
property bool expanded property bool expanded
property list<MenuItem> items property list<MenuItem> items
property real marginX
property real marginY
property int thisSideX: Menu.Right
property int thisSideY: Menu.Top
signal itemSelected(item: MenuItem) signal itemSelected(item: MenuItem)
anchors.fill: parent implicitHeight: root.expanded ? column.implicitHeight + Appearance.padding.small * 2 : 0
cursorShape: undefined implicitWidth: Math.max(200, column.implicitWidth)
enabled: expanded level: 2
layer.enabled: opacity < 1 opacity: root.expanded ? 1 : 0
opacity: expanded ? 1 : 0 radius: Appearance.rounding.normal
parent: {
const win = QsWindow.window;
const contentWin = win as Windows;
return contentWin ? contentWin.interactionWrapper : (win as QsWindow).contentItem;
}
Behavior on implicitHeight {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
Behavior on opacity { Behavior on opacity {
Anim { Anim {
type: Anim.DefaultEffects duration: Appearance.anim.durations.expressiveDefaultSpatial
} }
} }
onClicked: expanded = false CustomClippingRect {
onExpandedChanged: { anchors.fill: parent
const win = QsWindow.window; color: DynamicColors.palette.m3surfaceContainer
const contentWin = win as Windows; radius: parent.radius
if (expanded) {
contentWin.menuRegion.x = menu.x;
contentWin.menuRegion.y = menu.y;
contentWin.menuRegion.width = menu.width;
contentWin.menuRegion.height = menu.height;
} else {
contentWin.menuRegion.x = 0;
contentWin.menuRegion.y = 0;
contentWin.menuRegion.width = 0;
contentWin.menuRegion.height = 0;
}
}
TransformWatcher { ColumnLayout {
id: watcher id: column
a: root.parent anchors.left: parent.left
b: root.attachTo anchors.right: parent.right
} anchors.verticalCenter: parent.verticalCenter
spacing: 5
Elevation { Repeater {
id: menu model: root.items
implicitHeight: column.implicitHeight + column.anchors.margins * 2 CustomRect {
implicitWidth: Math.max(200, column.implicitWidth + column.anchors.margins * 2) id: item
level: 2
radius: Appearance.rounding.medium
x: {
watcher.transform;
const item = root.attachTo;
let off = root.attachSideX === Menu.Left ? 0 : item.width;
if (root.thisSideX === Menu.Right)
off -= width;
return item.mapToItem(root.parent, off, 0).x + root.marginX;
}
y: {
watcher.transform;
const item = root.attachTo;
let off = root.attachSideY === Menu.Top ? 0 : item.height;
if (root.thisSideY === Menu.Bottom)
off -= height;
return item.mapToItem(root.parent, 0, off).y + root.marginY;
}
transform: Scale { readonly property bool active: modelData === root.active
origin.y: root.thisSideY === Menu.Bottom ? menu.height : 0 required property int index
yScale: root.expanded ? 1 : 0.1 required property MenuItem modelData
Behavior on yScale { Layout.fillWidth: true
Anim { implicitHeight: menuOptionRow.implicitHeight + Appearance.padding.normal * 2
} implicitWidth: menuOptionRow.implicitWidth + Appearance.padding.normal * 2
}
}
CustomRect {
anchors.fill: parent
color: DynamicColors.palette.m3surfaceContainerLow
radius: parent.radius
ColumnLayout {
id: column
anchors.fill: parent
anchors.margins: Appearance.padding.extraSmall
spacing: Appearance.spacing.extraSmall
Repeater {
id: repeater
model: root.items
CustomRect { CustomRect {
id: item anchors.fill: parent
anchors.leftMargin: Appearance.padding.small
readonly property bool active: modelData === root.active anchors.rightMargin: Appearance.padding.small
required property int index color: Qt.alpha(DynamicColors.palette.m3secondaryContainer, active ? 1 : 0)
required property MenuItem modelData radius: Appearance.rounding.normal - Appearance.padding.small
Layout.fillWidth: true
color: Qt.alpha(DynamicColors.palette.m3tertiaryContainer, active ? 1 : 0)
implicitHeight: menuOptionRow.implicitHeight + Appearance.padding.larger * 2
implicitWidth: menuOptionRow.implicitWidth + Appearance.padding.larger * 2
radius: Appearance.rounding.small
Behavior on radius {
Anim {
}
}
StateLayer { StateLayer {
color: item.active ? DynamicColors.palette.m3onTertiaryContainer : DynamicColors.palette.m3onSurface function onClicked(): void {
enabled: root.expanded
onClicked: {
root.itemSelected(item.modelData); root.itemSelected(item.modelData);
root.active = item.modelData; root.active = item.modelData;
item.modelData.clicked();
root.expanded = false; root.expanded = false;
} }
color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
disabled: !root.expanded
}
}
RowLayout {
id: menuOptionRow
anchors.fill: parent
anchors.margins: Appearance.padding.normal
spacing: Appearance.spacing.small
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurfaceVariant
text: item.modelData.icon
} }
RowLayout { CustomText {
id: menuOptionRow Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
text: item.modelData.text
}
anchors.fill: parent Loader {
anchors.margins: Appearance.padding.larger Layout.alignment: Qt.AlignVCenter
spacing: Appearance.spacing.small active: item.modelData.trailingIcon.length > 0
visible: active
MaterialIcon { sourceComponent: MaterialIcon {
Layout.alignment: Qt.AlignVCenter color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
color: item.active ? DynamicColors.palette.m3onTertiaryContainer : DynamicColors.palette.m3onSurfaceVariant text: item.modelData.trailingIcon
text: item.modelData.icon
}
CustomText {
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
color: item.active ? DynamicColors.palette.m3onTertiaryContainer : DynamicColors.palette.m3onSurface
text: item.modelData.text
}
Loader {
Layout.alignment: Qt.AlignVCenter
active: item.modelData.trailingIcon.length > 0
asynchronous: true
visible: active
sourceComponent: MaterialIcon {
color: item.active ? DynamicColors.palette.m3onTertiaryContainer : DynamicColors.palette.m3onSurfaceVariant
text: item.modelData.trailingIcon
}
} }
} }
} }
+72 -175
View File
@@ -1,198 +1,95 @@
import QtQuick
import QtQuick.Shapes
import ZShell
import ZShell.Components
import qs.Helpers
import qs.Config import qs.Config
import QtQuick
MouseArea { MouseArea {
id: root id: root
property alias bottomLeftRadius: base.bottomLeftRadius property color color: DynamicColors.palette.m3onSurface
property alias bottomRightRadius: base.bottomRightRadius property bool disabled
property real circleRadius property real radius: parent?.radius ?? 0
property alias color: base.color property alias rect: hoverLayer
readonly property real endRadius: {
const d1 = distSq(0, 0);
const d2 = distSq(width, 0);
const d3 = distSq(0, height);
const d4 = distSq(width, height);
return (Math.sqrt(Math.max(d1, d2, d3, d4)) + (shapeMorph ? 24 : 0)) * 1.3;
}
property real endRadiusAtPress
property bool manualPressOverride
property real pressX: width / 2
property real pressY: height / 2
property alias radius: base.radius
readonly property alias rect: base
property bool shapeMorph
property bool showHoverBackground: true
property real stateOpacity: containsMouse ? 0.08 : 0
property alias topLeftRadius: base.topLeftRadius
property alias topRightRadius: base.topRightRadius
function clamp(r: real): real { function onClicked(): void {
return Math.max(0, Math.min(r, width / 2, height / 2));
}
function distSq(x: real, y: real): real {
return (pressX - x) ** 2 + (pressY - y) ** 2;
}
function press(x: real, y: real): void {
pressX = x;
pressY = y;
fadeAnim.complete();
circleRadius = 0;
circle.opacity = 0.1;
rippleAnim.restart();
endRadiusAtPress = endRadius;
} }
anchors.fill: parent anchors.fill: parent
cursorShape: !enabled ? undefined : Qt.PointingHandCursor cursorShape: disabled ? undefined : Qt.PointingHandCursor
enabled: parent.enabled && !Visibilities.getForActive().isDrawing enabled: !disabled
hoverEnabled: true hoverEnabled: true
Behavior on stateOpacity { onClicked: event => !disabled && onClicked(event)
onPressed: event => {
if (disabled)
return;
rippleAnim.x = event.x;
rippleAnim.y = event.y;
const dist = (ox, oy) => ox * ox + oy * oy;
rippleAnim.radius = Math.sqrt(Math.max(dist(event.x, event.y), dist(event.x, height - event.y), dist(width - event.x, event.y), dist(width - event.x, height - event.y)));
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 { Anim {
type: Anim.DefaultEffects easing.bezierCurve: MaterialEasing.standardDecel
from: 0
properties: "implicitWidth,implicitHeight"
target: ripple
to: rippleAnim.radius * 2
}
Anim {
property: "opacity"
target: ripple
to: 0
} }
} }
onCircleRadiusChanged: { CustomClippingRect {
if (!(pressed || manualPressOverride) && circleRadius > endRadiusAtPress * 0.99 && !fadeAnim.running) id: hoverLayer
fadeAnim.start();
}
onManualPressOverrideChanged: {
if (!(pressed || manualPressOverride) && circleRadius > endRadiusAtPress * 0.99 && !fadeAnim.running)
fadeAnim.start();
}
onPressed: e => press(e.x, e.y)
onPressedChanged: {
if (!(pressed || manualPressOverride) && !rippleAnim.running && circle.opacity > 0)
fadeAnim.start();
}
Anim {
id: rippleAnim
alwaysRunToEnd: true
duration: Appearance.anim.durations.expressiveSlowEffects * 2
easing.bezierCurve: Appearance.anim.curves.standard
property: "circleRadius"
target: root
to: root.endRadius
}
Anim {
id: fadeAnim
property: "opacity"
target: circle
to: 0
type: Anim.SlowEffects
}
CustomRect {
id: base
anchors.fill: parent anchors.fill: parent
bottomLeftRadius: root.parent?.bottomLeftRadius ?? radius ?? 0 border.pixelAligned: false
bottomRightRadius: root.parent?.bottomRightRadius ?? radius ?? 0 color: Qt.alpha(root.color, root.disabled ? 0 : root.pressed ? 0.1 : root.containsMouse ? 0.08 : 0)
color: DynamicColors.palette.m3onSurface radius: root.radius
opacity: root.stateOpacity
// Pick up radius from parent if it has one (parent can be anything with radius props)
// qmllint disable missing-property
radius: root.parent?.radius ?? 0
topLeftRadius: root.parent?.topLeftRadius ?? radius ?? 0
topRightRadius: root.parent?.topRightRadius ?? radius ?? 0
// qmllint enable missing-property
}
Shape { CustomRect {
id: circle id: ripple
anchors.fill: parent border.pixelAligned: false
opacity: 0 color: root.color
preferredRendererType: Shape.CurveRenderer opacity: 0
radius: Appearance.rounding.full
ShapePath { transform: Translate {
fillColor: base.color x: -ripple.width / 2
startX: root.clamp(base.topLeftRadius) y: -ripple.height / 2
startY: 0
strokeColor: "transparent"
strokeWidth: 0
fillGradient: RadialGradient {
centerRadius: root.circleRadius
centerX: root.pressX
centerY: root.pressY
focalX: centerX
focalY: centerY
GradientStop {
color: Qt.alpha(base.color, 1)
position: 0
}
GradientStop {
color: Qt.alpha(base.color, 1)
position: ZUtils.clamp(1 - 0.2 * root.endRadius / root.circleRadius, 0.01, 0.99)
}
GradientStop {
color: Qt.alpha(base.color, ZUtils.clamp((root.circleRadius / root.endRadius - 0.9) / 0.1, 0, 1))
position: 1
}
}
PathLine {
x: root.width - root.clamp(base.topRightRadius)
y: 0
}
PathArc {
radiusX: root.clamp(base.topRightRadius)
radiusY: root.clamp(base.topRightRadius)
relativeX: root.clamp(base.topRightRadius)
relativeY: root.clamp(base.topRightRadius)
}
PathLine {
x: root.width
y: root.height - root.clamp(base.bottomRightRadius)
}
PathArc {
radiusX: root.clamp(base.bottomRightRadius)
radiusY: root.clamp(base.bottomRightRadius)
relativeX: -root.clamp(base.bottomRightRadius)
relativeY: root.clamp(base.bottomRightRadius)
}
PathLine {
x: root.clamp(base.bottomLeftRadius)
y: root.height
}
PathArc {
radiusX: root.clamp(base.bottomLeftRadius)
radiusY: root.clamp(base.bottomLeftRadius)
relativeX: -root.clamp(base.bottomLeftRadius)
relativeY: -root.clamp(base.bottomLeftRadius)
}
PathLine {
x: 0
y: root.clamp(base.topLeftRadius)
}
PathArc {
radiusX: root.clamp(base.topLeftRadius)
radiusY: root.clamp(base.topLeftRadius)
x: root.clamp(base.topLeftRadius)
y: 0
} }
} }
} }
-40
View File
@@ -1,40 +0,0 @@
import QtQuick
import qs.Config
ButtonBase {
id: root
readonly property alias label: label
property alias text: label.text
activeColor: type === TextButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondary
activeOnColor: {
if (type === TextButton.Text)
return DynamicColors.palette.m3primary;
return type === TextButton.Filled ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSecondary;
}
horizontalPadding: Appearance.padding.normal
implicitHeight: label.implicitHeight + verticalPadding * 2
implicitWidth: label.implicitWidth + horizontalPadding * 2
inactiveColor: {
if (!isToggle && type === TextButton.Filled)
return DynamicColors.palette.m3primary;
return type === TextButton.Filled ? DynamicColors.tPalette.m3surfaceContainer : DynamicColors.palette.m3secondaryContainer;
}
inactiveOnColor: {
if (!isToggle && type === TextButton.Filled)
return DynamicColors.palette.m3onPrimary;
if (type === TextButton.Text)
return DynamicColors.palette.m3primary;
return type === TextButton.Filled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSecondaryContainer;
}
verticalPadding: Appearance.padding.small
CustomText {
id: label
anchors.centerIn: parent
color: root.onColor
font: root.font
}
}
+2
View File
@@ -7,6 +7,8 @@ Singleton {
readonly property AppearanceConf.Deform deform: Config.appearance.deform readonly property AppearanceConf.Deform deform: Config.appearance.deform
readonly property AppearanceConf.FontStuff font: Config.appearance.font readonly property AppearanceConf.FontStuff font: Config.appearance.font
readonly property AppearanceConf.Padding padding: Config.appearance.padding readonly property AppearanceConf.Padding padding: Config.appearance.padding
// Literally just here to shorten accessing stuff :woe:
// Also kinda so I can keep accessing it with `Appearance.xxx` instead of `Conf.appearance.xxx`
readonly property AppearanceConf.Rounding rounding: Config.appearance.rounding readonly property AppearanceConf.Rounding rounding: Config.appearance.rounding
readonly property AppearanceConf.Spacing spacing: Config.appearance.spacing readonly property AppearanceConf.Spacing spacing: Config.appearance.spacing
readonly property AppearanceConf.Transparency transparency: Config.appearance.transparency readonly property AppearanceConf.Transparency transparency: Config.appearance.transparency
+4 -18
View File
@@ -28,13 +28,9 @@ JsonObject {
property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1] property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1]
property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1] property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1] property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
property list<real> expressiveDefaultEffects: [0.34, 0.8, 0.34, 1, 1, 1]
property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1] property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1]
property list<real> expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1] property list<real> expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1]
property list<real> expressiveFastEffects: [0.31, 0.94, 0.34, 1, 1, 1]
property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 1, 1] property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 1, 1]
property list<real> expressiveSlowEffects: [0.34, 0.88, 0.34, 1, 1, 1]
property list<real> expressiveSlowSpatial: [0.39, 1.29, 0.35, 0.98, 1, 1]
property list<real> standard: [0.2, 0, 0, 1, 1, 1] property list<real> standard: [0.2, 0, 0, 1, 1, 1]
property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1] property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1]
property list<real> standardDecel: [0, 0, 0, 1, 1, 1] property list<real> standardDecel: [0, 0, 0, 1, 1, 1]
@@ -42,9 +38,7 @@ JsonObject {
component AnimDurations: JsonObject { component AnimDurations: JsonObject {
property int expressiveDefaultSpatial: 500 * scale property int expressiveDefaultSpatial: 500 * scale
property int expressiveEffects: 200 * scale property int expressiveEffects: 200 * scale
property int expressiveFastEffects: 150 * scale
property int expressiveFastSpatial: 350 * scale property int expressiveFastSpatial: 350 * scale
property int expressiveSlowEffects: 300 * scale
property int extraLarge: 1000 * scale property int extraLarge: 1000 * scale
property int large: 600 * scale property int large: 600 * scale
property int normal: 400 * scale property int normal: 400 * scale
@@ -63,8 +57,7 @@ JsonObject {
component FontSize: JsonObject { component FontSize: JsonObject {
property int extraLarge: 28 * scale property int extraLarge: 28 * scale
property int large: 18 * scale property int large: 18 * scale
property int larger: 16 * scale property int larger: 15 * scale
property int medium: 14 * scale
property int normal: 13 * scale property int normal: 13 * scale
property real scale: 1 property real scale: 1
property int small: 11 * scale property int small: 11 * scale
@@ -77,30 +70,23 @@ JsonObject {
} }
} }
component Padding: JsonObject { component Padding: JsonObject {
property int extraLarge: 28 * scale property int large: 15 * scale
property int extraLargeIncreased: 32 * scale property int larger: 13 * scale
property int extraSmall: 4 * scale property int normal: 9 * scale
property int large: 16 * scale
property int largeIncreased: 20 * scale
property int larger: 12 * scale
property int normal: 8 * scale
property real scale: 1 property real scale: 1
property int small: 5 * scale property int small: 5 * scale
property int smaller: 7 * scale property int smaller: 7 * scale
property int smallest: 2 * scale property int smallest: 2 * scale
} }
component Rounding: JsonObject { component Rounding: JsonObject {
property int extraSmall: 4 * scale
property int full: 1000 * scale property int full: 1000 * scale
property int large: 24 * scale property int large: 24 * scale
property int medium: 16 * scale
property int normal: 18 * scale property int normal: 18 * scale
property real scale: 1 property real scale: 1
property int small: 12 * scale property int small: 12 * scale
property int smallest: 8 * scale property int smallest: 8 * scale
} }
component Spacing: JsonObject { component Spacing: JsonObject {
property int extraSmall: 4 * scale
property int large: 20 * scale property int large: 20 * scale
property int larger: 16 * scale property int larger: 16 * scale
property int normal: 12 * scale property int normal: 12 * scale
-4
View File
@@ -40,10 +40,6 @@ JsonObject {
id: "tray", id: "tray",
enabled: true enabled: true
}, },
{
id: "network",
enabled: false
},
{ {
id: "clock", id: "clock",
enabled: true enabled: true
-15
View File
@@ -1,15 +0,0 @@
import Quickshell.Io
JsonObject {
property bool enabled: true
property int maxEntriesShown: 10
property Sizes sizes: Sizes {
}
component Sizes: JsonObject {
property int itemHeight: 60
property int minPreviewWidth: 200
property int previewWidth: 500
property int width: 500
}
}
+2 -21
View File
@@ -13,7 +13,6 @@ Singleton {
property alias appearance: adapter.appearance property alias appearance: adapter.appearance
property alias background: adapter.background property alias background: adapter.background
property alias barConfig: adapter.barConfig property alias barConfig: adapter.barConfig
property alias clipboard: adapter.clipboard
property alias colors: adapter.colors property alias colors: adapter.colors
property alias dashboard: adapter.dashboard property alias dashboard: adapter.dashboard
property alias dock: adapter.dock property alias dock: adapter.dock
@@ -117,19 +116,6 @@ Singleton {
}; };
} }
function serializeClipboard(): var {
return {
enabled: clipboard.enabled,
maxEntriesShown: clipboard.maxEntriesShown,
sizes: {
width: clipboard.sizes.width,
previewWidth: clipboard.sizes.previewWidth,
minPreviewWidth: clipboard.sizes.minPreviewWidth,
itemHeight: clipboard.sizes.itemHeight
}
};
}
function serializeColors(): var { function serializeColors(): var {
return { return {
schemeType: colors.schemeType, schemeType: colors.schemeType,
@@ -157,8 +143,7 @@ Singleton {
launcher: serializeLauncher(), launcher: serializeLauncher(),
colors: serializeColors(), colors: serializeColors(),
dock: serializeDock(), dock: serializeDock(),
screenshot: serializeScreenshot(), screenshot: serializeScreenshot()
clipboard: serializeClipboard()
}; };
} }
@@ -188,7 +173,7 @@ Singleton {
resourceProgessThickness: dashboard.sizes.resourceProgessThickness, resourceProgessThickness: dashboard.sizes.resourceProgessThickness,
weatherWidth: dashboard.sizes.weatherWidth, weatherWidth: dashboard.sizes.weatherWidth,
mediaCoverArtSize: dashboard.sizes.mediaCoverArtSize, mediaCoverArtSize: dashboard.sizes.mediaCoverArtSize,
mediaVisualizerSize: dashboard.sizes.mediaVisualizerSize, mediaVisualiserSize: dashboard.sizes.mediaVisualiserSize,
resourceSize: dashboard.sizes.resourceSize resourceSize: dashboard.sizes.resourceSize
} }
}; };
@@ -209,7 +194,6 @@ Singleton {
return { return {
logo: general.logo, logo: general.logo,
wallpaperPath: general.wallpaperPath, wallpaperPath: general.wallpaperPath,
showOverFullscreen: general.showOverFullscreen,
desktopIcons: general.desktopIcons, desktopIcons: general.desktopIcons,
dateFormat: general.dateFormat, dateFormat: general.dateFormat,
color: { color: {
@@ -331,7 +315,6 @@ Singleton {
weatherLocation: services.weatherLocation, weatherLocation: services.weatherLocation,
updates: services.updates, updates: services.updates,
useFahrenheit: services.useFahrenheit, useFahrenheit: services.useFahrenheit,
minBrightness: services.minBrightness,
ddcutilService: services.ddcutilService, ddcutilService: services.ddcutilService,
useTwelveHourClock: services.useTwelveHourClock, useTwelveHourClock: services.useTwelveHourClock,
gpuType: services.gpuType, gpuType: services.gpuType,
@@ -464,8 +447,6 @@ Singleton {
} }
property BarConfig barConfig: BarConfig { property BarConfig barConfig: BarConfig {
} }
property ClipboardConfig clipboard: ClipboardConfig {
}
property Colors colors: Colors { property Colors colors: Colors {
} }
property DashboardConfig dashboard: DashboardConfig { property DashboardConfig dashboard: DashboardConfig {
+1 -1
View File
@@ -25,7 +25,7 @@ JsonObject {
readonly property int mediaCoverArtSize: 150 readonly property int mediaCoverArtSize: 150
readonly property int mediaProgressSweep: 180 readonly property int mediaProgressSweep: 180
readonly property int mediaProgressThickness: 8 readonly property int mediaProgressThickness: 8
readonly property int mediaVisualizerSize: 200 readonly property int mediaVisualiserSize: 80
readonly property int mediaWidth: 200 readonly property int mediaWidth: 200
readonly property int resourceProgessThickness: 10 readonly property int resourceProgessThickness: 10
readonly property int resourceSize: 200 readonly property int resourceSize: 200
-5
View File
@@ -13,16 +13,11 @@ JsonObject {
property Idle idle: Idle { property Idle idle: Idle {
} }
property string logo: "" property string logo: ""
property bool showOverFullscreen: true
property string wallpaperPath: Quickshell.env("HOME") + "/Pictures/Wallpapers" property string wallpaperPath: Quickshell.env("HOME") + "/Pictures/Wallpapers"
component Apps: JsonObject { component Apps: JsonObject {
property list<string> archiver: ["ark"]
property list<string> audio: ["pavucontrol"] property list<string> audio: ["pavucontrol"]
property list<string> document: ["libreoffice"]
property list<string> editor: ["kate"]
property list<string> explorer: ["dolphin"] property list<string> explorer: ["dolphin"]
property list<string> image: ["imv"]
property list<string> playback: ["mpv"] property list<string> playback: ["mpv"]
property list<string> terminal: ["kitty"] property list<string> terminal: ["kitty"]
} }
-1
View File
@@ -8,7 +8,6 @@ JsonObject {
property string defaultPlayer: "Spotify" property string defaultPlayer: "Spotify"
property string gpuType: "" property string gpuType: ""
property real maxVolume: 1.0 property real maxVolume: 1.0
property real minBrightness: 0.01
property list<var> playerAliases: [ property list<var> playerAliases: [
{ {
"from": "com.github.th_ch.youtube_music", "from": "com.github.th_ch.youtube_music",
-6
View File
@@ -215,10 +215,6 @@ Singleton {
notif.hasActionIcons = notif.notification.hasActionIcons; notif.hasActionIcons = notif.notification.hasActionIcons;
} }
function onHintsChanged(): void {
notif.hints = notif.notification.hints;
}
function onImageChanged(): void { function onImageChanged(): void {
notif.imageSource = notif.notification.image || ""; notif.imageSource = notif.notification.image || "";
notif.image = notif.imageSource; notif.image = notif.imageSource;
@@ -241,7 +237,6 @@ Singleton {
} }
property real expireTimeout: 5 property real expireTimeout: 5
property bool hasActionIcons property bool hasActionIcons
property var hints
property string image property string image
property string imageSource property string imageSource
property var locks: new Set() property var locks: new Set()
@@ -338,7 +333,6 @@ Singleton {
appName = notification.appName; appName = notification.appName;
imageSource = notification.image || ""; imageSource = notification.image || "";
image = imageSource; image = imageSource;
hints = notification.hints;
expireTimeout = notification.expireTimeout; expireTimeout = notification.expireTimeout;
urgency = notification.urgency; urgency = notification.urgency;
resident = notification.resident; resident = notification.resident;
-25
View File
@@ -1,25 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
Variants {
model: Quickshell.screens
Scope {
id: scope
required property ShellScreen modelData
Exclusions {
bar: content.bar
screen: scope.modelData
}
Windows {
id: content
screen: scope.modelData
}
}
}
+26 -20
View File
@@ -1,30 +1,36 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick import QtQuick
import ZShell.Internal
Item { Canvas {
id: root id: root
readonly property alias content: contentLoader.item property color penColor: "white"
readonly property PersistentProperties drawingState: PersistentProperties { property real penWidth: 4
property color penColor: "white" property var points: []
property int penWidth: 4
reloadableId: "drawingState" function clear(): void {
var ctx = getContext('2d');
root.points = [];
ctx.reset();
root.requestPaint();
} }
required property PersistentProperties visibilities
Loader { renderStrategy: Canvas.Cooperative
id: contentLoader
active: root.visibilities.isDrawing onPaint: {
anchors.fill: parent if (points.length < 2)
return;
sourceComponent: StrokeCanvas { var ctx = root.getContext('2d');
penColor: root.drawingState.penColor ctx.save();
penWidth: root.drawingState.penWidth ctx.lineWidth = root.penWidth;
} ctx.strokeStyle = root.penColor;
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for (var i = 1; i < points.length; i++)
ctx.lineTo(points[i].x, points[i].y);
ctx.stroke();
points = points.slice(points.length - 2);
ctx.restore();
} }
} }
+60
View File
@@ -0,0 +1,60 @@
import Quickshell
import QtQuick
import qs.Components
import qs.Config
CustomMouseArea {
id: root
required property var bar
required property Drawing drawing
required property Panels panels
required property var popout
required property PersistentProperties visibilities
function inLeftPanel(panel: Item, x: real, y: real): bool {
return x < panel.x + panel.width + Config.barConfig.border && withinPanelHeight(panel, x, y);
}
function withinPanelHeight(panel: Item, x: real, y: real): bool {
const panelY = panel.y + bar.implicitHeight;
return y >= panelY && y <= panelY + panel.height;
}
acceptedButtons: Qt.LeftButton | Qt.RightButton
enabled: z > 0
hoverEnabled: true
visible: root.visibilities.isDrawing
onPositionChanged: event => {
const x = event.x;
const y = event.y;
if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) {
root.drawing.points.push(Qt.point(x, y));
root.drawing.requestPaint();
return;
}
if (!(event.buttons & Qt.LeftButton) && root.inLeftPanel(root.popout, x, y)) {
root.z = -2;
root.panels.drawing.expanded = true;
}
}
onPressed: event => {
const x = event.x;
const y = event.y;
if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) {
root.panels.drawing.expanded = false;
root.drawing.points.push(Qt.point(x, y));
root.drawing.requestPaint();
return;
}
if (event.buttons & Qt.RightButton)
root.drawing.clear();
}
onReleased: {
root.drawing.points = [];
}
}
+8 -61
View File
@@ -9,9 +9,9 @@ Item {
id: root id: root
required property Item bar required property Item bar
required property real borderThickness
property bool dashboardShortcutActive property bool dashboardShortcutActive
required property Drawing drawing required property Drawing drawing
required property DrawingInput input
property bool osdShortcutActive property bool osdShortcutActive
required property Panels panels required property Panels panels
required property BarPopouts.Wrapper popouts required property BarPopouts.Wrapper popouts
@@ -48,7 +48,7 @@ Item {
} }
function withinPanelWidth(panel: Item, x: real, y: real): bool { function withinPanelWidth(panel: Item, x: real, y: real): bool {
const panelX = panel.x + root.borderThickness; const panelX = panel.x;
return x >= panelX && x <= panelX + panel.width; return x >= panelX && x <= panelX + panel.width;
} }
@@ -59,8 +59,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 grabPermissions: PointerHandler.CanTakeOverFromHandlersOfDifferentType | PointerHandler.ApprovesTakeOverByAnything
grabPermissions: PointerHandler.CanTakeOverFromHandlersOfSameType | PointerHandler.ApprovesTakeOverByAnything
maximumPointCount: 1 maximumPointCount: 1
minimumPointCount: 1 minimumPointCount: 1
target: null target: null
@@ -75,7 +74,7 @@ Item {
const dragX = x - centroid.pressPosition.x; const dragX = x - centroid.pressPosition.x;
const dragY = y - centroid.pressPosition.y; const dragY = y - centroid.pressPosition.y;
if (centroid.pressPosition.y >= root.screen.height - Config.barConfig.border && centroid.pressPosition.x > root.screen.width / 5 && dragY < -200) if (centroid.pressPosition.y >= root.screen.height - Config.barConfig.border && dragY < -200)
root.visibilities.launcher = true; root.visibilities.launcher = true;
if (root.singleGestureTriggered) if (root.singleGestureTriggered)
@@ -91,10 +90,7 @@ Item {
} }
} }
if (centroid.pressPosition.y > root.screen.height - Config.barConfig.border && centroid.pressPosition.x < root.screen.width / 5 && dragY < -50) if (!Config.dock.hoverToReveal && centroid.pressPosition.y > root.screen.height - root.bar.implicitHeight)
root.visibilities.clipboard = true;
if (!Config.dock.hoverToReveal && centroid.pressPosition.y > root.screen.height - root.bar.implicitHeight && centroid.pressPosition.x > root.screen.width / 5 && !root.visibilities.launcher)
if (dragY < -10) { if (dragY < -10) {
root.visibilities.dock = true; root.visibilities.dock = true;
root.singleGestureTriggered = true; root.singleGestureTriggered = true;
@@ -117,52 +113,9 @@ Item {
} }
} }
PointHandler {
id: drawingHandler
property bool setInitialPoint: false
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)
onActiveChanged: {
if (!active) {
setInitialPoint = false;
root.drawing.content.endStroke();
} else {
root.panels.drawing.collapse();
}
}
onPointChanged: {
if (!active)
return;
const x = point.position.x;
const y = point.position.y;
const origX = point.pressPosition.x;
const origY = point.pressPosition.y;
if (point.pressedButtons & Qt.RightButton) {
root.drawing.content.clear();
return;
}
if (x === 0 && y === 0 && origX === 0 && origY === 0)
return;
if (!setInitialPoint) {
setInitialPoint = true;
root.drawing.content.beginStroke(origX, origY);
return;
}
root.drawing.content.appendPoint(x, y);
}
}
HoverHandler { HoverHandler {
id: hoverHandler id: hoverHandler
cursorShape: root.visibilities.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) {
@@ -182,15 +135,9 @@ Item {
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.visibilities.isDrawing && !root.inLeftPanel(root.panels.drawing, x, y)) {
if (root.inLeftPanel(root.panels.drawing, x, y) && !(drawingHandler.point.pressedButtons & Qt.LeftButton)) { root.input.z = 2;
root.panels.drawing.expand(); root.panels.drawing.expanded = false;
root.drawing.content.hideHover();
return;
}
root.drawing.content.showHover(x, y);
return;
} }
if (!root.visibilities.bar && Config.barConfig.autoHide && y < root.bar.implicitHeight) if (!root.visibilities.bar && Config.barConfig.autoHide && y < root.bar.implicitHeight)
+2 -15
View File
@@ -13,20 +13,17 @@ import qs.Modules.Resources as Resources
import qs.Modules.Settings as Settings import qs.Modules.Settings as Settings
import qs.Modules.Drawing as Drawing import qs.Modules.Drawing as Drawing
import qs.Modules.Dock as Dock import qs.Modules.Dock as Dock
import qs.Modules.Clipboard as Clipboard
import qs.Config import qs.Config
Item { Item {
id: root id: root
required property Item bar required property Item bar
required property real borderThickness
readonly property alias clipboard: clipboard
readonly property alias dashboard: dashboard readonly property alias dashboard: dashboard
readonly property alias dashboardWrapper: dashboardWrapper readonly property alias dashboardWrapper: dashboardWrapper
readonly property alias dock: dock readonly property alias dock: dock
readonly property alias drawing: drawing readonly property alias drawing: drawing
required property var drawingItem required property Canvas drawingItem
readonly property alias launcher: launcher readonly property alias launcher: launcher
readonly property alias notifications: notifications readonly property alias notifications: notifications
readonly property alias osd: osd readonly property alias osd: osd
@@ -44,7 +41,7 @@ Item {
required property PersistentProperties visibilities required property PersistentProperties visibilities
anchors.fill: parent anchors.fill: parent
anchors.margins: borderThickness anchors.margins: Config.barConfig.border
anchors.topMargin: bar.implicitHeight anchors.topMargin: bar.implicitHeight
Item { Item {
@@ -100,7 +97,6 @@ Item {
id: popouts id: popouts
anchors.top: parent.top anchors.top: parent.top
borderThickness: root.borderThickness
screen: root.screen screen: root.screen
} }
@@ -211,13 +207,4 @@ Item {
screen: root.screen screen: root.screen
visibilities: root.visibilities visibilities: root.visibilities
} }
Clipboard.Wrapper {
id: clipboard
anchors.bottom: parent.bottom
anchors.left: parent.left
screen: root.screen
visibilities: root.visibilities
}
} }
+364 -427
View File
@@ -9,460 +9,397 @@ import Quickshell.Hyprland
import ZShell.Blobs import ZShell.Blobs
import qs.Daemons import qs.Daemons
import qs.Components import qs.Components
import qs.Modules
import qs.Modules.Bar import qs.Modules.Bar
import qs.Config import qs.Config
import qs.Helpers import qs.Helpers
import qs.Drawers import qs.Drawers
CustomWindow { Variants {
id: root model: Quickshell.screens
readonly property alias bar: bar Scope {
readonly property real borderLayoutThickness: hasFullscreen ? 0 : Config.barConfig.border id: scope
readonly property real borderRounding: Config.barConfig.rounding * (1 - fsTransitionProg)
readonly property real borderThickness: Config.barConfig.border * (1 - fsTransitionProg)
readonly property int dragMaskPadding: {
if (focusGrab.active)
return 0;
if (monitor?.lastIpcObject.specialWorkspace?.name || monitor?.activeWorkspace.lastIpcObject.windows > 0) required property var modelData
return 0;
return 100; Exclusions {
} bar: bar
property real fsTransitionProg: hasFullscreen ? 1 : 0 screen: scope.modelData
readonly property bool hasFullscreen: {
if (hasSpecialWorkspace) {
const specialName = monitor?.lastIpcObject.specialWorkspace?.name;
if (!specialName)
return false;
const specialWs = Hypr.workspaces.values.find(ws => ws.name === specialName);
return specialWs?.toplevels.values.some(t => t.lastIpcObject.fullscreen > 1) ?? false;
}
return hasFullscreenOnNormalWs;
}
readonly property bool hasFullscreenOnNormalWs: monitor?.activeWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen > 1) ?? false
readonly property bool hasSpecialWorkspace: (monitor?.lastIpcObject.specialWorkspace?.name.length ?? 0) > 0
readonly property alias interactionWrapper: interactions
readonly property alias menuRegion: menuPopoutRegion
readonly property HyprlandMonitor monitor: Hypr.monitorFor(screen)
property var root: Quickshell.shellDir
readonly property real sdfBorderOffset: 2 * fsTransitionProg
readonly property real shadowOpacity: 0.7 * (1 - fsTransitionProg)
property color surfaceColor: DynamicColors.tPalette.m3surface
WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.keyboardFocus: visibilities.launcher || visibilities.settings ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
WlrLayershell.layer: (fsTransitionProg > 0 && Config.general.showOverFullscreen) || (hasSpecialWorkspace && hasFullscreenOnNormalWs) ? WlrLayer.Overlay : WlrLayer.Top
color: "transparent"
contentItem.focus: true
mask: visibilities.isDrawing ? null : (hasFullscreen ? emptyRegion : region)
name: "Bar"
Behavior on fsTransitionProg {
Anim {
}
}
Behavior on surfaceColor {
CAnim {
}
}
contentItem.Keys.onEscapePressed: {
if (Config.barConfig.autoHide)
visibilities.bar = false;
visibilities.launcher = false;
visibilities.sidebar = false;
visibilities.dashboard = false;
visibilities.osd = false;
visibilities.settings = false;
visibilities.resources = false;
visibilities.dock = false;
visibilities.clipboard = false;
panels.popouts.hasCurrent = false;
}
onHasFullscreenChanged: {
visibilities.launcher = false;
visibilities.sidebar = false;
visibilities.dashboard = false;
visibilities.osd = false;
visibilities.settings = false;
visibilities.resources = false;
visibilities.clipboard = false;
visibilities.dock = false;
panels.popouts.hasCurrent = false;
}
Region {
id: menuPopoutRegion
intersection: Intersection.Subtract
}
Region {
id: emptyRegion
height: panels.notifications.height
width: panels.notifications.width
x: panels.notifications.x + root.borderThickness
y: panels.notifications.y + bar.implicitHeight
}
Region {
id: region
height: root.height - bar.implicitHeight - root.borderThickness - root.dragMaskPadding * 2
intersection: Intersection.Xor
regions: [...popoutRegions.instances, menuPopoutRegion]
width: root.width - root.borderThickness * 2 - root.dragMaskPadding * 2
x: root.borderThickness + root.dragMaskPadding
y: bar.implicitHeight + root.dragMaskPadding
}
anchors {
bottom: true
left: true
right: true
top: true
}
Variants {
id: popoutRegions
model: panels.children
Region {
required property Item modelData
height: modelData.height
intersection: Intersection.Subtract
width: modelData.width
x: modelData.x + root.borderThickness
y: modelData.y + bar.implicitHeight
}
}
HyprlandFocusGrab {
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"))
windows: [root]
onCleared: {
visibilities.launcher = false;
visibilities.sidebar = false;
visibilities.dashboard = false;
visibilities.osd = false;
visibilities.settings = false;
visibilities.resources = false;
visibilities.clipboard = false;
visibilities.dock = 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)
}
IpcHandler {
function toggleLauncher(fix: string): void {
visibilities.launcher = !visibilities.launcher;
} }
target: "visibilities" CustomWindow {
} id: win
Binding { readonly property bool hasFullscreen: Hypr.monitorFor(screen)?.activeWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen === 2)
property: "bar" property var root: Quickshell.shellDir
target: visibilities
value: visibilities.sidebar || visibilities.dashboard || visibilities.osd || (!Config.barConfig.hideWhenNotif && visibilities.notif) || visibilities.resources || visibilities.settings || bar.isHovered
when: Config.barConfig.autoHide
}
Item { WlrLayershell.exclusionMode: ExclusionMode.Ignore
id: surface // WlrLayershell.keyboardFocus: visibilities.dock || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.resources ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
color: "transparent"
contentItem.focus: true
mask: visibilities.isDrawing ? null : region
name: "Bar"
screen: scope.modelData
anchors.fill: parent contentItem.Keys.onEscapePressed: {
layer.enabled: true if (Config.barConfig.autoHide)
opacity: root.surfaceColor.a visibilities.bar = false;
visibilities.sidebar = false;
visibilities.dashboard = false;
visibilities.osd = false;
visibilities.settings = false;
visibilities.resources = false;
}
onHasFullscreenChanged: {
visibilities.launcher = false;
visibilities.dashboard = false;
visibilities.osd = false;
visibilities.settings = false;
visibilities.resources = false;
}
layer.effect: MultiEffect { Region {
blurMax: 32 id: region
shadowColor: Qt.alpha(DynamicColors.palette.m3shadow, Math.max(0, root.shadowOpacity))
shadowEnabled: true
}
BlobGroup { height: win.height - bar.implicitHeight - Config.barConfig.border
id: blobGroup intersection: Intersection.Xor
regions: popoutRegions.instances
width: win.width - Config.barConfig.border * 2
x: Config.barConfig.border
y: bar.implicitHeight
}
color: root.surfaceColor anchors {
smoothing: Config.barConfig.smoothing bottom: true
} left: true
right: true
top: true
}
BlobInvertedRect { Variants {
anchors.fill: parent id: popoutRegions
anchors.margins: -50
borderBottom: root.borderThickness - anchors.margins - root.sdfBorderOffset
borderLeft: root.borderThickness - anchors.margins - root.sdfBorderOffset
borderRight: root.borderThickness - anchors.margins - root.sdfBorderOffset
borderTop: bar.implicitHeight - anchors.margins - root.sdfBorderOffset
group: blobGroup
radius: root.borderRounding
}
PanelBg { model: panels.children
id: dashBg
property real extraHeight: 0.2 Region {
required property Item modelData
deformAmount: 0.06 height: modelData.height
implicitHeight: panels.dashboard.height * (1 + extraHeight) intersection: Intersection.Subtract
implicitWidth: panels.dashboard.width width: modelData.width
panel: panels.dashboardWrapper x: modelData.x + Config.barConfig.border
radius: Appearance.rounding.normal y: modelData.y + bar.implicitHeight
x: panels.dashboardWrapper.x + panels.dashboard.x + root.borderThickness
y: panels.dashboardWrapper.y + panels.dashboard.y + bar.implicitHeight - panels.dashboard.height * extraHeight
}
PanelBg {
id: launcherBg
property real extraHeight: 0.2
deformAmount: 0.06
implicitHeight: panels.launcher.height * (1 + extraHeight)
panel: panels.launcher
radius: Appearance.rounding.smallest + 5
y: panels.launcher.y + bar.implicitHeight
}
PanelBg {
id: sidebarBg
bottomLeftRadius: 0
deformAmount: 0.04
exclude: panels.sidebar.offsetScale > 0.08 ? [] : [utilsBg]
implicitHeight: panel.height * (1 / rawDeformMatrix.m22) + 2
panel: panels.sidebar
}
PanelBg {
id: osdBg
deformAmount: 0.1
implicitHeight: panels.osd.height
implicitWidth: panels.osd.width
panel: panels.osdWrapper
radius: 20
x: panels.osdWrapper.x + panels.osd.x + root.borderThickness
y: panels.osdWrapper.y + panels.osd.y + bar.implicitHeight
}
PanelBg {
id: notifsBg
panel: panels.notifications
radius: Appearance.rounding.normal
}
PanelBg {
id: utilsBg
deformAmount: 0.1
exclude: panels.sidebar.offsetScale > 0.08 ? [] : [sidebarBg]
panel: panels.utilities
topLeftRadius: 0
}
PanelBg {
id: popoutBg
property real extraHeight: 0.2
deformAmount: panels.popouts.currentName.startsWith("traymenu") ? 0.15 : 0.08
implicitHeight: panels.popouts.height * (1 + extraHeight)
implicitWidth: panels.popouts.width
panel: panels.popoutsWrapper
radius: panels.popouts.current?.panelRadius ?? Appearance.rounding.normal
x: panels.popoutsWrapper.x + panels.popouts.x + root.borderThickness
y: panels.popoutsWrapper.y + panels.popouts.y + bar.implicitHeight - panels.popouts.height * extraHeight
Behavior on extraHeight {
Anim {
} }
} }
}
PanelBg { HyprlandFocusGrab {
id: resourcesBg id: focusGrab
deformAmount: 0.05 active: visibilities.dock || visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu"))
implicitHeight: panels.resources.height windows: [win]
implicitWidth: panels.resources.width
panel: panels.resourcesWrapper
radius: Appearance.rounding.large
x: panels.resourcesWrapper.x + panels.resources.x + root.borderThickness
y: panels.resourcesWrapper.y + panels.resources.y + bar.implicitHeight
}
PanelBg { onCleared: {
id: settingsBg visibilities.launcher = false;
visibilities.sidebar = false;
property real extraHeight: 0.2 visibilities.dashboard = false;
visibilities.osd = false;
deformAmount: 0.03 visibilities.settings = false;
implicitHeight: panels.settings.height * (1 + extraHeight) visibilities.resources = false;
implicitWidth: panels.settings.width visibilities.dock = false;
panel: panels.settings panels.popouts.hasCurrent = false;
radius: Appearance.rounding.large }
topLeftRadius: Appearance.rounding.large + Appearance.padding.smaller
topRightRadius: Appearance.rounding.large + Appearance.padding.smaller
x: panels.settingsWrapper.x + panels.settings.x + root.borderThickness
y: panels.settingsWrapper.y + panels.settings.y + bar.implicitHeight - panels.settings.height * extraHeight
}
PanelBg {
id: dockBg
deformAmount: 0.08
panel: panels.dock
radius: Appearance.rounding.normal
}
PanelBg {
id: drawingBg
deformAmount: 0.08
panel: panels.drawing
radius: Appearance.rounding.normal
}
PanelBg {
id: clipboardBg
deformAmount: 0.03
panel: panels.clipboard
radius: 29
}
}
Drawing {
id: drawing
anchors.fill: parent
layer.enabled: true
visibilities: visibilities
z: 2
layer.effect: MultiEffect {
maskEnabled: true
maskInverted: true
maskSource: maskSource
}
}
Item {
id: maskSource
anchors.fill: parent
layer.enabled: true
visible: false
CustomRect {
readonly property int extraWidth: radius
color: "white"
implicitHeight: panels.drawing.height
implicitWidth: panels.drawing.width + extraWidth
radius: drawingBg.radius
x: -extraWidth + root.borderThickness + panels.drawing.x
y: panels.drawing.y + bar.implicitHeight
}
}
Interactions {
id: interactions
anchors.fill: parent
bar: bar
borderThickness: root.borderLayoutThickness
drawing: drawing
enabled: true
panels: panels
popouts: panels.popouts
screen: root.screen
visibilities: visibilities
Panels {
id: panels
bar: bar
borderThickness: root.borderThickness
drawingItem: drawing
screen: root.screen
visibilities: visibilities
clipboard.transform: Matrix4x4 {
matrix: clipboardBg.deformMatrix
} }
dashboard.transform: Matrix4x4 {
matrix: dashBg.deformMatrix
}
dock.transform: Matrix4x4 {
matrix: dockBg.deformMatrix
}
launcher.transform: Matrix4x4 {
matrix: launcherBg.deformMatrix
}
notifications.transform: Matrix4x4 {
matrix: notifsBg.deformMatrix
}
osd.transform: Matrix4x4 {
matrix: osdBg.deformMatrix
}
popouts.transform: Matrix4x4 {
matrix: popoutBg.deformMatrix
}
resources.transform: Matrix4x4 {
matrix: resourcesBg.deformMatrix
}
settings.transform: Matrix4x4 {
matrix: settingsBg.deformMatrix
}
sidebar.transform: Matrix4x4 {
matrix: sidebarBg.deformMatrix
}
utilities.transform: Matrix4x4 {
matrix: utilsBg.deformMatrix
}
}
BarLoader { PersistentProperties {
id: bar id: visibilities
anchors.left: parent.left property bool bar
anchors.right: parent.right property bool dashboard
enabled: !visibilities.isDrawing property bool dock
fullscreen: root.hasFullscreen property bool isDrawing
popouts: panels.popouts property bool launcher
popoutsWrapper: panels.popoutsWrapper property bool notif: NotifServer.popups.length > 0
screen: root.screen property bool osd
visibilities: visibilities property bool resources
property bool settings
property bool sidebar
Component.onCompleted: Visibilities.load(scope.modelData, this)
}
IpcHandler {
function toggleLauncher(fix: string): void {
visibilities.launcher = !visibilities.launcher;
}
target: "visibilities"
}
Binding {
property: "bar"
target: visibilities
value: visibilities.sidebar || visibilities.dashboard || visibilities.osd || (!Config.barConfig.hideWhenNotif && visibilities.notif) || visibilities.resources || visibilities.settings || bar.isHovered
when: Config.barConfig.autoHide
}
Item {
anchors.fill: parent
layer.enabled: true
opacity: Appearance.transparency.enabled ? DynamicColors.transparency.base : 1
layer.effect: MultiEffect {
blurMax: 32
shadowColor: Qt.alpha(DynamicColors.palette.m3shadow, 1)
shadowEnabled: true
}
BlobGroup {
id: blobGroup
color: DynamicColors.palette.m3surface
smoothing: Config.barConfig.smoothing
Behavior on color {
CAnim {
}
}
}
BlobInvertedRect {
anchors.fill: parent
anchors.margins: -50
borderBottom: Config.barConfig.border - anchors.margins
borderLeft: Config.barConfig.border - anchors.margins
borderRight: Config.barConfig.border - anchors.margins
borderTop: bar.implicitHeight - anchors.margins
group: blobGroup
radius: Config.barConfig.rounding
}
PanelBg {
id: dashBg
property real extraHeight: 0.2
deformAmount: 0.06
implicitHeight: panels.dashboard.height * (1 + extraHeight)
implicitWidth: panels.dashboard.width
panel: panels.dashboardWrapper
radius: Appearance.rounding.normal
x: panels.dashboardWrapper.x + panels.dashboard.x + Config.barConfig.border
y: panels.dashboardWrapper.y + panels.dashboard.y + bar.implicitHeight - panels.dashboard.height * extraHeight
}
PanelBg {
id: launcherBg
property real extraHeight: 0.2
deformAmount: 0.06
implicitHeight: panels.launcher.height * (1 + extraHeight)
panel: panels.launcher
radius: Appearance.rounding.smallest + 5
y: panels.launcher.y + bar.implicitHeight
}
PanelBg {
id: sidebarBg
bottomLeftRadius: 0
deformAmount: 0.04
exclude: panels.sidebar.offsetScale > 0.08 ? [] : [utilsBg]
implicitHeight: panel.height * (1 / rawDeformMatrix.m22) + 2
panel: panels.sidebar
}
PanelBg {
id: osdBg
deformAmount: 0.1
implicitHeight: panels.osd.height
implicitWidth: panels.osd.width
panel: panels.osdWrapper
radius: 20
x: panels.osdWrapper.x + panels.osd.x + Config.barConfig.border
y: panels.osdWrapper.y + panels.osd.y + bar.implicitHeight
}
PanelBg {
id: notifsBg
panel: panels.notifications
radius: Appearance.rounding.normal
}
PanelBg {
id: utilsBg
deformAmount: panels.sidebar.visible ? (0.1) : (0.1)
exclude: panels.sidebar.offsetScale > 0.08 ? [] : [sidebarBg]
panel: panels.utilities
topLeftRadius: 0
}
PanelBg {
id: popoutBg
property real extraHeight: panels.popouts.isDetached ? 0 : 0.2
deformAmount: panels.popouts.isDetached ? 0.05 : panels.popouts.hasCurrent ? 0.15 : 0.1
implicitHeight: panels.popouts.height * (1 + extraHeight)
implicitWidth: panels.popouts.width
panel: panels.popoutsWrapper
radius: (panels.popouts.currentName.startsWith("audio") || panels.popouts.currentName.startsWith("updates")) ? Appearance.rounding.normal : 20 * Appearance.rounding.scale
x: panels.popoutsWrapper.x + panels.popouts.x + Config.barConfig.border
y: panels.popoutsWrapper.y + panels.popouts.y + bar.implicitHeight - panels.popouts.height * extraHeight
Behavior on extraHeight {
Anim {
}
}
}
PanelBg {
id: resourcesBg
deformAmount: 0.05
implicitHeight: panels.resources.height
implicitWidth: panels.resources.width
panel: panels.resourcesWrapper
radius: Appearance.rounding.normal
x: panels.resourcesWrapper.x + panels.resources.x + Config.barConfig.border
y: panels.resourcesWrapper.y + panels.resources.y + bar.implicitHeight
}
PanelBg {
id: settingsBg
property real extraHeight: 0.2
deformAmount: 0.03
implicitHeight: panels.settings.height * (1 + extraHeight)
implicitWidth: panels.settings.width
panel: panels.settings
radius: Appearance.rounding.large
topLeftRadius: Appearance.rounding.large + Appearance.padding.smaller
topRightRadius: Appearance.rounding.large + Appearance.padding.smaller
x: panels.settingsWrapper.x + panels.settings.x + Config.barConfig.border
y: panels.settingsWrapper.y + panels.settings.y + bar.implicitHeight - panels.settings.height * extraHeight
}
PanelBg {
id: dockBg
deformAmount: 0.08
panel: panels.dock
radius: Appearance.rounding.normal
}
PanelBg {
id: drawingBg
deformAmount: 0.08
panel: panels.drawing
radius: Appearance.rounding.normal
}
}
Loader {
id: drawingLoader
active: visibilities.isDrawing
anchors.fill: parent
z: 2
sourceComponent: Drawing {
id: drawing
}
}
Loader {
id: inputLoader
active: visibilities.isDrawing
anchors.fill: parent
z: 2
sourceComponent: DrawingInput {
id: input
bar: bar
drawing: drawingLoader.item
panels: panels
popout: panels.drawing
visibilities: visibilities
}
}
Interactions {
id: mouseArea
anchors.fill: parent
bar: bar
drawing: drawingLoader.item
enabled: true
input: inputLoader.item
panels: panels
popouts: panels.popouts
screen: scope.modelData
visibilities: visibilities
z: 1
Panels {
id: panels
bar: bar
drawingItem: drawingLoader.item
screen: scope.modelData
visibilities: visibilities
dashboard.transform: Matrix4x4 {
matrix: dashBg.deformMatrix
}
dock.transform: Matrix4x4 {
matrix: dockBg.deformMatrix
}
launcher.transform: Matrix4x4 {
matrix: launcherBg.deformMatrix
}
notifications.transform: Matrix4x4 {
matrix: notifsBg.deformMatrix
}
osd.transform: Matrix4x4 {
matrix: osdBg.deformMatrix
}
popouts.transform: Matrix4x4 {
matrix: popoutBg.deformMatrix
}
resources.transform: Matrix4x4 {
matrix: resourcesBg.deformMatrix
}
settings.transform: Matrix4x4 {
matrix: settingsBg.deformMatrix
}
sidebar.transform: Matrix4x4 {
matrix: sidebarBg.deformMatrix
}
utilities.transform: Matrix4x4 {
matrix: utilsBg.deformMatrix
}
}
BarLoader {
id: bar
anchors.left: parent.left
anchors.right: parent.right
popouts: panels.popouts
popoutsWrapper: panels.popoutsWrapper
screen: scope.modelData
visibilities: visibilities
}
}
} }
} }
@@ -475,7 +412,7 @@ CustomWindow {
implicitHeight: panel.height implicitHeight: panel.height
implicitWidth: panel.width implicitWidth: panel.width
radius: Appearance.rounding.smallest radius: Appearance.rounding.smallest
x: panel.x + root.borderThickness x: panel.x + Config.barConfig.border
y: panel.y + bar.implicitHeight y: panel.y + bar.implicitHeight
} }
} }
+7 -7
View File
@@ -118,12 +118,12 @@ ColumnLayout {
} }
StateLayer { StateLayer {
cursorShape: Qt.IBeamCursor function onClicked(): void {
hoverEnabled: false
onClicked: {
parent.forceActiveFocus(); parent.forceActiveFocus();
} }
cursorShape: Qt.IBeamCursor
hoverEnabled: false
} }
RowLayout { RowLayout {
@@ -179,11 +179,11 @@ ColumnLayout {
radius: Appearance.rounding.full radius: Appearance.rounding.full
StateLayer { StateLayer {
color: root.greeter.buffer && !root.greeter.launching ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface function onClicked(): void {
onClicked: {
root.greeter.submit(); root.greeter.submit();
} }
color: root.greeter.buffer && !root.greeter.launching ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
} }
MaterialIcon { MaterialIcon {
+3 -58
View File
@@ -2,62 +2,7 @@ import QtQuick
import qs.Config import qs.Config
NumberAnimation { NumberAnimation {
enum Type { duration: MaterialEasing.standardTime
StandardSmall = 0, easing.bezierCurve: MaterialEasing.standard
Standard, easing.type: Easing.BezierSpline
StandardLarge,
StandardExtraLarge,
EmphasizedSmall,
Emphasized,
EmphasizedLarge,
EmphasizedExtraLarge,
FastSpatial,
DefaultSpatial,
SlowSpatial,
FastEffects,
DefaultEffects,
SlowEffects
}
property int type: Anim.DefaultSpatial
duration: {
if (type < Anim.StandardSmall || type > Anim.SlowEffects)
return Appearance.anim.durations.normal;
if (type === Anim.FastSpatial)
return Appearance.anim.durations.expressiveFastSpatial;
if (type === Anim.DefaultSpatial)
return Appearance.anim.durations.expressiveDefaultSpatial;
if (type === Anim.SlowSpatial)
return Appearance.anim.durations.large;
if (type === Anim.FastEffects)
return Appearance.anim.durations.expressiveFastEffects;
if (type === Anim.DefaultEffects)
return Appearance.anim.durations.expressiveEffects;
if (type === Anim.SlowEffects)
return Appearance.anim.durations.expressiveSlowEffects;
const types = ["small", "normal", "large", "extraLarge"];
const idx = type % 4; // 0-7 are the 4 standard types
return Appearance.anim.durations[types[idx]];
}
easing.bezierCurve: {
if (type === Anim.FastSpatial)
return Appearance.anim.curves.expressiveFastSpatial;
if (type === Anim.DefaultSpatial)
return Appearance.anim.curves.expressiveDefaultSpatial;
if (type === Anim.SlowSpatial)
return Appearance.anim.curves.expressiveSlowSpatial;
if (type === Anim.FastEffects)
return Appearance.anim.curves.expressiveFastEffects;
if (type === Anim.DefaultEffects)
return Appearance.anim.curves.expressiveDefaultEffects;
if (type === Anim.SlowEffects)
return Appearance.anim.curves.expressiveSlowEffects;
if (type >= Anim.EmphasizedSmall && type <= Anim.EmphasizedExtraLarge)
return Appearance.anim.curves.emphasized;
return Appearance.anim.curves.standard;
}
} }
+5 -5
View File
@@ -59,15 +59,15 @@ ColumnLayout {
} }
StateLayer { StateLayer {
function onClicked(): void {
root.toggleRequested();
root.expanded = !root.expanded;
}
anchors.fill: parent anchors.fill: parent
color: DynamicColors.palette.m3onSurface color: DynamicColors.palette.m3onSurface
radius: Appearance.rounding.normal radius: Appearance.rounding.normal
showHoverBackground: false showHoverBackground: false
onClicked: {
root.toggleRequested();
root.expanded = !root.expanded;
}
} }
} }
+3 -3
View File
@@ -23,10 +23,10 @@ Button {
} }
StateLayer { StateLayer {
radius: control.radius function onClicked(): void {
onClicked: {
control.clicked(); control.clicked();
} }
radius: control.radius
} }
} }
+4 -4
View File
@@ -33,13 +33,13 @@ RadioButton {
} }
StateLayer { StateLayer {
function onClicked(): void {
root.click();
}
anchors.margins: -7 anchors.margins: -7
color: root.checked ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3primary color: root.checked ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3primary
z: -1 z: -1
onClicked: {
root.click();
}
} }
CustomRect { CustomRect {
+27 -156
View File
@@ -1,174 +1,45 @@
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Templates import QtQuick.Templates
import ZShell.Components
import ZShell
import qs.Components
import qs.Config import qs.Config
Slider { Slider {
id: root id: root
property bool animateWave background: Item {
property color bgColor: enabled ? DynamicColors.palette.m3secondaryContainer : Qt.alpha(DynamicColors.palette.m3onSurface, 0.1)
property color fgColor: enabled ? DynamicColors.palette.m3primary : Qt.alpha(DynamicColors.palette.m3onSurface, 0.38)
property real filledWidth
property real pos: visualPosition
property int waveDuration: 1000
property real waveFrequency: 6
property bool wavy
signal interaction(v: real)
implicitHeight: 12
implicitWidth: 200
contentItem: Item {
anchors.fill: parent
CustomRect { CustomRect {
id: remaining anchors.bottom: parent.bottom
anchors.left: handle.right
anchors.leftMargin: Appearance.spacing.extraSmall
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
bottomLeftRadius: Appearance.rounding.extraSmall / 2
color: root.bgColor
implicitHeight: parent.height * (parent.height <= 12 ? opacity : Math.min(opacity * 2, 1))
opacity: Math.min(width, 12) / 12
radius: Appearance.rounding.small
topLeftRadius: Appearance.rounding.extraSmall / 2
}
CustomRect {
anchors.right: parent.right
anchors.rightMargin: 4 * remaining.opacity
anchors.verticalCenter: parent.verticalCenter
color: root.fgColor
implicitHeight: 4 * remaining.opacity
implicitWidth: implicitHeight
opacity: remaining.opacity
radius: Appearance.rounding.full
}
CustomRect {
id: handle
anchors.left: filled.right
anchors.leftMargin: Appearance.spacing.extraSmall
anchors.verticalCenter: parent.verticalCenter
color: root.fgColor
implicitHeight: {
const mult = parent.height <= 12 ? 3 : 1.2;
const pressMult = parent.height <= 12 ? 4 : 1.5;
return parent.height * (mouse.pressed ? pressMult : mult);
}
implicitWidth: 4
radius: Appearance.rounding.full
Behavior on implicitHeight {
Anim {
type: Anim.FastSpatial
}
}
}
Loader {
id: filled
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.top: parent.top
asynchronous: true bottomRightRadius: root.implicitHeight / 6
sourceComponent: root.wavy ? waveComp : lineComp color: DynamicColors.palette.m3primary
implicitWidth: root.handle.x - root.implicitHeight / 2
radius: Appearance.rounding.full
topRightRadius: root.implicitHeight / 6
} }
Component { CustomRect {
id: lineComp anchors.bottom: parent.bottom
anchors.right: parent.right
CustomRect { anchors.top: parent.top
bottomRightRadius: Appearance.rounding.extraSmall / 2 bottomLeftRadius: root.implicitHeight / 6
color: root.fgColor color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: root.height implicitWidth: parent.width - root.handle.x - root.handle.implicitWidth - root.implicitHeight / 2
implicitWidth: root.filledWidth radius: Appearance.rounding.full
radius: Appearance.rounding.small topLeftRadius: root.implicitHeight / 6
topRightRadius: Appearance.rounding.extraSmall / 2
}
}
Component {
id: waveComp
WavyLine {
color: root.fgColor
frequency: root.waveFrequency
fullLength: root.width - handle.implicitWidth - handle.anchors.leftMargin
implicitHeight: lineWidth * amplitudeMultiplier * 2 + lineWidth
implicitWidth: root.filledWidth
lineWidth: root.height * 0.7
startX: x
Behavior on color {
CAnim {
}
}
Anim on waveProgress {
duration: root.waveDuration
easing.type: Easing.Linear
from: 0
loops: Animation.Infinite
paused: !root.animateWave
running: true
to: 1
}
}
} }
} }
Behavior on filledWidth { handle: CustomRect {
id: widthBehavior
Anim {
}
}
Component.onCompleted: filledWidth = Qt.binding(() => (width - handle.implicitWidth - handle.anchors.leftMargin) * pos)
Binding {
id: posBinding
property: "pos"
target: root
value: ZUtils.clamp(mouse.pressStartPos + mouse.dragMovement, 0, 1)
when: mouse.pressed
}
MouseArea {
id: mouse
property real dragMovement
property real pressStartPos
property real pressStartX
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
implicitHeight: handle.implicitHeight color: DynamicColors.palette.m3primary
preventStealing: true implicitHeight: 15
implicitWidth: 5
radius: Appearance.rounding.full
x: root.visualPosition * root.availableWidth - implicitWidth / 2
onPositionChanged: e => { MouseArea {
dragMovement = (e.x - pressStartX) / width; acceptedButtons: Qt.NoButton
root.interaction(posBinding.value); anchors.fill: parent
} cursorShape: Qt.PointingHandCursor
onPressed: e => {
widthBehavior.enabled = false;
pressStartX = e.x;
pressStartPos = root.visualPosition;
}
onReleased: e => {
root.interaction(posBinding.value);
widthBehavior.enabled = true;
dragMovement = 0;
} }
} }
} }
+13 -12
View File
@@ -28,7 +28,6 @@ RowLayout {
CustomTextField { CustomTextField {
id: textField id: textField
color: root.enabled ? DynamicColors.palette.m3onSurface : Qt.alpha(DynamicColors.palette.m3onSurface, 0.5)
implicitHeight: upButton.implicitHeight implicitHeight: upButton.implicitHeight
inputMethodHints: Qt.ImhFormattedNumbersOnly inputMethodHints: Qt.ImhFormattedNumbersOnly
leftPadding: Appearance.padding.normal leftPadding: Appearance.padding.normal
@@ -37,7 +36,7 @@ RowLayout {
text: root.isEditing ? text : root.displayText text: root.isEditing ? text : root.displayText
background: CustomRect { background: CustomRect {
color: root.enabled ? DynamicColors.tPalette.m3surfaceContainerHigh : DynamicColors.tPalette.m3surfaceContainerLow color: DynamicColors.tPalette.m3surfaceContainerHigh
implicitWidth: 100 implicitWidth: 100
radius: Appearance.rounding.full radius: Appearance.rounding.full
} }
@@ -86,7 +85,7 @@ RowLayout {
CustomRect { CustomRect {
id: upButton id: upButton
color: root.enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 1) color: DynamicColors.palette.m3primary
implicitHeight: upIcon.implicitHeight + Appearance.padding.small * 2 implicitHeight: upIcon.implicitHeight + Appearance.padding.small * 2
implicitWidth: implicitHeight implicitWidth: implicitHeight
radius: Appearance.rounding.full radius: Appearance.rounding.full
@@ -94,9 +93,7 @@ RowLayout {
StateLayer { StateLayer {
id: upState id: upState
color: DynamicColors.palette.m3onPrimary function onClicked(): void {
onClicked: {
let newValue = Math.min(root.max, root.value + root.step); let newValue = Math.min(root.max, root.value + root.step);
// Round to avoid floating point precision errors // Round to avoid floating point precision errors
const decimals = root.step < 1 ? Math.max(1, Math.ceil(-Math.log10(root.step))) : 0; const decimals = root.step < 1 ? Math.max(1, Math.ceil(-Math.log10(root.step))) : 0;
@@ -105,6 +102,9 @@ RowLayout {
root.displayText = newValue.toString(); root.displayText = newValue.toString();
root.valueModified(newValue); root.valueModified(newValue);
} }
color: DynamicColors.palette.m3onPrimary
onPressAndHold: timer.start() onPressAndHold: timer.start()
onReleased: timer.stop() onReleased: timer.stop()
} }
@@ -113,13 +113,13 @@ RowLayout {
id: upIcon id: upIcon
anchors.centerIn: parent anchors.centerIn: parent
color: root.enabled ? DynamicColors.palette.m3onPrimary : Qt.alpha(DynamicColors.palette.m3onSurface, 0.5) color: DynamicColors.palette.m3onPrimary
text: "keyboard_arrow_up" text: "keyboard_arrow_up"
} }
} }
CustomRect { CustomRect {
color: root.enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 1) color: DynamicColors.palette.m3primary
implicitHeight: downIcon.implicitHeight + Appearance.padding.small * 2 implicitHeight: downIcon.implicitHeight + Appearance.padding.small * 2
implicitWidth: implicitHeight implicitWidth: implicitHeight
radius: Appearance.rounding.full radius: Appearance.rounding.full
@@ -127,9 +127,7 @@ RowLayout {
StateLayer { StateLayer {
id: downState id: downState
color: DynamicColors.palette.m3onPrimary function onClicked(): void {
onClicked: {
let newValue = Math.max(root.min, root.value - root.step); let newValue = Math.max(root.min, root.value - root.step);
// Round to avoid floating point precision errors // Round to avoid floating point precision errors
const decimals = root.step < 1 ? Math.max(1, Math.ceil(-Math.log10(root.step))) : 0; const decimals = root.step < 1 ? Math.max(1, Math.ceil(-Math.log10(root.step))) : 0;
@@ -138,6 +136,9 @@ RowLayout {
root.displayText = newValue.toString(); root.displayText = newValue.toString();
root.valueModified(newValue); root.valueModified(newValue);
} }
color: DynamicColors.palette.m3onPrimary
onPressAndHold: timer.start() onPressAndHold: timer.start()
onReleased: timer.stop() onReleased: timer.stop()
} }
@@ -146,7 +147,7 @@ RowLayout {
id: downIcon id: downIcon
anchors.centerIn: parent anchors.centerIn: parent
color: root.enabled ? DynamicColors.palette.m3onPrimary : Qt.alpha(DynamicColors.palette.m3onSurface, 0.5) color: DynamicColors.palette.m3onPrimary
text: "keyboard_arrow_down" text: "keyboard_arrow_down"
} }
} }
+69 -36
View File
@@ -1,6 +1,7 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import qs.Config import qs.Config
import qs.Helpers
Row { Row {
id: root id: root
@@ -11,46 +12,66 @@ Row {
} }
property alias active: menu.active property alias active: menu.active
property color colour: type == CustomSplitButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondaryContainer property color color: type == CustomSplitButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondaryContainer
property bool disabled property bool disabled
property color disabledColour: Qt.alpha(DynamicColors.palette.m3onSurface, 0.1) property color disabledColor: Qt.alpha(DynamicColors.palette.m3onSurface, 0.1)
property color disabledTextColour: Qt.alpha(DynamicColors.palette.m3onSurface, 0.38) property color disabledTextColor: Qt.alpha(DynamicColors.palette.m3onSurface, 0.38)
readonly property alias expandBtn: expandBtn
property alias expanded: menu.expanded property alias expanded: menu.expanded
property string fallbackIcon property string fallbackIcon
property string fallbackText property string fallbackText
property real horizontalPadding: Appearance.padding.larger property real horizontalPadding: Appearance.padding.normal
readonly property alias iconLabel: iconLabel property alias iconLabel: iconLabel
readonly property alias label: label property alias label: label
readonly property alias menu: menu property alias menu: menu
property alias menuItems: menu.items property alias menuItems: menu.items
property bool menuOnTop property bool menuOnTop
property real minLeftWidth property alias stateLayer: stateLayer
readonly property alias stateLayer: stateLayer property color textColor: type == CustomSplitButton.Filled ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSecondaryContainer
property color textColour: type == CustomSplitButton.Filled ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSecondaryContainer
readonly property alias textRow: textRow
property int type: CustomSplitButton.Filled property int type: CustomSplitButton.Filled
property real verticalPadding: Appearance.padding.small property real verticalPadding: Appearance.padding.smaller
spacing: Math.floor(Appearance.spacing.extraSmall) function closeDropdown(): void {
SettingsDropdowns.close(menu);
}
function openDropdown(): void {
if (root.disabled)
return;
SettingsDropdowns.open(menu, root);
}
function toggleDropdown(): void {
if (root.disabled)
return;
SettingsDropdowns.toggle(menu, root);
}
spacing: Math.floor(Appearance.spacing.small / 2)
onExpandedChanged: {
if (!expanded)
SettingsDropdowns.forget(menu);
}
CustomRect { CustomRect {
bottomRightRadius: Appearance.rounding.small / 2 bottomRightRadius: Appearance.rounding.small / 2
color: root.disabled ? root.disabledColour : root.colour color: root.disabled ? root.disabledColor : root.color
implicitHeight: expandBtn.implicitHeight implicitHeight: expandBtn.implicitHeight
implicitWidth: Math.max(root.minLeftWidth, textRow.implicitWidth + root.horizontalPadding * 2) implicitWidth: textRow.implicitWidth + root.horizontalPadding * 2
radius: implicitHeight / 2 * Math.min(1, Appearance.rounding.scale) radius: implicitHeight / 2 * Math.min(1, Appearance.rounding.scale)
topRightRadius: Appearance.rounding.small / 2 topRightRadius: Appearance.rounding.small / 2
StateLayer { StateLayer {
id: stateLayer id: stateLayer
bottomRightRadius: parent.bottomRightRadius function onClicked(): void {
color: root.textColour root.active?.clicked();
disabled: root.disabled }
topRightRadius: parent.topRightRadius
onClicked: root.active?.clicked() color: root.textColor
disabled: root.disabled
rect.bottomRightRadius: parent.bottomRightRadius
rect.topRightRadius: parent.topRightRadius
} }
RowLayout { RowLayout {
@@ -65,7 +86,7 @@ Row {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
animate: true animate: true
color: root.disabled ? root.disabledTextColour : root.textColour color: root.disabled ? root.disabledTextColor : root.textColor
fill: 1 fill: 1
text: root.active?.activeIcon ?? root.fallbackIcon text: root.active?.activeIcon ?? root.fallbackIcon
} }
@@ -77,12 +98,12 @@ Row {
Layout.preferredWidth: implicitWidth Layout.preferredWidth: implicitWidth
animate: true animate: true
clip: true clip: true
color: root.disabled ? root.disabledTextColour : root.textColour color: root.disabled ? root.disabledTextColor : root.textColor
text: root.active?.activeText ?? root.fallbackText text: root.active?.activeText ?? root.fallbackText
Behavior on Layout.preferredWidth { Behavior on Layout.preferredWidth {
Anim { Anim {
type: Anim.Emphasized easing.bezierCurve: Appearance.anim.curves.emphasized
} }
} }
} }
@@ -95,7 +116,7 @@ Row {
property real rad: root.expanded ? implicitHeight / 2 * Math.min(1, Appearance.rounding.scale) : Appearance.rounding.small / 2 property real rad: root.expanded ? implicitHeight / 2 * Math.min(1, Appearance.rounding.scale) : Appearance.rounding.small / 2
bottomLeftRadius: rad bottomLeftRadius: rad
color: root.disabled ? root.disabledColour : root.colour color: root.disabled ? root.disabledColor : root.color
implicitHeight: expandIcon.implicitHeight + root.verticalPadding * 2 implicitHeight: expandIcon.implicitHeight + root.verticalPadding * 2
implicitWidth: implicitHeight implicitWidth: implicitHeight
radius: implicitHeight / 2 * Math.min(1, Appearance.rounding.scale) radius: implicitHeight / 2 * Math.min(1, Appearance.rounding.scale)
@@ -109,12 +130,14 @@ Row {
StateLayer { StateLayer {
id: expandStateLayer id: expandStateLayer
color: root.textColour function onClicked(): void {
root.toggleDropdown();
}
color: root.textColor
disabled: root.disabled disabled: root.disabled
rect.bottomLeftRadius: parent.bottomLeftRadius rect.bottomLeftRadius: parent.bottomLeftRadius
rect.topLeftRadius: parent.topLeftRadius rect.topLeftRadius: parent.topLeftRadius
onClicked: root.expanded = !root.expanded
} }
MaterialIcon { MaterialIcon {
@@ -122,7 +145,7 @@ Row {
anchors.centerIn: parent anchors.centerIn: parent
anchors.horizontalCenterOffset: root.expanded ? 0 : -Math.floor(root.verticalPadding / 4) anchors.horizontalCenterOffset: root.expanded ? 0 : -Math.floor(root.verticalPadding / 4)
color: root.disabled ? root.disabledTextColour : root.textColour color: root.disabled ? root.disabledTextColor : root.textColor
rotation: root.expanded ? 180 : 0 rotation: root.expanded ? 180 : 0
text: "expand_more" text: "expand_more"
@@ -135,14 +158,24 @@ Row {
} }
} }
} }
}
Menu { Menu {
id: menu id: menu
attachSideY: root.menuOnTop ? Menu.Top : Menu.Bottom anchors.bottomMargin: Appearance.spacing.small
attachTo: expandBtn anchors.right: parent.right
marginY: Appearance.spacing.small * (root.menuOnTop ? -1 : 1) anchors.top: parent.bottom
thisSideY: root.menuOnTop ? Menu.Bottom : Menu.Top anchors.topMargin: Appearance.spacing.small
states: State {
when: root.menuOnTop
AnchorChanges {
anchors.bottom: expandBtn.top
anchors.top: undefined
target: menu
}
}
}
} }
} }
+8 -19
View File
@@ -8,32 +8,19 @@ Item {
id: root id: root
property alias active: splitButton.active property alias active: splitButton.active
property alias buttonAlias: splitButton property bool enabled: true
property alias expanded: splitButton.expanded property alias expanded: splitButton.expanded
property int expandedZ: 100 property int expandedZ: 100
required property string label required property string label
property alias menuItems: splitButton.menuItems property alias menuItems: splitButton.menuItems
property bool shouldBeActive: true
property alias type: splitButton.type property alias type: splitButton.type
signal selected(item: MenuItem) signal selected(item: MenuItem)
anchors.left: parent.left Layout.fillWidth: true
anchors.right: parent.right Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2
clip: false clip: false
implicitHeight: row.implicitHeight + Appearance.padding.smaller * 2 z: root.expanded ? expandedZ : -1
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
z: splitButton.menu.implicitHeight > 0 ? expandedZ : 1
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
RowLayout { RowLayout {
id: row id: row
@@ -49,6 +36,7 @@ Item {
color: root.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSurfaceVariant color: root.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.larger font.pointSize: Appearance.font.size.larger
text: root.label text: root.label
z: root.expanded ? root.expandedZ : -1
} }
CustomSplitButton { CustomSplitButton {
@@ -56,13 +44,14 @@ Item {
enabled: root.enabled enabled: root.enabled
type: CustomSplitButton.Filled type: CustomSplitButton.Filled
z: 2 z: root.expanded ? root.expandedZ : -1
menu.onItemSelected: item => { menu.onItemSelected: item => {
root.selected(item); root.selected(item);
splitButton.closeDropdown();
} }
stateLayer.onClicked: { stateLayer.onClicked: {
splitButton.expanded = !splitButton.expanded; splitButton.toggleDropdown();
} }
} }
} }
+16 -18
View File
@@ -1,6 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Shapes
import QtQuick.Templates import QtQuick.Templates
import QtQuick.Shapes
import qs.Config import qs.Config
Switch { Switch {
@@ -12,41 +12,38 @@ Switch {
implicitWidth: implicitIndicatorWidth implicitWidth: implicitIndicatorWidth
indicator: CustomRect { indicator: CustomRect {
color: root.checked && root.enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, root.cLayer) color: root.checked ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, root.cLayer)
implicitHeight: Appearance.font.size.medium + Appearance.padding.normal * 2 implicitHeight: 13 + 7 * 2
implicitWidth: implicitHeight * 1.7 implicitWidth: implicitHeight * 1.7
radius: Appearance.rounding.full radius: Appearance.rounding.full
CustomRect { CustomRect {
readonly property real nonAnimWidth: root.pressed ? implicitHeight * 1.2 : implicitHeight readonly property real nonAnimWidth: root.pressed ? implicitHeight * 1.3 : implicitHeight
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: root.checked && root.enabled ? DynamicColors.palette.m3onPrimary : DynamicColors.layer(DynamicColors.palette.m3outline, root.cLayer + 1) color: root.checked ? DynamicColors.palette.m3onPrimary : DynamicColors.layer(DynamicColors.palette.m3outline, root.cLayer + 1)
implicitHeight: parent.implicitHeight - Appearance.padding.extraSmall implicitHeight: parent.implicitHeight - 10
implicitWidth: nonAnimWidth implicitWidth: nonAnimWidth
radius: Appearance.rounding.full radius: Appearance.rounding.full
x: root.checked ? parent.implicitWidth - nonAnimWidth - Appearance.padding.extraSmall / 2 : Appearance.padding.extraSmall / 2 x: root.checked ? parent.implicitWidth - nonAnimWidth - 10 / 2 : 10 / 2
Behavior on implicitWidth { Behavior on implicitWidth {
Anim { Anim {
type: Anim.FastSpatial
} }
} }
Behavior on x { Behavior on x {
Anim { Anim {
type: Anim.FastSpatial
} }
} }
CustomRect { CustomRect {
anchors.fill: parent anchors.fill: parent
color: root.checked && root.enabled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurface color: root.checked ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurface
opacity: root.pressed ? 0.1 : root.hovered ? 0.08 : 0 opacity: root.pressed ? 0.1 : root.hovered ? 0.08 : 0
radius: parent.radius radius: parent.radius
Behavior on opacity { Behavior on opacity {
Anim { Anim {
type: Anim.DefaultEffects
} }
} }
} }
@@ -66,14 +63,14 @@ Switch {
} }
property point end2: { property point end2: {
if (root.pressed) if (root.pressed)
return Qt.point(width * 0.8, height / 2); return Qt.point(width, height / 2);
if (root.checked) if (root.checked)
return Qt.point(width * 0.85, height * 0.2); return Qt.point(width * 0.85, height * 0.2);
return Qt.point(width * 0.85, height * 0.15); return Qt.point(width * 0.85, height * 0.15);
} }
property point start1: { property point start1: {
if (root.pressed) if (root.pressed)
return Qt.point(width * 0.2, height / 2); return Qt.point(width * 0.1, height / 2);
if (root.checked) if (root.checked)
return Qt.point(width * 0.15, height / 2); return Qt.point(width * 0.15, height / 2);
return Qt.point(width * 0.15, height * 0.15); return Qt.point(width * 0.15, height * 0.15);
@@ -91,7 +88,7 @@ Switch {
anchors.centerIn: parent anchors.centerIn: parent
asynchronous: true asynchronous: true
height: parent.implicitHeight - Appearance.padding.larger height: parent.implicitHeight - Appearance.padding.small * 2
preferredRendererType: Shape.CurveRenderer preferredRendererType: Shape.CurveRenderer
width: height width: height
@@ -113,11 +110,11 @@ Switch {
} }
ShapePath { ShapePath {
capStyle: ShapePath.RoundCap capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
fillColor: "transparent" fillColor: "transparent"
startX: icon.start1.x startX: icon.start1.x
startY: icon.start1.y startY: icon.start1.y
strokeColor: root.checked && root.enabled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3surfaceContainerHighest strokeColor: root.checked ? DynamicColors.palette.m3primary : DynamicColors.palette.m3surfaceContainerHighest
strokeWidth: Appearance.font.size.larger * 0.15 strokeWidth: Appearance.font.size.larger * 0.15
Behavior on strokeColor { Behavior on strokeColor {
@@ -151,7 +148,8 @@ Switch {
} }
component PropAnim: PropertyAnimation { component PropAnim: PropertyAnimation {
duration: Appearance.anim.durations.expressiveFastSpatial duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial easing.bezierCurve: MaterialEasing.expressiveEffects
easing.type: Easing.BezierSpline
} }
} }
-1
View File
@@ -15,7 +15,6 @@ Text {
color: DynamicColors.palette.m3onSurface color: DynamicColors.palette.m3onSurface
font.family: Appearance.font.family.sans font.family: Appearance.font.family.sans
font.pointSize: Appearance.font.size.normal font.pointSize: Appearance.font.size.normal
linkColor: DynamicColors.palette.m3onPrimaryFixedVariant
renderType: Text.NativeRendering renderType: Text.NativeRendering
textFormat: Text.PlainText textFormat: Text.PlainText
+1 -2
View File
@@ -1,6 +1,6 @@
import qs.Config
import QtQuick import QtQuick
import QtQuick.Effects import QtQuick.Effects
import qs.Config
RectangularShadow { RectangularShadow {
property real dp: [0, 1, 3, 6, 8, 12][level] property real dp: [0, 1, 3, 6, 8, 12][level]
@@ -13,7 +13,6 @@ RectangularShadow {
Behavior on dp { Behavior on dp {
Anim { Anim {
type: Anim.SlowEffects
} }
} }
} }
-33
View File
@@ -1,33 +0,0 @@
import QtQuick
import QtQuick.Controls
import qs.Config
IconButton {
id: root
required property bool shouldBeVisible
opacity: 0
scale: 0
visible: root.scale > 0
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.small
}
}
Behavior on scale {
Anim {
}
}
onShouldBeVisibleChanged: {
if (root.shouldBeVisible) {
root.opacity = 1;
root.scale = 1;
} else {
root.opacity = 0;
root.scale = 0;
}
}
}
+6 -5
View File
@@ -41,11 +41,12 @@ CustomRect {
color: type === IconButton.Text ? "transparent" : disabled ? disabledColour : internalChecked ? activeColour : inactiveColour color: type === IconButton.Text ? "transparent" : disabled ? disabledColour : internalChecked ? activeColour : inactiveColour
implicitHeight: label.implicitHeight + padding * 2 implicitHeight: label.implicitHeight + padding * 2
implicitWidth: implicitHeight implicitWidth: implicitHeight
radius: internalChecked ? 6 : (implicitHeight / 2 * Math.min(1, 1)) * Appearance.rounding.scale radius: internalChecked ? 6 : implicitHeight / 2 * Math.min(1, 1)
Behavior on radius { Behavior on radius {
Anim { Anim {
id: radiusAnim id: radiusAnim
} }
} }
@@ -54,14 +55,14 @@ CustomRect {
StateLayer { StateLayer {
id: stateLayer id: stateLayer
color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour function onClicked(): void {
disabled: root.disabled
onClicked: {
if (root.toggle) if (root.toggle)
root.internalChecked = !root.internalChecked; root.internalChecked = !root.internalChecked;
root.clicked(); root.clicked();
} }
color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour
disabled: root.disabled
} }
MaterialIcon { MaterialIcon {
-2
View File
@@ -102,7 +102,6 @@ Item {
animate: root.animate animate: root.animate
animateProp: "opacity" animateProp: "opacity"
color: root.color color: root.color
font.pointSize: elideText.font.pointSize
text: elideText.text text: elideText.text
} }
@@ -112,7 +111,6 @@ Item {
animate: root.animate animate: root.animate
animateProp: "opacity" animateProp: "opacity"
color: root.color color: root.color
font.pointSize: elideText.font.pointSize
text: t1.text text: t1.text
x: t1.width + root.gap x: t1.width + root.gap
} }
+69 -129
View File
@@ -2,169 +2,109 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell
import qs.Config import qs.Config
import qs.Drawers
MouseArea { Elevation {
id: root id: root
enum Side {
Top,
Bottom,
Left,
Right
}
property MenuItem active: items[0] ?? null property MenuItem active: items[0] ?? null
property int attachSideX: Menu.Right
property int attachSideY: Menu.Bottom
required property Item attachTo
property bool expanded property bool expanded
property list<MenuItem> items property list<MenuItem> items
property real marginX
property real marginY
property int thisSideX: Menu.Right
property int thisSideY: Menu.Top
signal itemSelected(item: MenuItem) signal itemSelected(item: MenuItem)
anchors.fill: parent implicitHeight: root.expanded ? column.implicitHeight + Appearance.padding.small * 2 : 0
enabled: expanded implicitWidth: Math.max(200, column.implicitWidth)
layer.enabled: opacity < 1 level: 2
opacity: expanded ? 1 : 0 opacity: root.expanded ? 1 : 0
parent: { radius: Appearance.rounding.normal
const win = QsWindow.window;
const contentWin = win as Windows;
return contentWin ? contentWin.interactionWrapper : (win as QsWindow).contentItem;
}
Behavior on implicitHeight {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
Behavior on opacity { Behavior on opacity {
Anim { Anim {
type: Anim.DefaultEffects duration: Appearance.anim.durations.expressiveDefaultSpatial
} }
} }
onClicked: expanded = false CustomClippingRect {
anchors.fill: parent
color: DynamicColors.palette.m3surfaceContainer
radius: parent.radius
TransformWatcher { ColumnLayout {
id: watcher id: column
a: root.parent anchors.left: parent.left
b: root.attachTo anchors.right: parent.right
} anchors.verticalCenter: parent.verticalCenter
spacing: 5
Elevation { Repeater {
id: menu model: root.items
implicitHeight: column.implicitHeight + column.anchors.margins * 2 CustomRect {
implicitWidth: Math.max(200, column.implicitWidth + column.anchors.margins * 2) id: item
level: 2
radius: Appearance.rounding.medium
x: {
watcher.transform;
const item = root.attachTo;
let off = root.attachSideX === Menu.Left ? 0 : item.width;
if (root.thisSideX === Menu.Right)
off -= width;
return item.mapToItem(root.parent, off, 0).x + root.marginX;
}
y: {
watcher.transform;
const item = root.attachTo;
let off = root.attachSideY === Menu.Top ? 0 : item.height;
if (root.thisSideY === Menu.Bottom)
off -= height;
return item.mapToItem(root.parent, 0, off).y + root.marginY;
}
transform: Scale { readonly property bool active: modelData === root.active
origin.y: root.thisSideY === Menu.Bottom ? menu.height : 0 required property int index
yScale: root.expanded ? 1 : 0.1 required property MenuItem modelData
Behavior on yScale { Layout.fillWidth: true
Anim { implicitHeight: menuOptionRow.implicitHeight + Appearance.padding.normal * 2
} implicitWidth: menuOptionRow.implicitWidth + Appearance.padding.normal * 2
}
}
CustomRect {
anchors.fill: parent
color: DynamicColors.palette.m3surfaceContainerLow
radius: parent.radius
ColumnLayout {
id: column
anchors.fill: parent
anchors.margins: Appearance.padding.extraSmall
spacing: Appearance.spacing.extraSmall
Repeater {
id: repeater
model: root.items
CustomRect { CustomRect {
id: item anchors.fill: parent
anchors.leftMargin: Appearance.padding.small
readonly property bool active: modelData === root.active anchors.rightMargin: Appearance.padding.small
required property int index color: Qt.alpha(DynamicColors.palette.m3secondaryContainer, active ? 1 : 0)
required property MenuItem modelData radius: Appearance.rounding.normal - Appearance.padding.small
Layout.fillWidth: true
color: Qt.alpha(DynamicColors.palette.m3tertiaryContainer, active ? 1 : 0)
implicitHeight: menuOptionRow.implicitHeight + Appearance.padding.larger * 2
implicitWidth: menuOptionRow.implicitWidth + Appearance.padding.larger * 2
radius: Appearance.rounding.small
Behavior on radius {
Anim {
}
}
StateLayer { StateLayer {
color: item.active ? DynamicColors.palette.m3onTertiaryContainer : DynamicColors.palette.m3onSurface function onClicked(): void {
disabled: !root.expanded
onClicked: {
root.itemSelected(item.modelData); root.itemSelected(item.modelData);
root.active = item.modelData; root.active = item.modelData;
item.modelData.clicked();
root.expanded = false; root.expanded = false;
} }
color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
disabled: !root.expanded
}
}
RowLayout {
id: menuOptionRow
anchors.fill: parent
anchors.margins: Appearance.padding.normal
spacing: Appearance.spacing.small
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurfaceVariant
text: item.modelData.icon
} }
RowLayout { CustomText {
id: menuOptionRow Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
text: item.modelData.text
}
anchors.fill: parent Loader {
anchors.margins: Appearance.padding.larger Layout.alignment: Qt.AlignVCenter
spacing: Appearance.spacing.small active: item.modelData.trailingIcon.length > 0
visible: active
MaterialIcon { sourceComponent: MaterialIcon {
Layout.alignment: Qt.AlignVCenter color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
color: item.active ? DynamicColors.palette.m3onTertiaryContainer : DynamicColors.palette.m3onSurfaceVariant text: item.modelData.trailingIcon
text: item.modelData.icon
}
CustomText {
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
color: item.active ? DynamicColors.palette.m3onTertiaryContainer : DynamicColors.palette.m3onSurface
text: item.modelData.text
}
Loader {
Layout.alignment: Qt.AlignVCenter
active: item.modelData.trailingIcon.length > 0
asynchronous: true
visible: active
sourceComponent: MaterialIcon {
color: item.active ? DynamicColors.palette.m3onTertiaryContainer : DynamicColors.palette.m3onSurfaceVariant
text: item.modelData.trailingIcon
}
} }
} }
} }
-2
View File
@@ -29,7 +29,6 @@ Elevation {
level: root.expanded ? 2 : 0 level: root.expanded ? 2 : 0
radius: itemHeight / 2 radius: itemHeight / 2
visible: implicitHeight > 0 visible: implicitHeight > 0
z: root.expanded ? 100 : 0
Behavior on implicitHeight { Behavior on implicitHeight {
Anim { Anim {
@@ -69,7 +68,6 @@ Elevation {
anchors.fill: parent anchors.fill: parent
color: DynamicColors.palette.m3surfaceContainer color: DynamicColors.palette.m3surfaceContainer
radius: parent.radius radius: parent.radius
z: root.z
// Main visible spinner: normal/outside text color // Main visible spinner: normal/outside text color
PathView { PathView {
+69 -174
View File
@@ -1,53 +1,15 @@
import QtQuick
import QtQuick.Shapes
import ZShell
import ZShell.Components
import qs.Helpers
import qs.Config import qs.Config
import QtQuick
MouseArea { MouseArea {
id: root id: root
property alias bottomLeftRadius: base.bottomLeftRadius property color color: DynamicColors.palette.m3onSurface
property alias bottomRightRadius: base.bottomRightRadius
property real circleRadius
property alias color: base.color
property bool disabled property bool disabled
readonly property real endRadius: { property real radius: parent?.radius ?? 0
const d1 = distSq(0, 0); property alias rect: hoverLayer
const d2 = distSq(width, 0);
const d3 = distSq(0, height);
const d4 = distSq(width, height);
return (Math.sqrt(Math.max(d1, d2, d3, d4)) + (shapeMorph ? 24 : 0)) * 1.3;
}
property real endRadiusAtPress
property bool manualPressOverride
property real pressX: width / 2
property real pressY: height / 2
property alias radius: base.radius
readonly property alias rect: base
property bool shapeMorph
property bool showHoverBackground: true
property real stateOpacity: containsMouse ? 0.08 : 0
property alias topLeftRadius: base.topLeftRadius
property alias topRightRadius: base.topRightRadius
function clamp(r: real): real { function onClicked(): void {
return Math.max(0, Math.min(r, width / 2, height / 2));
}
function distSq(x: real, y: real): real {
return (pressX - x) ** 2 + (pressY - y) ** 2;
}
function press(x: real, y: real): void {
pressX = x;
pressY = y;
fadeAnim.complete();
circleRadius = 0;
circle.opacity = 0.1;
rippleAnim.restart();
endRadiusAtPress = endRadius;
} }
anchors.fill: parent anchors.fill: parent
@@ -55,146 +17,79 @@ MouseArea {
enabled: !disabled enabled: !disabled
hoverEnabled: true hoverEnabled: true
Behavior on stateOpacity { onClicked: event => !disabled && onClicked(event)
onPressed: event => {
if (disabled)
return;
rippleAnim.x = event.x;
rippleAnim.y = event.y;
const dist = (ox, oy) => ox * ox + oy * oy;
rippleAnim.radius = Math.sqrt(Math.max(dist(event.x, event.y), dist(event.x, height - event.y), dist(width - event.x, event.y), dist(width - event.x, height - event.y)));
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 { Anim {
type: Anim.DefaultEffects easing.bezierCurve: MaterialEasing.standardDecel
from: 0
properties: "implicitWidth,implicitHeight"
target: ripple
to: rippleAnim.radius * 2
}
Anim {
property: "opacity"
target: ripple
to: 0
} }
} }
onCircleRadiusChanged: { CustomClippingRect {
if (!(pressed || manualPressOverride) && circleRadius > endRadiusAtPress * 0.99 && !fadeAnim.running) id: hoverLayer
fadeAnim.start();
}
onClicked: event => !disabled && onClicked(event)
onManualPressOverrideChanged: {
if (!(pressed || manualPressOverride) && circleRadius > endRadiusAtPress * 0.99 && !fadeAnim.running)
fadeAnim.start();
}
onPressed: e => press(e.x, e.y)
onPressedChanged: {
if (!(pressed || manualPressOverride) && !rippleAnim.running && circle.opacity > 0)
fadeAnim.start();
}
Anim {
id: rippleAnim
alwaysRunToEnd: true
duration: Appearance.anim.durations.expressiveSlowEffects * 2
easing.bezierCurve: Appearance.anim.curves.standard
property: "circleRadius"
target: root
to: root.endRadius
}
Anim {
id: fadeAnim
property: "opacity"
target: circle
to: 0
type: Anim.SlowEffects
}
CustomRect {
id: base
anchors.fill: parent anchors.fill: parent
bottomLeftRadius: root.parent?.bottomLeftRadius ?? radius ?? 0 border.pixelAligned: false
bottomRightRadius: root.parent?.bottomRightRadius ?? radius ?? 0 color: Qt.alpha(root.color, root.disabled ? 0 : root.pressed ? 0.1 : root.containsMouse ? 0.08 : 0)
color: DynamicColors.palette.m3onSurface radius: root.radius
opacity: root.stateOpacity
// Pick up radius from parent if it has one (parent can be anything with radius props)
// qmllint disable missing-property
radius: root.parent?.radius ?? 0
topLeftRadius: root.parent?.topLeftRadius ?? radius ?? 0
topRightRadius: root.parent?.topRightRadius ?? radius ?? 0
// qmllint enable missing-property
}
Shape { CustomRect {
id: circle id: ripple
anchors.fill: parent border.pixelAligned: false
opacity: 0 color: root.color
preferredRendererType: Shape.CurveRenderer opacity: 0
radius: Appearance.rounding.full
ShapePath { transform: Translate {
fillColor: base.color x: -ripple.width / 2
startX: root.clamp(base.topLeftRadius) y: -ripple.height / 2
startY: 0
strokeColor: "transparent"
strokeWidth: 0
fillGradient: RadialGradient {
centerRadius: root.circleRadius
centerX: root.pressX
centerY: root.pressY
focalX: centerX
focalY: centerY
GradientStop {
color: Qt.alpha(base.color, 1)
position: 0
}
GradientStop {
color: Qt.alpha(base.color, 1)
position: ZUtils.clamp(1 - 0.2 * root.endRadius / root.circleRadius, 0.01, 0.99)
}
GradientStop {
color: Qt.alpha(base.color, ZUtils.clamp((root.circleRadius / root.endRadius - 0.9) / 0.1, 0, 1))
position: 1
}
}
PathLine {
x: root.width - root.clamp(base.topRightRadius)
y: 0
}
PathArc {
radiusX: root.clamp(base.topRightRadius)
radiusY: root.clamp(base.topRightRadius)
relativeX: root.clamp(base.topRightRadius)
relativeY: root.clamp(base.topRightRadius)
}
PathLine {
x: root.width
y: root.height - root.clamp(base.bottomRightRadius)
}
PathArc {
radiusX: root.clamp(base.bottomRightRadius)
radiusY: root.clamp(base.bottomRightRadius)
relativeX: -root.clamp(base.bottomRightRadius)
relativeY: root.clamp(base.bottomRightRadius)
}
PathLine {
x: root.clamp(base.bottomLeftRadius)
y: root.height
}
PathArc {
radiusX: root.clamp(base.bottomLeftRadius)
radiusY: root.clamp(base.bottomLeftRadius)
relativeX: -root.clamp(base.bottomLeftRadius)
relativeY: -root.clamp(base.bottomLeftRadius)
}
PathLine {
x: 0
y: root.clamp(base.topLeftRadius)
}
PathArc {
radiusX: root.clamp(base.topLeftRadius)
radiusY: root.clamp(base.topLeftRadius)
x: root.clamp(base.topLeftRadius)
y: 0
} }
} }
} }
+2 -1
View File
@@ -4,9 +4,10 @@ import Quickshell
Singleton { Singleton {
readonly property AppearanceConf.Anim anim: Config.appearance.anim readonly property AppearanceConf.Anim anim: Config.appearance.anim
readonly property AppearanceConf.Deform deform: Config.appearance.deform
readonly property AppearanceConf.FontStuff font: Config.appearance.font readonly property AppearanceConf.FontStuff font: Config.appearance.font
readonly property AppearanceConf.Padding padding: Config.appearance.padding readonly property AppearanceConf.Padding padding: Config.appearance.padding
// Literally just here to shorten accessing stuff :woe:
// Also kinda so I can keep accessing it with `Appearance.xxx` instead of `Conf.appearance.xxx`
readonly property AppearanceConf.Rounding rounding: Config.appearance.rounding readonly property AppearanceConf.Rounding rounding: Config.appearance.rounding
readonly property AppearanceConf.Spacing spacing: Config.appearance.spacing readonly property AppearanceConf.Spacing spacing: Config.appearance.spacing
readonly property AppearanceConf.Transparency transparency: Config.appearance.transparency readonly property AppearanceConf.Transparency transparency: Config.appearance.transparency
+7 -24
View File
@@ -3,8 +3,6 @@ import Quickshell.Io
JsonObject { JsonObject {
property Anim anim: Anim { property Anim anim: Anim {
} }
property Deform deform: Deform {
}
property FontStuff font: FontStuff { property FontStuff font: FontStuff {
} }
property Padding padding: Padding { property Padding padding: Padding {
@@ -28,13 +26,9 @@ JsonObject {
property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1] property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1]
property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1] property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1] property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
property list<real> expressiveDefaultEffects: [0.34, 0.8, 0.34, 1, 1, 1]
property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1] property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1]
property list<real> expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1] property list<real> expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1]
property list<real> expressiveFastEffects: [0.31, 0.94, 0.34, 1, 1, 1]
property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 1, 1] property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 1, 1]
property list<real> expressiveSlowEffects: [0.34, 0.88, 0.34, 1, 1, 1]
property list<real> expressiveSlowSpatial: [0.39, 1.29, 0.35, 0.98, 1, 1]
property list<real> standard: [0.2, 0, 0, 1, 1, 1] property list<real> standard: [0.2, 0, 0, 1, 1, 1]
property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1] property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1]
property list<real> standardDecel: [0, 0, 0, 1, 1, 1] property list<real> standardDecel: [0, 0, 0, 1, 1, 1]
@@ -42,18 +36,13 @@ JsonObject {
component AnimDurations: JsonObject { component AnimDurations: JsonObject {
property int expressiveDefaultSpatial: 500 * scale property int expressiveDefaultSpatial: 500 * scale
property int expressiveEffects: 200 * scale property int expressiveEffects: 200 * scale
property int expressiveFastEffects: 150 * scale
property int expressiveFastSpatial: 350 * scale property int expressiveFastSpatial: 350 * scale
property int expressiveSlowEffects: 300 * scale
property int extraLarge: 1000 * scale property int extraLarge: 1000 * scale
property int large: 600 * scale property int large: 600 * scale
property int normal: 400 * scale property int normal: 400 * scale
property real scale: 1 property real scale: 1
property int small: 200 * scale property int small: 200 * scale
} }
component Deform: JsonObject {
property real scale: 1
}
component FontFamily: JsonObject { component FontFamily: JsonObject {
property string clock: "Rubik" property string clock: "Rubik"
property string material: "Material Symbols Rounded" property string material: "Material Symbols Rounded"
@@ -63,8 +52,7 @@ JsonObject {
component FontSize: JsonObject { component FontSize: JsonObject {
property int extraLarge: 28 * scale property int extraLarge: 28 * scale
property int large: 18 * scale property int large: 18 * scale
property int larger: 16 * scale property int larger: 15 * scale
property int medium: 14 * scale
property int normal: 13 * scale property int normal: 13 * scale
property real scale: 1 property real scale: 1
property int small: 11 * scale property int small: 11 * scale
@@ -77,33 +65,28 @@ JsonObject {
} }
} }
component Padding: JsonObject { component Padding: JsonObject {
property int extraLargeIncreased: 32 * scale property int large: 15 * scale
property int extraSmall: 4 * scale
property int large: 16 * scale
property int larger: 12 * scale property int larger: 12 * scale
property int normal: 8 * scale property int normal: 10 * scale
property real scale: 1 property real scale: 1
property int small: 5 * scale property int small: 5 * scale
property int smaller: 7 * scale property int smaller: 7 * scale
property int smallest: 2 * scale property int smallest: 2 * scale
} }
component Rounding: JsonObject { component Rounding: JsonObject {
property int extraSmall: 4 * scale
property int full: 1000 * scale property int full: 1000 * scale
property int large: 24 * scale property int large: 25 * scale
property int medium: 16 * scale property int normal: 17 * scale
property int normal: 18 * scale
property real scale: 1 property real scale: 1
property int small: 12 * scale property int small: 12 * scale
property int smallest: 8 * scale property int smallest: 8 * scale
} }
component Spacing: JsonObject { component Spacing: JsonObject {
property int extraSmall: 4 * scale
property int large: 20 * scale property int large: 20 * scale
property int larger: 16 * scale property int larger: 15 * scale
property int normal: 12 * scale property int normal: 12 * scale
property real scale: 1 property real scale: 1
property int small: 8 * scale property int small: 7 * scale
property int smaller: 10 * scale property int smaller: 10 * scale
} }
component Transparency: JsonObject { component Transparency: JsonObject {
-7
View File
@@ -4,11 +4,4 @@ import qs.Config
JsonObject { JsonObject {
property bool enabled: true property bool enabled: true
property int wallFadeDuration: MaterialEasing.standardTime property int wallFadeDuration: MaterialEasing.standardTime
property real alignX: 0.5
property real alignY: 0.5
property real zoom: 1.0
property real sourceClipX: 0
property real sourceClipY: 0
property real sourceClipW: 0
property real sourceClipH: 0
} }
+11 -10
View File
@@ -8,6 +8,10 @@ JsonObject {
id: "workspaces", id: "workspaces",
enabled: true enabled: true
}, },
{
id: "audio",
enabled: true
},
{ {
id: "media", id: "media",
enabled: true enabled: true
@@ -20,6 +24,10 @@ JsonObject {
id: "updates", id: "updates",
enabled: true enabled: true
}, },
{
id: "dash",
enabled: true
},
{ {
id: "spacer", id: "spacer",
enabled: true enabled: true
@@ -33,12 +41,12 @@ JsonObject {
enabled: true enabled: true
}, },
{ {
id: "hyprsunset", id: "tray",
enabled: true enabled: true
}, },
{ {
id: "tray", id: "upower",
enabled: true enabled: false
}, },
{ {
id: "network", id: "network",
@@ -54,13 +62,9 @@ JsonObject {
}, },
] ]
property int height: 34 property int height: 34
property bool hideWhenNotif: false
property Popouts popouts: Popouts { property Popouts popouts: Popouts {
} }
property int rounding: 8 property int rounding: 8
property int smoothing: 32
property Tray tray: Tray {
}
component Popouts: JsonObject { component Popouts: JsonObject {
property bool activeWindow: true property bool activeWindow: true
@@ -71,7 +75,4 @@ JsonObject {
property bool tray: true property bool tray: true
property bool upower: true property bool upower: true
} }
component Tray: JsonObject {
property int trayIconSize: 24
}
} }
-8
View File
@@ -1,13 +1,5 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property Presets presets: Presets {
}
property string schemeType: "vibrant" property string schemeType: "vibrant"
component Presets: JsonObject {
property string accent: ""
property string name: ""
property string variant: ""
}
} }
+7 -55
View File
@@ -4,6 +4,8 @@ import Quickshell
import Quickshell.Io import Quickshell.Io
import ZShell import ZShell
import QtQuick import QtQuick
import qs.Helpers
import qs.Paths
Singleton { Singleton {
id: root id: root
@@ -21,7 +23,6 @@ Singleton {
property alias osd: adapter.osd property alias osd: adapter.osd
property alias overview: adapter.overview property alias overview: adapter.overview
property bool recentlySaved: false property bool recentlySaved: false
property alias screenshot: adapter.screenshot
property alias services: adapter.services property alias services: adapter.services
property alias sidebar: adapter.sidebar property alias sidebar: adapter.sidebar
property alias utilities: adapter.utilities property alias utilities: adapter.utilities
@@ -47,9 +48,6 @@ Singleton {
padding: { padding: {
scale: appearance.padding.scale scale: appearance.padding.scale
}, },
deform: {
scale: appearance.deform.scale
},
font: { font: {
family: { family: {
sans: appearance.font.family.sans, sans: appearance.font.family.sans,
@@ -79,28 +77,16 @@ Singleton {
function serializeBackground(): var { function serializeBackground(): var {
return { return {
wallFadeDuration: background.wallFadeDuration, wallFadeDuration: background.wallFadeDuration,
enabled: background.enabled, enabled: background.enabled
alignX: background.alignX,
sourceClipX: background.sourceClipX,
sourceClipY: background.sourceClipY,
sourceClipW: background.sourceClipW,
sourceClipH: background.sourceClipH,
alignY: background.alignY,
zoom: background.zoom
}; };
} }
function serializeBar(): var { function serializeBar(): var {
return { return {
autoHide: barConfig.autoHide, autoHide: barConfig.autoHide,
hideWhenNotif: barConfig.hideWhenNotif,
rounding: barConfig.rounding, rounding: barConfig.rounding,
border: barConfig.border, border: barConfig.border,
smoothing: barConfig.smoothing,
height: barConfig.height, height: barConfig.height,
tray: {
trayIconSize: barConfig.tray.trayIconSize
},
popouts: { popouts: {
tray: barConfig.popouts.tray, tray: barConfig.popouts.tray,
audio: barConfig.popouts.audio, audio: barConfig.popouts.audio,
@@ -116,12 +102,7 @@ Singleton {
function serializeColors(): var { function serializeColors(): var {
return { return {
schemeType: colors.schemeType, schemeType: colors.schemeType
presets: {
name: colors.presets.name,
variant: colors.presets.variant,
accent: colors.presets.accent
}
}; };
} }
@@ -140,8 +121,7 @@ Singleton {
background: serializeBackground(), background: serializeBackground(),
launcher: serializeLauncher(), launcher: serializeLauncher(),
colors: serializeColors(), colors: serializeColors(),
dock: serializeDock(), dock: serializeDock()
screenshot: serializeScreenshot()
}; };
} }
@@ -192,16 +172,11 @@ Singleton {
return { return {
logo: general.logo, logo: general.logo,
wallpaperPath: general.wallpaperPath, wallpaperPath: general.wallpaperPath,
username: general.username,
desktopIcons: general.desktopIcons, desktopIcons: general.desktopIcons,
dateFormat: general.dateFormat,
color: { color: {
mode: general.color.mode, mode: general.color.mode,
smart: general.color.smart, smart: general.color.smart,
scheduleDark: general.color.scheduleDark,
scheduleHyprsunset: general.color.scheduleHyprsunset,
scheduleHyprsunsetStart: general.color.scheduleHyprsunsetStart,
hyprsunsetTemp: general.color.hyprsunsetTemp,
scheduleHyprsunsetEnd: general.color.scheduleHyprsunsetEnd,
schemeGeneration: general.color.schemeGeneration, schemeGeneration: general.color.schemeGeneration,
scheduleDarkStart: general.color.scheduleDarkStart, scheduleDarkStart: general.color.scheduleDarkStart,
scheduleDarkEnd: general.color.scheduleDarkEnd, scheduleDarkEnd: general.color.scheduleDarkEnd,
@@ -215,10 +190,6 @@ Singleton {
}, },
idle: { idle: {
timeouts: general.idle.timeouts timeouts: general.idle.timeouts
},
battery: {
popupThresholds: general.battery.popupThresholds,
critPerc: general.battery.critPerc
} }
}; };
} }
@@ -227,7 +198,6 @@ Singleton {
return { return {
maxAppsShown: launcher.maxAppsShown, maxAppsShown: launcher.maxAppsShown,
maxWallpapers: launcher.maxWallpapers, maxWallpapers: launcher.maxWallpapers,
uwsm: launcher.uwsm,
actionPrefix: launcher.actionPrefix, actionPrefix: launcher.actionPrefix,
specialPrefix: launcher.specialPrefix, specialPrefix: launcher.specialPrefix,
useFuzzy: { useFuzzy: {
@@ -251,8 +221,6 @@ Singleton {
return { return {
recolorLogo: lock.recolorLogo, recolorLogo: lock.recolorLogo,
enableFprint: lock.enableFprint, enableFprint: lock.enableFprint,
showNotifContent: lock.showNotifContent,
showNotifIcon: lock.showNotifIcon,
maxFprintTries: lock.maxFprintTries, maxFprintTries: lock.maxFprintTries,
blurAmount: lock.blurAmount, blurAmount: lock.blurAmount,
sizes: { sizes: {
@@ -294,24 +262,9 @@ Singleton {
}; };
} }
function serializeScreenshot(): var {
return {
enable_pp: screenshot.enable_pp,
mode: screenshot.mode,
radius: screenshot.radius,
shadow: screenshot.shadow,
rounding: screenshot.rounding,
shadow_blur: screenshot.shadow_blur,
shadow_color: screenshot.shadow_color,
shadow_offset_x: screenshot.shadow_offset_x,
shadow_offset_y: screenshot.shadow_offset_y
};
}
function serializeServices(): var { function serializeServices(): var {
return { return {
weatherLocation: services.weatherLocation, weatherLocation: services.weatherLocation,
updates: services.updates,
useFahrenheit: services.useFahrenheit, useFahrenheit: services.useFahrenheit,
ddcutilService: services.ddcutilService, ddcutilService: services.ddcutilService,
useTwelveHourClock: services.useTwelveHourClock, useTwelveHourClock: services.useTwelveHourClock,
@@ -364,6 +317,7 @@ Singleton {
ElapsedTimer { ElapsedTimer {
id: timer id: timer
} }
Timer { Timer {
@@ -461,8 +415,6 @@ Singleton {
} }
property Overview overview: Overview { property Overview overview: Overview {
} }
property Screenshot screenshot: Screenshot {
}
property Services services: Services { property Services services: Services {
} }
property SidebarConfig sidebar: SidebarConfig { property SidebarConfig sidebar: SidebarConfig {
+64 -12
View File
@@ -29,10 +29,9 @@ Singleton {
readonly property alias wallLuminance: analyser.luminance readonly property alias wallLuminance: analyser.luminance
function alterColor(c: color, a: real, layer: int): color { function alterColor(c: color, a: real, layer: int): color {
const initLuminance = getLuminance(c); const luminance = getLuminance(c);
const luminance = Math.max(initLuminance, 0.001);
const offset = (!light || layer == 1 ? 1 : -layer / 2) * (light ? 0.2 : 0.3) * (0.2 + 0.3 * (1 - transparency.base)) * (1 + wallLuminance * (light ? (layer == 1 ? 3 : 1) : 2.5)); const offset = (!light || layer == 1 ? 1 : -layer / 2) * (light ? 0.2 : 0.3) * (1 - transparency.base) * (1 + wallLuminance * (light ? (layer == 1 ? 3 : 1) : 2.5));
const scale = (luminance + offset) / luminance; const scale = (luminance + offset) / luminance;
const r = Math.max(0, Math.min(1, c.r * scale)); const r = Math.max(0, Math.min(1, c.r * scale));
const g = Math.max(0, Math.min(1, c.g * scale)); const g = Math.max(0, Math.min(1, c.g * scale));
@@ -85,10 +84,6 @@ Singleton {
Config.save(); Config.save();
} }
function swapRG(c: color): color {
return Qt.rgba(c.g, c.r, c.b, c.a);
}
FileView { FileView {
path: "/etc/zshell-greeter/scheme.json" path: "/etc/zshell-greeter/scheme.json"
watchChanges: true watchChanges: true
@@ -100,9 +95,69 @@ Singleton {
ImageAnalyser { ImageAnalyser {
id: analyser id: analyser
source: WallpaperPath.lockscreenBg source: WallpaperPath.currentWallpaperPath
} }
component M3MaccchiatoPalette: QtObject {
property color m3background: "#131317"
property color m3error: "#ffb4ab"
property color m3errorContainer: "#93000a"
property color m3inverseOnSurface: "#303034"
property color m3inversePrimary: "#525b92"
property color m3inverseSurface: "#e4e1e7"
property color m3neutral_paletteKeyColor: "#77767b"
property color m3neutral_variant_paletteKeyColor: "#767680"
property color m3onBackground: "#e4e1e7"
property color m3onError: "#690005"
property color m3onErrorContainer: "#ffdad6"
property color m3onPrimary: "#232c60"
property color m3onPrimaryContainer: "#ffffff"
property color m3onPrimaryFixed: "#0b154b"
property color m3onPrimaryFixedVariant: "#3a4378"
property color m3onSecondary: "#2c2f44"
property color m3onSecondaryContainer: "#b1b3ce"
property color m3onSecondaryFixed: "#171a2e"
property color m3onSecondaryFixedVariant: "#42455c"
property color m3onSuccess: "#213528"
property color m3onSuccessContainer: "#D1E9D6"
property color m3onSurface: "#e4e1e7"
property color m3onSurfaceVariant: "#c6c5d1"
property color m3onTertiary: "#4c1f48"
property color m3onTertiaryContainer: "#000000"
property color m3onTertiaryFixed: "#340831"
property color m3onTertiaryFixedVariant: "#66365f"
property color m3outline: "#90909a"
property color m3outlineVariant: "#46464f"
property color m3primary: "#bac3ff"
property color m3primaryContainer: "#6a73ac"
property color m3primaryFixed: "#dee0ff"
property color m3primaryFixedDim: "#bac3ff"
property color m3primary_paletteKeyColor: "#6a73ac"
property color m3scrim: "#000000"
property color m3secondary: "#c3c5e0"
property color m3secondaryContainer: "#42455c"
property color m3secondaryFixed: "#dfe1fd"
property color m3secondaryFixedDim: "#c3c5e0"
property color m3secondary_paletteKeyColor: "#72758e"
property color m3shadow: "#000000"
property color m3success: "#B5CCBA"
property color m3successContainer: "#374B3E"
property color m3surface: "#131317"
property color m3surfaceBright: "#39393d"
property color m3surfaceContainer: "#1f1f23"
property color m3surfaceContainerHigh: "#2a2a2e"
property color m3surfaceContainerHighest: "#353438"
property color m3surfaceContainerLow: "#1b1b1f"
property color m3surfaceContainerLowest: "#0e0e12"
property color m3surfaceDim: "#131317"
property color m3surfaceTint: "#bac3ff"
property color m3surfaceVariant: "#46464f"
property color m3tertiary: "#f1b3e5"
property color m3tertiaryContainer: "#b77ead"
property color m3tertiaryFixed: "#ffd7f4"
property color m3tertiaryFixedDim: "#f1b3e5"
property color m3tertiary_paletteKeyColor: "#9b6592"
}
component M3Palette: QtObject { component M3Palette: QtObject {
property color m3background: "#191114" property color m3background: "#191114"
property color m3error: "#ffb4ab" property color m3error: "#ffb4ab"
@@ -224,11 +279,8 @@ Singleton {
readonly property color m3tertiary_paletteKeyColor: root.layer(root.palette.m3tertiary_paletteKeyColor) readonly property color m3tertiary_paletteKeyColor: root.layer(root.palette.m3tertiary_paletteKeyColor)
} }
component Transparency: QtObject { component Transparency: QtObject {
readonly property real base: Math.max(0, Math.min(1, Appearance.transparency.base - (root.light ? 0.1 : 0))) readonly property real base: Appearance.transparency.base - (root.light ? 0.1 : 0)
readonly property bool enabled: Appearance.transparency.enabled readonly property bool enabled: Appearance.transparency.enabled
readonly property real layers: Appearance.transparency.layers readonly property real layers: Appearance.transparency.layers
onBaseChanged: debounceTimer.restart()
onEnabledChanged: debounceTimer.restart()
} }
} }
+1 -19
View File
@@ -4,15 +4,13 @@ import Quickshell
JsonObject { JsonObject {
property Apps apps: Apps { property Apps apps: Apps {
} }
property Battery battery: Battery {
}
property Color color: Color { property Color color: Color {
} }
property string dateFormat: "ddd d MMM - hh:mm:ss"
property bool desktopIcons: false property bool desktopIcons: false
property Idle idle: Idle { property Idle idle: Idle {
} }
property string logo: "" property string logo: ""
property string username: ""
property string wallpaperPath: Quickshell.env("HOME") + "/Pictures/Wallpapers" property string wallpaperPath: Quickshell.env("HOME") + "/Pictures/Wallpapers"
component Apps: JsonObject { component Apps: JsonObject {
@@ -21,27 +19,11 @@ JsonObject {
property list<string> playback: ["mpv"] property list<string> playback: ["mpv"]
property list<string> terminal: ["kitty"] property list<string> terminal: ["kitty"]
} }
component Battery: JsonObject {
property int critPerc: 5
property list<var> popupThresholds: [
{
perc: 20,
name: qsTr("Low battery"),
message: qsTr("Battery is low"),
icon: "battery_android_frame_2"
},
]
}
component Color: JsonObject { component Color: JsonObject {
property int hyprsunsetTemp: 5000
property string mode: "dark" property string mode: "dark"
property bool neovimColors: false property bool neovimColors: false
property bool scheduleDark: false
property int scheduleDarkEnd: 0 property int scheduleDarkEnd: 0
property int scheduleDarkStart: 0 property int scheduleDarkStart: 0
property bool scheduleHyprsunset: false
property int scheduleHyprsunsetEnd: 0
property int scheduleHyprsunsetStart: 0
property bool schemeGeneration: true property bool schemeGeneration: true
property bool smart: false property bool smart: false
} }
-1
View File
@@ -91,7 +91,6 @@ JsonObject {
property string specialPrefix: "@" property string specialPrefix: "@"
property UseFuzzy useFuzzy: UseFuzzy { property UseFuzzy useFuzzy: UseFuzzy {
} }
property bool uwsm: true
component Sizes: JsonObject { component Sizes: JsonObject {
property int itemHeight: 50 property int itemHeight: 50
-2
View File
@@ -5,8 +5,6 @@ JsonObject {
property bool enableFprint: true property bool enableFprint: true
property int maxFprintTries: 3 property int maxFprintTries: 3
property bool recolorLogo: false property bool recolorLogo: false
property bool showNotifContent: false
property bool showNotifIcon: true
property Sizes sizes: Sizes { property Sizes sizes: Sizes {
} }
-13
View File
@@ -1,13 +0,0 @@
import Quickshell.Io
JsonObject {
property bool enable_pp: true
property string mode: "manual"
property real radius: 12.0
property bool rounding: false
property bool shadow: true
property real shadow_blur: 22.0
property list<int> shadow_color: [0, 0, 0, 160]
property real shadow_offset_x: 5.0
property real shadow_offset_y: 5.0
}
-1
View File
@@ -14,7 +14,6 @@ JsonObject {
"to": "YT Music" "to": "YT Music"
} }
] ]
property bool updates: true
property bool useFahrenheit: false property bool useFahrenheit: false
property bool useTwelveHourClock: Qt.locale().timeFormat(Locale.ShortFormat).toLowerCase().includes("a") property bool useTwelveHourClock: Qt.locale().timeFormat(Locale.ShortFormat).toLowerCase().includes("a")
property int visualizerBars: 30 property int visualizerBars: 30
+1 -1
View File
@@ -83,7 +83,7 @@ ColumnLayout {
radius: Appearance.rounding.normal - Appearance.padding.smaller radius: Appearance.rounding.normal - Appearance.padding.smaller
StateLayer { StateLayer {
onClicked: { function onClicked(): void {
root.greeter.sessionIndex = index; root.greeter.sessionIndex = index;
} }
} }
+1 -1
View File
@@ -84,7 +84,7 @@ ColumnLayout {
radius: Appearance.rounding.normal - Appearance.padding.smaller radius: Appearance.rounding.normal - Appearance.padding.smaller
StateLayer { StateLayer {
onClicked: { function onClicked(): void {
root.greeter.selectUser(modelData.username); root.greeter.selectUser(modelData.username);
} }
} }
+84 -30
View File
@@ -1,13 +1,19 @@
pragma Singleton pragma Singleton
import Quickshell import Quickshell
import qs.Helpers import Quickshell.Io
import "../scripts/levendist.js" as Levendist
import "../scripts/fuzzysort.js" as Fuzzy import "../scripts/fuzzysort.js" as Fuzzy
import qs.Config
Singleton { Singleton {
id: root id: root
readonly property list<DesktopEntry> list: Array.from(DesktopEntries.applications.values) readonly property list<DesktopEntry> list: Array.from(DesktopEntries.applications.values).filter((app, index, self) => index === self.findIndex(t => (t.id === app.id)))
readonly property var preppedIcons: list.map(a => ({
name: Fuzzy.prepare(`${a.icon} `),
entry: a
}))
readonly property var preppedNames: list.map(a => ({ readonly property var preppedNames: list.map(a => ({
name: Fuzzy.prepare(`${a.name} `), name: Fuzzy.prepare(`${a.name} `),
entry: a entry: a
@@ -30,7 +36,8 @@ Singleton {
"replace": "system-lock-screen" "replace": "system-lock-screen"
} }
] ]
property real scoreThreshold: 0.2 readonly property real scoreGapThreshold: 0.1
readonly property real scoreThreshold: 0.6
property var substitutions: ({ property var substitutions: ({
"code-url-handler": "visual-studio-code", "code-url-handler": "visual-studio-code",
"Code": "visual-studio-code", "Code": "visual-studio-code",
@@ -38,27 +45,64 @@ Singleton {
"pavucontrol-qt": "pavucontrol", "pavucontrol-qt": "pavucontrol",
"wps": "wps-office2019-kprometheus", "wps": "wps-office2019-kprometheus",
"wpsoffice": "wps-office2019-kprometheus", "wpsoffice": "wps-office2019-kprometheus",
"footclient": "foot", "footclient": "foot"
"zen": "zen-browser"
}) })
signal reload function bestFuzzyEntry(search: string, preppedList: list<var>, key: string): var {
const results = Fuzzy.go(search, preppedList, {
function fuzzyQuery(search: string): var { key: key,
return Fuzzy.go(search, preppedNames, { threshold: root.scoreThreshold,
all: true, limit: 2
key: "name"
}).map(r => {
return r.obj.entry;
}); });
if (!results || results.length === 0)
return null;
const best = results[0];
const second = results.length > 1 ? results[1] : null;
if (second && (best.score - second.score) < root.scoreGapThreshold)
return null;
return best.obj.entry;
}
function fuzzyQuery(search: string, preppedList: list<var>): var {
const entry = bestFuzzyEntry(search, preppedList, "name");
return entry ? [entry] : [];
}
function getKebabNormalizedAppName(str: string): string {
return str.toLowerCase().replace(/\s+/g, "-");
}
function getReverseDomainNameAppName(str: string): string {
return str.split('.').slice(-1)[0];
}
function getUndescoreToKebabAppName(str: string): string {
return str.toLowerCase().replace(/_/g, "-");
} }
function guessIcon(str) { function guessIcon(str) {
if (!str || str.length == 0) if (!str || str.length == 0)
return "image-missing"; return "image-missing";
if (iconExists(str))
return str;
const entry = DesktopEntries.byId(str);
if (entry)
return entry.icon;
const heuristicEntry = DesktopEntries.heuristicLookup(str);
if (heuristicEntry)
return heuristicEntry.icon;
if (substitutions[str]) if (substitutions[str])
return substitutions[str]; return substitutions[str];
if (substitutions[str.toLowerCase()])
return substitutions[str.toLowerCase()];
for (let i = 0; i < regexSubstitutions.length; i++) { for (let i = 0; i < regexSubstitutions.length; i++) {
const substitution = regexSubstitutions[i]; const substitution = regexSubstitutions[i];
@@ -67,25 +111,35 @@ Singleton {
return replacedName; return replacedName;
} }
if (iconExists(str)) const lowercased = str.toLowerCase();
return str; if (iconExists(lowercased))
return lowercased;
let guessStr = str; const reverseDomainNameAppName = getReverseDomainNameAppName(str);
guessStr = str.split('.').slice(-1)[0].toLowerCase(); if (iconExists(reverseDomainNameAppName))
if (iconExists(guessStr)) return reverseDomainNameAppName;
return guessStr;
guessStr = str.toLowerCase().replace(/\s+/g, "-");
if (iconExists(guessStr))
return guessStr;
const searchResults = root.fuzzyQuery(str);
if (searchResults.length > 0) {
const firstEntry = searchResults[0];
guessStr = firstEntry.icon;
if (iconExists(guessStr))
return guessStr;
}
return str; const lowercasedDomainNameAppName = reverseDomainNameAppName.toLowerCase();
if (iconExists(lowercasedDomainNameAppName))
return lowercasedDomainNameAppName;
const kebabNormalizedGuess = getKebabNormalizedAppName(str);
if (iconExists(kebabNormalizedGuess))
return kebabNormalizedGuess;
const undescoreToKebabGuess = getUndescoreToKebabAppName(str);
if (iconExists(undescoreToKebabGuess))
return undescoreToKebabGuess;
const iconSearchResult = fuzzyQuery(str, preppedIcons);
if (iconSearchResult && iconExists(iconSearchResult.icon))
return iconSearchResult.icon;
const nameSearchResult = root.fuzzyQuery(str, preppedNames);
if (nameSearchResult && iconExists(nameSearchResult.icon))
return nameSearchResult.icon;
return "application-x-executable";
} }
function iconExists(iconName) { function iconExists(iconName) {
-2
View File
@@ -29,6 +29,4 @@ Singleton {
readonly property bool isLaptop: UPower.displayDevice.isLaptopBattery readonly property bool isLaptop: UPower.displayDevice.isLaptopBattery
readonly property bool onBattery: UPower.onBattery readonly property bool onBattery: UPower.onBattery
readonly property bool ready: UPower.displayDevice.ready readonly property bool ready: UPower.displayDevice.ready
readonly property real timeToEmpty: UPower.displayDevice.timeToEmpty
readonly property real timeToFull: UPower.displayDevice.timeToFull
} }
+1 -1
View File
@@ -232,7 +232,7 @@ Singleton {
} }
function setBrightness(value: real): void { function setBrightness(value: real): void {
value = Math.max(Config.services.minBrightness, Math.min(1, value)); value = Math.max(0, Math.min(1, value));
const rounded = Math.round(value * 100); const rounded = Math.round(value * 100);
if (Math.round(brightness * 100) === rounded) if (Math.round(brightness * 100) === rounded)
return; return;
-249
View File
@@ -1,249 +0,0 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Config
import "../scripts/fuzzysort.js" as Fuzzy
Singleton {
id: root
property string cliphistBinary: "cliphist"
property string currentEntry: ""
property list<string> entries: []
property real pasteDelay: 0.05
readonly property var preparedEntries: entries.map(a => ({
name: Fuzzy.prepare(displayText(a)),
entry: a
}))
property string previewImageFile: "/tmp/qs-cliphist-preview.img"
property string previewImageSource: ""
property bool previewIsImage: false
property list<var> previewText: []
property int previewToken: 0
property real scoreThreshold: 0.2
function copy(entry): void {
Quickshell.execDetached(["bash", "-c", `printf '${shellSingleQuoteEscape(entry)}' | ${root.cliphistBinary} decode | wl-copy`]);
}
function deleteEntry(entry): void {
deleteProc.deleteEntry(entry);
}
function displayText(entry): string {
return entry.replace(/^\s*\d+\s+/, "");
}
function entryIsImage(entry): bool {
return !!(/^\d+\t\[\[.*binary data.*\d+x\d+.*\]\]$/.test(entry));
}
function escapeHtml(str): string {
return String(str).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
}
function fuzzyQuery(search: string): var {
if (search.trim() === "") {
return entries;
}
return Fuzzy.go(search, preparedEntries, {
all: true,
key: "name"
}).map(r => {
return r.obj.entry;
});
}
function paste(entry): void {
Quickshell.execDetached(["bash", "-c", `printf '${shellSingleQuoteEscape(entry)}' | ${root.cliphistBinary} decode | wl-copy && wl-paste`]);
}
function processPreviewLines(text: string): var {
if (text === "")
return [];
const lines = text.split("\n");
let minIndent = Infinity;
for (let i = 0; i < lines.length; i++) {
const m = lines[i].match(/^(\s*)/);
const indent = m ? m[1].length : 0;
if (lines[i].trim().length > 0 && indent < minIndent)
minIndent = indent;
}
if (minIndent === Infinity)
minIndent = 0;
const result = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const indentMatch = line.match(/^(\s*)/);
const indentLen = indentMatch ? indentMatch[1].length : 0;
const stripped = line.slice(Math.min(minIndent, indentLen));
result.push({
line: i + 1,
text: stripped
});
}
return result;
}
function refresh(): void {
readProc.buffer = [];
readProc.running = true;
}
function refreshPreview(): void {
previewToken += 1;
const token = previewToken;
if (!currentEntry) {
previewText = [];
previewImageSource = "";
previewIsImage = false;
return;
}
previewImageSource = "";
previewIsImage = entryIsImage(currentEntry);
if (previewIsImage) {
previewImageProc.token = token;
previewImageProc.running = true;
} else {
previewTextProc.token = token;
previewTextProc.running = true;
}
}
function shellSingleQuoteEscape(str): string {
return String(str).replace(/'/g, "'\\''");
}
function wipe(): void {
wipeProc.running = true;
}
Process {
id: deleteProc
property string entry: ""
function deleteEntry(entry) {
deleteProc.entry = entry;
deleteProc.running = true;
deleteProc.entry = "";
}
command: ["bash", "-c", `echo '${root.shellSingleQuoteEscape(deleteProc.entry)}' | ${root.cliphistBinary} delete`]
onExited: (exitCode, exitStatus) => {
root.refresh();
}
}
Process {
id: wipeProc
command: [root.cliphistBinary, "wipe"]
onExited: (exitCode, exitStatus) => {
root.refresh();
}
}
Connections {
function onClipboardTextChanged() {
delayedUpdateTimer.restart();
}
target: Quickshell
}
Timer {
id: delayedUpdateTimer
interval: 50
repeat: false
onTriggered: {
root.refresh();
}
}
Process {
id: previewTextProc
property int token: 0
command: ["bash", "-c", `
printf '%s' '${root.shellSingleQuoteEscape(root.currentEntry)}' | ${root.cliphistBinary} decode
`]
running: false
stdout: StdioCollector {
onStreamFinished: {
if (previewTextProc.token !== root.previewToken)
return;
root.previewText = root.processPreviewLines(this.text);
}
}
}
Process {
id: previewImageProc
property int token: 0
command: ["bash", "-c", `
set -euo pipefail
tmp='${root.shellSingleQuoteEscape(root.previewImageFile)}'
printf '%s' '${root.shellSingleQuoteEscape(root.currentEntry)}' | ${root.cliphistBinary} decode > "$tmp"
`]
running: false
onExited: (exitCode, exitStatus) => {
if (token !== root.previewToken)
return;
if (exitCode !== 0) {
console.error("[Cliphist] image preview failed", exitCode, exitStatus);
return;
}
root.previewImageSource = "";
Qt.callLater(() => {
root.previewImageSource = `file://${root.previewImageFile}`;
});
}
}
Process {
id: readProc
property list<string> buffer: []
command: [root.cliphistBinary, "list"]
stdout: SplitParser {
onRead: line => {
readProc.buffer.push(line);
}
}
onExited: (exitCode, exitStatus) => {
if (exitCode === 0) {
root.entries = readProc.buffer;
} else {
console.error("[Cliphist] Failed to refresh with code", exitCode, "and status", exitStatus);
}
}
}
IpcHandler {
function update(): void {
root.refresh();
}
target: "cliphistService"
}
}
+4 -28
View File
@@ -42,24 +42,11 @@ Singleton {
function checkStartup() { function checkStartup() {
if (!root.enabled) if (!root.enabled)
return; return;
var now = new Date(); var now = new Date();
var nowMinutes = now.getHours() * 60 + now.getMinutes(); if (now.getHours() >= darkStart || now.getHours() < darkEnd) {
applyDarkMode();
var isDarkTime;
if (root.darkStart <= root.darkEnd) {
isDarkTime = (nowMinutes >= root.darkStart && nowMinutes < root.darkEnd);
} else { } else {
isDarkTime = (nowMinutes >= root.darkStart || nowMinutes < root.darkEnd); applyLightMode();
}
if (isDarkTime) {
if (DynamicColors.light)
root.applyDarkMode();
} else {
if (!DynamicColors.light)
root.applyLightMode();
} }
} }
@@ -73,19 +60,8 @@ Singleton {
onTriggered: { onTriggered: {
if (!root.enabled) if (!root.enabled)
return; return;
var now = new Date(); var now = new Date();
var nowMinutes = now.getHours() * 60 + now.getMinutes(); if (now.getHours() >= root.darkStart || now.getHours() < root.darkEnd) {
var isDarkTime;
if (root.darkStart <= root.darkEnd) {
isDarkTime = (nowMinutes >= root.darkStart && nowMinutes < root.darkEnd);
} else {
isDarkTime = (nowMinutes >= root.darkStart || nowMinutes < root.darkEnd);
}
if (isDarkTime) {
if (DynamicColors.light) if (DynamicColors.light)
root.applyDarkMode(); root.applyDarkMode();
} else { } else {
+45
View File
@@ -0,0 +1,45 @@
pragma Singleton
import Quickshell
import Quickshell.Networking
Singleton {
id: root
readonly property list<NetworkDevice> netDevice: Networking.devices.values
readonly property string networkName: getNetworkName()
readonly property list<string> nicNames: networkInterfaceCardNames()
// Useless will prob remove
function getConnectedDevices() {
let connectedDevices = [];
for (var i = 0; i < netDevice.length; i++) {
if (netDevice[i].connected === true) {
connectedDevices.push(netDevice[i].name);
}
}
return connectedDevices;
}
// SHOULD retrieve network names of connected devices
// Currently only gives connected nic name
function getNetworkName() {
const devices = netDevice.filter(device => device.connected === true);
for (var i = 0; i < devices.length; i++) {
return devices[i].name;
}
return "Failed network name";
}
// Searches wired/wireless devices and sets them in a list
function networkInterfaceCardNames() {
let nicList = [];
for (let i = 0; i < netDevice.length; ++i) {
nicList.push(netDevice[i].name);
}
return nicList;
}
}
+1 -21
View File
@@ -39,21 +39,6 @@ Singleton {
readonly property real uploadSpeed: _uploadSpeed readonly property real uploadSpeed: _uploadSpeed
readonly property real uploadTotal: _uploadTotal readonly property real uploadTotal: _uploadTotal
function resetSampling(): void {
netDevFile.reload();
const content = netDevFile.text();
if (!content)
return;
const data = root.parseNetDev(content);
const now = Date.now();
root._prevRxBytes = data.rx;
root._prevTxBytes = data.tx;
root._prevTimestamp = now;
}
function formatBytes(bytes: real): var { function formatBytes(bytes: real): var {
// Handle negative or invalid values // Handle negative or invalid values
if (bytes < 0 || isNaN(bytes) || !isFinite(bytes)) { if (bytes < 0 || isNaN(bytes) || !isFinite(bytes)) {
@@ -171,15 +156,10 @@ function resetSampling(): void {
Timer { Timer {
interval: Config.dashboard.resourceUpdateInterval interval: Config.dashboard.resourceUpdateInterval
repeat: true repeat: true
running: true running: root.refCount > 0
triggeredOnStart: true triggeredOnStart: true
onTriggered: { onTriggered: {
if (root.refCount <= 0) {
root.resetSampling();
return;
}
netDevFile.reload(); netDevFile.reload();
const content = netDevFile.text(); const content = netDevFile.text();
if (!content) if (!content)
+464
View File
@@ -0,0 +1,464 @@
pragma Singleton
import Quickshell
import Quickshell.Io
import QtQuick
import qs.Config
Singleton {
id: root
property string autoGpuType: "NONE"
property string cpuName: ""
property real cpuPerc
property real cpuTemp
// Individual disks: Array of { mount, used, total, free, perc }
property var disks: []
property real gpuMemTotal: 0
property real gpuMemUsed
property string gpuName
property real gpuPerc
property real gpuTemp
readonly property string gpuType: Config.services.gpuType.toUpperCase() || autoGpuType
property real lastCpuIdle
property real lastCpuTotal
readonly property real memPerc: memTotal > 0 ? memUsed / memTotal : 0
property real memTotal
property real memUsed
property int refCount
readonly property real storagePerc: {
let totalUsed = 0;
let totalSize = 0;
for (const disk of disks) {
totalUsed += disk.used;
totalSize += disk.total;
}
return totalSize > 0 ? totalUsed / totalSize : 0;
}
function cleanCpuName(name: string): string {
return name.replace(/\(R\)/gi, "").replace(/\(TM\)/gi, "").replace(/CPU/gi, "").replace(/\d+th Gen /gi, "").replace(/\d+nd Gen /gi, "").replace(/\d+rd Gen /gi, "").replace(/\d+st Gen /gi, "").replace(/Core /gi, "").replace(/Processor/gi, "").replace(/\s+/g, " ").trim();
}
function cleanGpuName(name: string): string {
return name.replace(/NVIDIA GeForce /gi, "").replace(/NVIDIA /gi, "").replace(/AMD Radeon /gi, "").replace(/AMD /gi, "").replace(/Intel /gi, "").replace(/\(R\)/gi, "").replace(/\(TM\)/gi, "").replace(/Graphics/gi, "").replace(/\s+/g, " ").trim();
}
function formatKib(kib: real): var {
const mib = 1024;
const gib = 1024 ** 2;
const tib = 1024 ** 3;
if (kib >= tib)
return {
value: kib / tib,
unit: "TiB"
};
if (kib >= gib)
return {
value: kib / gib,
unit: "GiB"
};
if (kib >= mib)
return {
value: kib / mib,
unit: "MiB"
};
return {
value: kib,
unit: "KiB"
};
}
Timer {
interval: Config.dashboard.resourceUpdateInterval
repeat: true
running: root.refCount > 0
triggeredOnStart: true
onTriggered: {
stat.reload();
meminfo.reload();
if (root.gpuType === "GENERIC")
gpuUsage.running = true;
if (root.gpuType === "GENERIC" && root.gpuMemTotal === 0)
oneshotMemAmd.running = true;
}
}
Timer {
interval: 60000 * 120
repeat: true
running: true
triggeredOnStart: true
onTriggered: {
storage.running = true;
}
}
Timer {
interval: Config.dashboard.resourceUpdateInterval * 5
repeat: true
running: root.refCount > 0
triggeredOnStart: true
onTriggered: {
sensors.running = true;
}
}
FileView {
id: cpuinfoInit
path: "/proc/cpuinfo"
onLoaded: {
const nameMatch = text().match(/model name\s*:\s*(.+)/);
if (nameMatch)
root.cpuName = root.cleanCpuName(nameMatch[1]);
}
}
FileView {
id: stat
path: "/proc/stat"
onLoaded: {
const data = text().match(/^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/);
if (data) {
const stats = data.slice(1).map(n => parseInt(n, 10));
const total = stats.reduce((a, b) => a + b, 0);
const idle = stats[3] + (stats[4] ?? 0);
const totalDiff = total - root.lastCpuTotal;
const idleDiff = idle - root.lastCpuIdle;
const newCpuPerc = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0;
root.lastCpuTotal = total;
root.lastCpuIdle = idle;
if (Math.abs(newCpuPerc - root.cpuPerc) >= 0.01)
root.cpuPerc = newCpuPerc;
}
}
}
FileView {
id: meminfo
path: "/proc/meminfo"
onLoaded: {
const data = text();
const total = parseInt(data.match(/MemTotal: *(\d+)/)[1], 10) || 1;
const used = (root.memTotal - parseInt(data.match(/MemAvailable: *(\d+)/)[1], 10)) || 0;
if (root.memTotal !== total)
root.memTotal = total;
if (Math.abs(used - root.memUsed) >= 16384)
root.memUsed = used;
}
}
Process {
id: storage
command: ["lsblk", "-b", "-o", "NAME,SIZE,TYPE,FSUSED,FSSIZE", "-P"]
stdout: StdioCollector {
onStreamFinished: {
const diskMap = {}; // Map disk name -> { name, totalSize, used, fsTotal }
const lines = text.trim().split("\n");
for (const line of lines) {
if (line.trim() === "")
continue;
const nameMatch = line.match(/NAME="([^"]+)"/);
const sizeMatch = line.match(/SIZE="([^"]+)"/);
const typeMatch = line.match(/TYPE="([^"]+)"/);
const fsusedMatch = line.match(/FSUSED="([^"]*)"/);
const fssizeMatch = line.match(/FSSIZE="([^"]*)"/);
if (!nameMatch || !typeMatch)
continue;
const name = nameMatch[1];
const type = typeMatch[1];
const size = parseInt(sizeMatch?.[1] || "0", 10);
const fsused = parseInt(fsusedMatch?.[1] || "0", 10);
const fssize = parseInt(fssizeMatch?.[1] || "0", 10);
if (type === "disk") {
// Skip zram (swap) devices
if (name.startsWith("zram"))
continue;
// Initialize disk entry
if (!diskMap[name]) {
diskMap[name] = {
name: name,
totalSize: size,
used: 0,
fsTotal: 0
};
}
} else if (type === "part") {
// Find parent disk (remove trailing numbers/p+numbers)
let parentDisk = name.replace(/p?\d+$/, "");
// For nvme devices like nvme0n1p1, parent is nvme0n1
if (name.match(/nvme\d+n\d+p\d+/))
parentDisk = name.replace(/p\d+$/, "");
// Aggregate partition usage to parent disk
if (diskMap[parentDisk]) {
diskMap[parentDisk].used += fsused;
diskMap[parentDisk].fsTotal += fssize;
}
}
}
const diskList = [];
let totalUsed = 0;
let totalSize = 0;
for (const diskName of Object.keys(diskMap).sort()) {
const disk = diskMap[diskName];
// Use filesystem total if available, otherwise use disk size
const total = disk.fsTotal > 0 ? disk.fsTotal : disk.totalSize;
const used = disk.used;
const perc = total > 0 ? used / total : 0;
// Convert bytes to KiB for consistency with formatKib
diskList.push({
mount: disk.name // Using 'mount' property for compatibility
,
used: used / 1024,
total: total / 1024,
free: (total - used) / 1024,
perc: perc
});
totalUsed += used;
totalSize += total;
}
root.disks = diskList;
}
}
}
Process {
id: gpuNameDetect
command: ["sh", "-c", "nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null || lspci 2>/dev/null | grep -i 'vga\\|3d\\|display' | head -1"]
running: true
stdout: StdioCollector {
onStreamFinished: {
const output = text.trim();
if (!output)
return;
// Check if it's from nvidia-smi (clean GPU name)
if (output.toLowerCase().includes("nvidia") || output.toLowerCase().includes("geforce") || output.toLowerCase().includes("rtx") || output.toLowerCase().includes("gtx")) {
root.gpuName = root.cleanGpuName(output);
} else {
// Parse lspci output: extract name from brackets or after colon
const bracketMatch = output.match(/\[([^\]]+)\]/);
if (bracketMatch) {
root.gpuName = root.cleanGpuName(bracketMatch[1]);
} else {
const colonMatch = output.match(/:\s*(.+)/);
if (colonMatch)
root.gpuName = root.cleanGpuName(colonMatch[1]);
}
}
}
}
}
Process {
id: gpuTypeCheck
command: ["sh", "-c", "if command -v nvidia-smi &>/dev/null && nvidia-smi -L &>/dev/null; then echo NVIDIA; elif ls /sys/class/drm/card*/device/gpu_busy_percent 2>/dev/null | grep -q .; then echo GENERIC; else echo NONE; fi"]
running: !Config.services.gpuType
stdout: StdioCollector {
onStreamFinished: root.autoGpuType = text.trim()
}
}
Process {
id: oneshotMem
command: ["nvidia-smi", "--query-gpu=memory.total", "--format=csv,noheader,nounits"]
running: root.gpuType === "NVIDIA" && root.gpuMemTotal === 0
stdout: StdioCollector {
onStreamFinished: {
root.gpuMemTotal = Number(this.text.trim());
oneshotMem.running = false;
}
}
}
Process {
id: oneshotMemAmd
command: ["sh", "-c", "cat /sys/class/drm/card*/device/mem_info_vram_total"]
running: root.gpuType === "GENERIC" && root.gpuMemTotal === 0
stdout: StdioCollector {
onStreamFinished: {
const values = text.trim().split("\n").map(v => parseInt(v, 10)).filter(v => Number.isFinite(v));
if (values.length > 0) {
const totalBytes = values.reduce((a, b) => a + b, 0);
root.gpuMemTotal = totalBytes / (1024 * 1024);
}
oneshotMemAmd.running = false;
}
}
}
Process {
id: gpuUsageNvidia
command: ["/usr/bin/nvidia-smi", "--query-gpu=utilization.gpu,temperature.gpu,memory.used", "--format=csv,noheader,nounits", "-lms", "1000"]
running: root.refCount > 0 && root.gpuType === "NVIDIA"
stdout: SplitParser {
onRead: data => {
const parts = String(data).trim().split(/\s*,\s*/);
if (parts.length < 3)
return;
const usageRaw = parseInt(parts[0], 10);
const tempRaw = parseInt(parts[1], 10);
const memRaw = parseInt(parts[2], 10);
if (!Number.isFinite(usageRaw) || !Number.isFinite(tempRaw) || !Number.isFinite(memRaw))
return;
const newGpuPerc = Math.max(0, Math.min(1, usageRaw / 100));
const newGpuTemp = tempRaw;
const newGpuMemUsed = root.gpuMemTotal > 0 ? Math.max(0, Math.min(1, memRaw / root.gpuMemTotal)) : 0;
// Only publish meaningful changes to avoid needless binding churn / repaints
if (Math.abs(root.gpuPerc - newGpuPerc) >= 0.01)
root.gpuPerc = newGpuPerc;
if (Math.abs(root.gpuTemp - newGpuTemp) >= 1)
root.gpuTemp = newGpuTemp;
if (Math.abs(root.gpuMemUsed - newGpuMemUsed) >= 0.01)
root.gpuMemUsed = newGpuMemUsed;
}
}
}
Process {
id: gpuUsage
command: root.gpuType === "GENERIC" ? ["sh", "-c", "paste -d ' ' /sys/class/drm/card*/device/gpu_busy_percent /sys/class/drm/card*/device/mem_info_vram_used"] : ["echo"]
stdout: StdioCollector {
onStreamFinished: {
if (root.gpuType === "GENERIC") {
const lines = text.trim().split("\n");
let percSum = 0;
let memSum = 0;
let count = 0;
for (const line of lines) {
const parts = line.trim().split(/\s+/);
if (parts.length < 2)
continue;
const gpuBusy = parseInt(parts[0], 10);
const memUsed = parseInt(parts[1], 10);
if (!Number.isFinite(gpuBusy) || !Number.isFinite(memUsed))
continue;
percSum += gpuBusy;
memSum += memUsed;
count++;
}
if (count > 0) {
// GPU usage %
root.gpuPerc = (percSum / count) / 100;
// VRAM usage (bytes → MiB → normalized)
const memUsedMiB = memSum / (1024 * 1024);
const newGpuMemUsed = root.gpuMemTotal > 0 ? Math.max(0, Math.min(1, memUsedMiB / root.gpuMemTotal)) : 0;
if (Math.abs(root.gpuMemUsed - newGpuMemUsed) >= 0.01)
root.gpuMemUsed = newGpuMemUsed;
}
} else {
root.gpuPerc = 0;
root.gpuTemp = 0;
}
}
}
}
Process {
id: sensors
command: ["sensors"]
environment: ({
LANG: "C.UTF-8",
LC_ALL: "C.UTF-8"
})
stdout: StdioCollector {
onStreamFinished: {
let cpuTemp = text.match(/(?:Package id [0-9]+|Tdie):\s+((\+|-)[0-9.]+)(°| )C/);
if (!cpuTemp)
// If AMD Tdie pattern failed, try fallback on Tctl
cpuTemp = text.match(/Tctl:\s+((\+|-)[0-9.]+)(°| )C/);
if (cpuTemp && Math.abs(parseFloat(cpuTemp[1]) - root.cpuTemp) >= 0.5)
root.cpuTemp = parseFloat(cpuTemp[1]);
if (root.gpuType !== "GENERIC")
return;
let eligible = false;
let sum = 0;
let count = 0;
for (const line of text.trim().split("\n")) {
if (line === "Adapter: PCI adapter")
eligible = true;
else if (line === "")
eligible = false;
else if (eligible) {
let match = line.match(/^(temp[0-9]+|GPU core|edge)+:\s+\+([0-9]+\.[0-9]+)(°| )C/);
if (!match)
// Fall back to junction/mem if GPU doesn't have edge temp (for AMD GPUs)
match = line.match(/^(junction|mem)+:\s+\+([0-9]+\.[0-9]+)(°| )C/);
if (match) {
sum += parseFloat(match[2]);
count++;
}
}
}
root.gpuTemp = count > 0 ? sum / count : 0;
}
}
}
}
+12 -15
View File
@@ -32,26 +32,33 @@ Searcher {
showPreview = true; showPreview = true;
} }
function setCrop(screen: string, rect: rect, zoom: real): void { function setCrop(screen: string, rect: rect, scaledRect: rect, zoom: real): void {
let updated = Object.assign({}, root.crops);
if (zoom <= 0) if (zoom <= 0)
zoom = 1.0; zoom = 1.0;
else if (zoom > 5.0) else if (zoom > 5.0)
zoom = 5.0; zoom = 5.0;
root.crops[screen] = { updated[screen] = {
x: rect.x, x: rect.x,
y: rect.y, y: rect.y,
width: rect.width, width: rect.width,
height: rect.height, height: rect.height,
scaledX: scaledRect.x,
scaledY: scaledRect.y,
scaledWidth: scaledRect.width,
scaledHeight: scaledRect.height,
zoom: zoom zoom: zoom
}; };
// root.crops = updated;
root.crops = updated;
} }
function setWallpaper(path: string): void { function setWallpaper(path: string): void {
actualCurrent = path; actualCurrent = path;
WallpaperPath.currentWallpaperPath = path; WallpaperPath.currentWallpaperPath = path;
Quickshell.screens.forEach(n => setCrop(n.name, Qt.rect(0, 0, 1, 1), 1.0)); Quickshell.screens.forEach(n => setCrop(n.name, Qt.rect(0, 0, 1, 1), Qt.rect(0, 0, 0, 0), 1.0));
Quickshell.execDetached(["zshell-cli", "wallpaper", "lockscreen", "--input-image", `${root.actualCurrent}`, "--output-path", `${Paths.state}/lockscreen_bg.png`, "--blur-amount", `${Config.lock.blurAmount}`]); Quickshell.execDetached(["zshell-cli", "wallpaper", "lockscreen", "--input-image", `${root.actualCurrent}`, "--output-path", `${Paths.state}/lockscreen_bg.png`, "--blur-amount", `${Config.lock.blurAmount}`]);
if (Config.general.color.schemeGeneration) if (Config.general.color.schemeGeneration)
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${root.actualCurrent}`, "--scheme", `${Config.colors.schemeType}`, "--mode", `${Config.general.color.mode}`]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${root.actualCurrent}`, "--scheme", `${Config.colors.schemeType}`, "--mode", `${Config.general.color.mode}`]);
@@ -84,7 +91,7 @@ Searcher {
path: `${Paths.state}/wallpaper-crops.json` path: `${Paths.state}/wallpaper-crops.json`
watchChanges: true watchChanges: true
onAdapterUpdated: cropWriteDelay.restart() onAdapterUpdated: writeAdapter()
onFileChanged: reload() onFileChanged: reload()
JsonAdapter { JsonAdapter {
@@ -94,16 +101,6 @@ Searcher {
} }
} }
Timer {
id: cropWriteDelay
interval: 100
repeat: false
running: false
onTriggered: monitorCrops.writeAdapter()
}
FileSystemModel { FileSystemModel {
id: wallpapers id: wallpapers
-20
View File
@@ -9,7 +9,6 @@ import qs.Config
import qs.Helpers import qs.Helpers
import qs.Modules.SysTray import qs.Modules.SysTray
import qs.Modules.SysTray.Widgets import qs.Modules.SysTray.Widgets
import qs.Modules.Network
import qs.Modules.Updates import qs.Modules.Updates
RowLayout { RowLayout {
@@ -18,7 +17,6 @@ RowLayout {
required property Wrapper popouts required property Wrapper popouts
required property ClipWrapper popoutsWrapper required property ClipWrapper popoutsWrapper
required property ShellScreen screen required property ShellScreen screen
required property bool fullscreen
readonly property int vPadding: 6 readonly property int vPadding: 6
required property PersistentProperties visibilities required property PersistentProperties visibilities
@@ -92,7 +90,6 @@ RowLayout {
delegate: WrappedLoader { delegate: WrappedLoader {
sourceComponent: HyprsunsetWidget { sourceComponent: HyprsunsetWidget {
visible: !root.fullscreen
} }
} }
} }
@@ -111,7 +108,6 @@ RowLayout {
delegate: WrappedLoader { delegate: WrappedLoader {
sourceComponent: Workspaces { sourceComponent: Workspaces {
screen: root.screen screen: root.screen
visible: !root.fullscreen
} }
} }
} }
@@ -123,7 +119,6 @@ RowLayout {
sourceComponent: TrayWidget { sourceComponent: TrayWidget {
loader: root loader: root
popouts: root.popouts popouts: root.popouts
visible: !root.fullscreen
} }
} }
} }
@@ -134,7 +129,6 @@ RowLayout {
delegate: WrappedLoader { delegate: WrappedLoader {
sourceComponent: Resources { sourceComponent: Resources {
visibilities: root.visibilities visibilities: root.visibilities
visible: !root.fullscreen
} }
} }
} }
@@ -144,7 +138,6 @@ RowLayout {
delegate: WrappedLoader { delegate: WrappedLoader {
sourceComponent: UpdatesWidget { sourceComponent: UpdatesWidget {
visible: !root.fullscreen
} }
} }
} }
@@ -156,7 +149,6 @@ RowLayout {
sourceComponent: NotifBell { sourceComponent: NotifBell {
popouts: root.popouts popouts: root.popouts
visibilities: root.visibilities visibilities: root.visibilities
visible: !root.fullscreen
} }
} }
} }
@@ -169,7 +161,6 @@ RowLayout {
loader: root loader: root
popouts: root.popouts popouts: root.popouts
visibilities: root.visibilities visibilities: root.visibilities
visible: !root.fullscreen
} }
} }
} }
@@ -180,16 +171,6 @@ RowLayout {
delegate: WrappedLoader { delegate: WrappedLoader {
sourceComponent: WindowTitle { sourceComponent: WindowTitle {
bar: root bar: root
visible: !root.fullscreen
}
}
}
DelegateChoice {
roleValue: "network"
delegate: WrappedLoader {
sourceComponent: NetworkWidget {
} }
} }
} }
@@ -199,7 +180,6 @@ RowLayout {
delegate: WrappedLoader { delegate: WrappedLoader {
sourceComponent: MediaWidget { sourceComponent: MediaWidget {
visible: !root.fullscreen
} }
} }
} }
+2 -5
View File
@@ -10,20 +10,18 @@ import qs.Config
import qs.Helpers import qs.Helpers
import qs.Modules.SysTray import qs.Modules.SysTray
import qs.Modules.SysTray.Widgets import qs.Modules.SysTray.Widgets
import qs.Modules.Network
Item { Item {
id: root id: root
readonly property int contentHeight: Config.barConfig.height + padding * 2 readonly property int contentHeight: Config.barConfig.height + padding * 2
readonly property int exclusiveZone: Config.barConfig.autoHide ? Config.barConfig.border : contentHeight readonly property int exclusiveZone: Config.barConfig.autoHide ? Config.barConfig.border : contentHeight
required property bool fullscreen
property bool isHovered property bool isHovered
readonly property int padding: Math.max(Appearance.padding.smaller, Config.barConfig.border) readonly property int padding: Math.max(Appearance.padding.smaller, Config.barConfig.border)
required property Wrapper popouts required property Wrapper popouts
required property ClipWrapper popoutsWrapper required property ClipWrapper popoutsWrapper
required property ShellScreen screen required property ShellScreen screen
readonly property bool shouldBeVisible: !fullscreen && (!Config.barConfig.autoHide || visibilities.bar || isHovered) readonly property bool shouldBeVisible: (!Config.barConfig.autoHide || visibilities.bar || isHovered)
readonly property int vPadding: 6 readonly property int vPadding: 6
required property PersistentProperties visibilities required property PersistentProperties visibilities
@@ -31,7 +29,7 @@ Item {
content.item?.checkPopout(x); content.item?.checkPopout(x);
} }
implicitHeight: fullscreen ? 0 : Config.barConfig.border implicitHeight: Config.barConfig.border
visible: height > Config.barConfig.border visible: height > Config.barConfig.border
states: State { states: State {
@@ -76,7 +74,6 @@ Item {
anchors.right: parent.right anchors.right: parent.right
sourceComponent: Bar { sourceComponent: Bar {
fullscreen: root.fullscreen
height: root.contentHeight height: root.contentHeight
popouts: root.popouts popouts: root.popouts
popoutsWrapper: root.popoutsWrapper popoutsWrapper: root.popoutsWrapper
+7 -2
View File
@@ -8,7 +8,6 @@ import qs.Config
Item { Item {
id: root id: root
required property real borderThickness
readonly property alias content: content readonly property alias content: content
property real offsetScale: y > 0 || content.hasCurrent ? 0 : 1 property real offsetScale: y > 0 || content.hasCurrent ? 0 : 1
required property ShellScreen screen required property ShellScreen screen
@@ -18,15 +17,21 @@ Item {
implicitWidth: content.implicitWidth implicitWidth: content.implicitWidth
visible: width > 0 && height > 0 visible: width > 0 && height > 0
x: { x: {
const off = content.currentCenter - borderThickness - content.nonAnimWidth / 2; if (content.isDetached)
return (parent.width - content.nonAnimWidth) / 2;
const off = content.currentCenter - Config.barConfig.border - content.nonAnimWidth / 2;
const diff = parent.width - Math.floor(off + content.nonAnimWidth); const diff = parent.width - Math.floor(off + content.nonAnimWidth);
if (diff < 0) if (diff < 0)
return off + diff; return off + diff;
return Math.floor(Math.max(off, 0)); return Math.floor(Math.max(off, 0));
} }
y: content.isDetached ? (parent.height - content.nonAnimHeight) / 2 : 0
Behavior on offsetScale { Behavior on offsetScale {
Anim { Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
} }
} }
Behavior on x { Behavior on x {
-357
View File
@@ -1,357 +0,0 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Components
import qs.Helpers
import qs.Config
Item {
id: root
readonly property int itemHeight: Config.clipboard.sizes.itemHeight
required property ShellScreen screen
required property PersistentProperties visibilities
implicitHeight: search.implicitHeight + entries.anchors.topMargin + ((view.spacing + itemHeight) * Config.clipboard.maxEntriesShown) - view.spacing
implicitWidth: Config.clipboard.sizes.width + preview.width + preview.anchors.leftMargin
Component.onCompleted: {
if (ClipHistory.entries.length === 0)
ClipHistory.refresh();
searchField.forceActiveFocus();
}
CustomClippingRect {
id: search
anchors.left: parent.left
anchors.right: entries.right
anchors.top: parent.top
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: 50
radius: Appearance.rounding.full
MaterialIcon {
id: searchIcon
anchors.left: parent.left
anchors.margins: Appearance.padding.large
anchors.verticalCenter: parent.verticalCenter
text: "search"
}
CustomTextField {
id: searchField
anchors.bottom: parent.bottom
anchors.left: searchIcon.right
anchors.leftMargin: Appearance.spacing.small
anchors.right: parent.right
anchors.top: parent.top
color: DynamicColors.palette.m3onSurface
placeholderText: "Search clipboard history..."
Keys.onDownPressed: view.incrementCurrentIndex()
Keys.onUpPressed: view.decrementCurrentIndex()
onAccepted: {
ClipHistory.copy(view.currentItem.modelData);
root.visibilities.clipboard = false;
}
}
}
CustomClippingRect {
id: preview
anchors.bottom: parent.bottom
anchors.left: entries.right
anchors.leftMargin: Appearance.spacing.normal
anchors.top: parent.top
color: DynamicColors.tPalette.m3surfaceContainer
implicitWidth: ClipHistory.previewIsImage ? Math.max(Math.min(imagePreview.sourceSize.width + Appearance.padding.large * 2, Config.clipboard.sizes.previewWidth), Config.clipboard.sizes.minPreviewWidth) : Math.max(Math.min(textPreviewColumn.width + textPreviewColumn.anchors.margins * 2, Config.clipboard.sizes.previewWidth), Config.clipboard.sizes.minPreviewWidth)
radius: 25
Behavior on implicitWidth {
Anim {
}
}
Column {
id: textPreviewColumn
anchors.left: parent.left
anchors.leftMargin: 0
anchors.margins: Appearance.padding.normal
anchors.top: parent.top
visible: !ClipHistory.previewIsImage
Repeater {
id: processedLines
model: ClipHistory.previewText
Row {
id: lineRow
required property int index
required property var modelData
spacing: Appearance.spacing.normal
CustomRect {
color: lineRow.index % 2 ? DynamicColors.tPalette.m3surfaceContainer : "transparent"
implicitHeight: lineText.paintedHeight + Appearance.padding.extraSmall * 2
implicitWidth: 50
CustomText {
id: number
anchors.margins: Appearance.padding.large
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.small
text: lineRow.modelData.line
}
}
Repeater {
model: lineRow.modelData.text.split("\t").slice(1)
RowLayout {
height: lineText.height
width: lineText.height + Appearance.spacing.extraSmall
Item {
Layout.fillHeight: true
Layout.fillWidth: true
CustomRect {
anchors.centerIn: parent
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: parent.height / 8
implicitWidth: parent.height / 8
radius: Appearance.rounding.full
}
}
Item {
Layout.fillHeight: true
Layout.fillWidth: true
CustomRect {
anchors.centerIn: parent
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: parent.height / 8
implicitWidth: parent.height / 8
radius: Appearance.rounding.full
}
}
Item {
Layout.fillHeight: true
Layout.fillWidth: true
CustomRect {
anchors.centerIn: parent
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: parent.height / 8
implicitWidth: parent.height / 8
radius: Appearance.rounding.full
}
}
}
}
CustomText {
id: lineText
color: DynamicColors.palette.m3onSurface
font.family: ClipHistory.previewIsCode ? Appearance.font.family.mono : Appearance.font.family.sans
height: lineText.paintedHeight + Appearance.padding.extraSmall * 2
text: lineRow.modelData.text.trim()
verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
}
}
}
Image {
id: imagePreview
anchors.centerIn: parent
asynchronous: true
cache: false
fillMode: Image.PreserveAspectFit
mipmap: true
retainWhileLoading: true
smooth: true
source: ClipHistory.previewImageSource
visible: ClipHistory.previewIsImage && ClipHistory.previewImageSource !== ""
width: Math.min(sourceSize.width, Config.clipboard.sizes.previewWidth)
}
}
Item {
id: entries
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.top: search.bottom
anchors.topMargin: Appearance.spacing.normal
implicitWidth: Config.clipboard.sizes.width
CustomListView {
id: view
anchors.fill: parent
cacheBuffer: (root.itemHeight + spacing) * 2
highlightFollowsCurrentItem: false
highlightRangeMode: ListView.ApplyRange
preferredHighlightBegin: 0
preferredHighlightEnd: height
spacing: Appearance.spacing.normal
CustomScrollBar.vertical: CustomScrollBar {
flickable: view
minimumSize: 0.1
}
delegate: CustomRect {
id: clipItem
readonly property bool isImage: ClipHistory.entryIsImage(modelData)
required property string modelData
implicitHeight: root.itemHeight
implicitWidth: view.width
radius: textLayer.pressed ? (Appearance.rounding.small / 2) : Appearance.rounding.small
Behavior on radius {
Anim {
type: Anim.FastEffects
}
}
RowLayout {
anchors.fill: parent
spacing: Appearance.spacing.small
CustomClippingRect {
id: textRect
Layout.fillHeight: true
Layout.fillWidth: true
Item {
id: textWrapper
anchors.fill: parent
layer.enabled: true
layer.effect: OpacityMask {
maskSource: fadeMask
}
MaterialIcon {
id: icon
anchors.left: parent.left
anchors.margins: Appearance.padding.normal
anchors.verticalCenter: parent.verticalCenter
font.pointSize: Appearance.font.size.large
text: clipItem.isImage ? "image" : "text_fields"
}
CustomText {
id: text
anchors.left: icon.right
anchors.margins: Appearance.spacing.normal
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
text: clipItem.isImage ? qsTr("Image") : ClipHistory.displayText(clipItem.modelData)
}
}
CustomRect {
id: fadeMask
anchors.fill: parent
layer.enabled: true
visible: false
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop {
color: Qt.rgba(1, 1, 1, 1.0)
position: 0.85
}
GradientStop {
color: Qt.rgba(1, 1, 1, 0)
position: 1.0
}
}
}
}
IconButton {
Layout.fillHeight: true
Layout.margins: Appearance.padding.normal
Layout.preferredWidth: height
icon: "delete"
inactiveColor: Qt.alpha(DynamicColors.palette.m3error, 0.8)
inactiveOnColor: DynamicColors.palette.m3onError
isToggle: false
onClicked: ClipHistory.deleteEntry(clipItem.modelData)
}
}
StateLayer {
id: textLayer
onClicked: ClipHistory.copy(clipItem.modelData)
}
}
highlight: CustomRect {
color: DynamicColors.palette.m3onSurface
implicitHeight: view.currentItem?.height ?? 0
implicitWidth: view.width
opacity: 0.08
radius: Appearance.rounding.small
y: view.currentItem?.y ?? 0
Behavior on y {
Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
}
}
model: ScriptModel {
values: ClipHistory.fuzzyQuery(searchField.text)
}
onCurrentItemChanged: {
if (!currentItem)
return;
ClipHistory.currentEntry = currentItem.modelData;
ClipHistory.refreshPreview();
}
onVisibleChanged: currentIndex = 0
CustomClippingWrapperRect {
anchors.fill: parent
child: view.contentItem
radius: Appearance.rounding.small
}
}
}
}
-41
View File
@@ -1,41 +0,0 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import qs.Components
import qs.Config
Item {
id: root
property int contentHeight
property real offsetScale: shouldBeActive ? 0 : 1
required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.clipboard && Config.clipboard.enabled
required property PersistentProperties visibilities
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
implicitHeight: content.implicitHeight + Appearance.padding.normal * 2
implicitWidth: content.implicitWidth + Appearance.padding.normal * 2 || 400
opacity: 1 - offsetScale
visible: offsetScale < 1
Behavior on offsetScale {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
Loader {
id: content
active: root.shouldBeActive || root.visible
anchors.centerIn: parent
sourceComponent: Content {
screen: root.screen
visibilities: root.visibilities
}
}
}
-1
View File
@@ -34,7 +34,6 @@ CustomRect {
StateLayer { StateLayer {
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
color: root.visibilities.dashboard ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
onClicked: { onClicked: {
root.visibilities.dashboard = !root.visibilities.dashboard; root.visibilities.dashboard = !root.visibilities.dashboard;
+4 -4
View File
@@ -6,7 +6,6 @@ import QtQuick
import qs.Config import qs.Config
import qs.Components import qs.Components
import qs.Modules.WSOverview import qs.Modules.WSOverview
import qs.Modules.Network
import qs.Modules.SysTray.Popouts import qs.Modules.SysTray.Popouts
import qs.Modules.Updates import qs.Modules.Updates
@@ -17,8 +16,8 @@ Item {
readonly property Popout currentPopout: content.children.find(c => c.shouldBeActive) ?? null readonly property Popout currentPopout: content.children.find(c => c.shouldBeActive) ?? null
required property PopoutState popouts required property PopoutState popouts
implicitHeight: (currentPopout?.implicitHeight ?? 0) + Appearance.padding.small * 2 implicitHeight: (currentPopout?.implicitHeight ?? 0) + 5 * 2
implicitWidth: (currentPopout?.implicitWidth ?? 0) + Appearance.padding.small * 2 implicitWidth: (currentPopout?.implicitWidth ?? 0) + 5 * 2
Item { Item {
id: content id: content
@@ -28,7 +27,7 @@ Item {
Popout { Popout {
name: "audio" name: "audio"
sourceComponent: AudioPopout { sourceComponent: AudioPopup {
wrapper: root.wrapper wrapper: root.wrapper
} }
} }
@@ -73,6 +72,7 @@ Item {
name: "upower" name: "upower"
sourceComponent: UPowerPopout { sourceComponent: UPowerPopout {
wrapper: root.popouts
} }
} }
+2 -2
View File
@@ -10,9 +10,9 @@ 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 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 state
required property PersistentProperties visibilities required property PersistentProperties visibilities
implicitHeight: nonAnimHeight implicitHeight: nonAnimHeight
@@ -59,7 +59,7 @@ Item {
index: 0 index: 0
sourceComponent: Dash { sourceComponent: Dash {
dashState: root.dashState state: root.state
visibilities: root.visibilities visibilities: root.visibilities
} }
} }
+3 -3
View File
@@ -10,9 +10,9 @@ import qs.Modules.Dashboard.Dash
GridLayout { GridLayout {
id: root id: root
required property PersistentProperties dashState
readonly property bool dashboardVisible: visibilities.dashboard readonly property bool dashboardVisible: visibilities.dashboard
property int radius: Appearance.rounding.smallest property int radius: Appearance.rounding.smallest
required property PersistentProperties state
required property PersistentProperties visibilities required property PersistentProperties visibilities
columnSpacing: Appearance.spacing.smaller columnSpacing: Appearance.spacing.smaller
@@ -60,7 +60,7 @@ GridLayout {
User { User {
id: user id: user
dashState: root.dashState state: root.state
} }
} }
@@ -86,7 +86,7 @@ GridLayout {
Calendar { Calendar {
id: calendar id: calendar
dashState: root.dashState state: root.state
} }
} }
+149 -293
View File
@@ -1,50 +1,25 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import Quickshell
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import qs.Components import qs.Components
import qs.Helpers
import qs.Config import qs.Config
import qs.Modules
CustomMouseArea { CustomMouseArea {
id: root id: root
property int activeGrid: 1 readonly property int currMonth: state.currentDate.getMonth()
required property PersistentProperties dashState readonly property int currYear: state.currentDate.getFullYear()
property bool initialized: false required property var state
property int month1
property int month2
readonly property int realCurrMonth: dashState.currentDate.getMonth()
readonly property int realCurrYear: dashState.currentDate.getFullYear()
property int year1
property int year2
function handleDateChange() {
const currM = activeGrid === 1 ? month1 : month2;
const currY = activeGrid === 1 ? year1 : year2;
if (realCurrMonth !== currM || realCurrYear !== currY) {
monthChangeAnim.direction = (realCurrYear > currY || (realCurrYear === currY && realCurrMonth > currM)) ? -1 : 1;
if (activeGrid === 1) {
month2 = realCurrMonth;
year2 = realCurrYear;
activeGrid = 2;
} else {
month1 = realCurrMonth;
year1 = realCurrYear;
activeGrid = 1;
}
if (initialized)
monthChangeAnim.restart();
}
}
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.state.currentDate = new Date(root.currYear, root.currMonth - 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.state.currentDate = new Date(root.currYear, root.currMonth + 1, 1);
} }
acceptedButtons: Qt.MiddleButton acceptedButtons: Qt.MiddleButton
@@ -52,160 +27,102 @@ CustomMouseArea {
anchors.right: parent.right anchors.right: parent.right
implicitHeight: inner.implicitHeight + inner.anchors.margins * 2 implicitHeight: inner.implicitHeight + inner.anchors.margins * 2
Component.onCompleted: { onClicked: root.state.currentDate = new Date()
month1 = realCurrMonth;
year1 = realCurrYear;
month2 = realCurrMonth;
year2 = realCurrYear;
initialized = true;
}
onClicked: root.dashState.currentDate = new Date()
onRealCurrMonthChanged: handleDateChange()
onRealCurrYearChanged: handleDateChange()
SequentialAnimation {
id: monthChangeAnim
property int direction: 0
ScriptAction {
script: {
if (activeGrid === 1) {
titleTranslate1.x = -monthChangeAnim.direction * titleClip.width;
grid1Translate.x = -monthChangeAnim.direction * gridClip.width;
titleTranslate2.x = 0;
grid2Translate.x = 0;
} else {
titleTranslate2.x = -monthChangeAnim.direction * titleClip.width;
grid2Translate.x = -monthChangeAnim.direction * gridClip.width;
titleTranslate1.x = 0;
grid1Translate.x = 0;
}
}
}
ParallelAnimation {
Anim {
property: "x"
target: titleTranslate1
to: root.activeGrid === 1 ? 0 : monthChangeAnim.direction * titleClip.width
type: Anim.DefaultSpatial
}
Anim {
property: "x"
target: grid1Translate
to: root.activeGrid === 1 ? 0 : monthChangeAnim.direction * gridClip.width
type: Anim.DefaultSpatial
}
Anim {
property: "x"
target: titleTranslate2
to: root.activeGrid === 2 ? 0 : monthChangeAnim.direction * titleClip.width
type: Anim.DefaultSpatial
}
Anim {
property: "x"
target: grid2Translate
to: root.activeGrid === 2 ? 0 : monthChangeAnim.direction * gridClip.width
type: Anim.DefaultSpatial
}
}
}
ColumnLayout { ColumnLayout {
id: inner id: inner
anchors.fill: parent anchors.fill: parent
anchors.margins: Appearance.padding.large anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.extraSmall spacing: Appearance.spacing.small
RowLayout { RowLayout {
id: monthNavigationRow id: monthNavigationRow
Layout.fillWidth: true Layout.fillWidth: true
spacing: Appearance.spacing.extraSmall spacing: Appearance.spacing.small
IconButton { Item {
icon: "chevron_left" implicitHeight: prevMonthText.implicitHeight + Appearance.padding.small * 2
padding: Appearance.padding.small implicitWidth: implicitHeight
type: IconButton.Text
onClicked: root.dashState.currentDate = new Date(root.realCurrYear, root.realCurrMonth - 1, 1) StateLayer {
id: prevMonthStateLayer
function onClicked(): void {
root.state.currentDate = new Date(root.currYear, root.currMonth - 1, 1);
}
radius: Appearance.rounding.full
}
MaterialIcon {
id: prevMonthText
anchors.centerIn: parent
color: DynamicColors.palette.m3tertiary
font.pointSize: Appearance.font.size.normal
font.weight: 700
text: "chevron_left"
}
} }
Item { Item {
Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: monthYearDisplay1.implicitHeight + Appearance.padding.extraSmall * 2 implicitHeight: monthYearDisplay.implicitHeight + Appearance.padding.small * 2
implicitWidth: monthYearDisplay1.implicitWidth + Appearance.padding.large * 2 implicitWidth: monthYearDisplay.implicitWidth + Appearance.padding.small * 2
StateLayer { StateLayer {
color: DynamicColors.palette.m3primary function onClicked(): void {
enabled: { root.state.currentDate = new Date();
}
anchors.fill: monthYearDisplay
anchors.leftMargin: -Appearance.padding.normal
anchors.margins: -Appearance.padding.small
anchors.rightMargin: -Appearance.padding.normal
disabled: {
const now = new Date(); const now = new Date();
return root.realCurrMonth !== now.getMonth() || root.realCurrYear !== now.getFullYear(); return root.currMonth === now.getMonth() && root.currYear === now.getFullYear();
} }
radius: pressed ? Appearance.rounding.small : Appearance.rounding.large radius: Appearance.rounding.full
Behavior on radius {
Anim {
type: Anim.DefaultEffects
}
}
onClicked: root.dashState.currentDate = new Date()
} }
Item { CustomText {
id: titleClip id: monthYearDisplay
anchors.fill: parent anchors.centerIn: parent
clip: true color: DynamicColors.palette.m3primary
font.capitalization: Font.Capitalize
CustomText { font.pointSize: Appearance.font.size.normal
id: monthYearDisplay1 font.weight: 500
text: grid.title
anchors.centerIn: parent
// qmllint enable missing-property
color: DynamicColors.palette.m3primary
// qmllint disable missing-property
text: grid1.item ? grid1.item.title : ""
visible: root.activeGrid === 1 || monthChangeAnim.running
transform: Translate {
id: titleTranslate1
}
}
CustomText {
id: monthYearDisplay2
anchors.centerIn: parent
// qmllint enable missing-property
color: DynamicColors.palette.m3primary
// qmllint disable missing-property
text: grid2.item ? grid2.item.title : ""
visible: root.activeGrid === 2 || monthChangeAnim.running
transform: Translate {
id: titleTranslate2
}
}
} }
} }
IconButton { Item {
icon: "chevron_right" implicitHeight: nextMonthText.implicitHeight + Appearance.padding.small * 2
padding: Appearance.padding.small implicitWidth: implicitHeight
type: IconButton.Text
onClicked: root.dashState.currentDate = new Date(root.realCurrYear, root.realCurrMonth + 1, 1) StateLayer {
id: nextMonthStateLayer
function onClicked(): void {
root.state.currentDate = new Date(root.currYear, root.currMonth + 1, 1);
}
radius: Appearance.rounding.full
}
MaterialIcon {
id: nextMonthText
anchors.centerIn: parent
color: DynamicColors.palette.m3tertiary
font.pointSize: Appearance.font.size.normal
font.weight: 700
text: "chevron_right"
}
} }
} }
@@ -213,170 +130,109 @@ CustomMouseArea {
id: daysRow id: daysRow
Layout.fillWidth: true Layout.fillWidth: true
locale: Qt.locale() locale: grid.locale
delegate: CustomText { delegate: CustomText {
required property var model required property var model
color: (model.day === 0) ? DynamicColors.palette.m3tertiary : DynamicColors.palette.m3onSurface color: (model.day === 0) ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3onSurfaceVariant
font.weight: 500
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: Qt.locale("en_US").standaloneDayName(model.day, Locale.ShortFormat) text: model.shortName
} }
} }
Item { Item {
id: gridClip
Layout.fillWidth: true Layout.fillWidth: true
clip: true implicitHeight: grid.implicitHeight
implicitHeight: grid1.implicitHeight
Component { MonthGrid {
id: gridComp id: grid
Item { anchors.fill: parent
id: internalGridContainer locale: Qt.locale("en_SE")
month: root.currMonth
spacing: 3
year: root.currYear
property int month delegate: Item {
property alias title: internalGrid.title id: dayItem
property int year
implicitHeight: internalGrid.implicitHeight required property var model
MonthGrid { implicitHeight: text.implicitHeight + Appearance.padding.small * 2
id: internalGrid implicitWidth: implicitHeight
anchors.fill: parent CustomText {
locale: daysRow.locale id: text
month: internalGridContainer.month
spacing: 3
title: `${Qt.locale("en_US").standaloneMonthName(month, Locale.LongFormat)} ${year}`
year: internalGridContainer.year
delegate: Item { anchors.centerIn: parent
id: dayItem color: {
const dayOfWeek = dayItem.model.date.getUTCDay();
if (dayOfWeek === 6)
return DynamicColors.palette.m3secondary;
required property var model return DynamicColors.palette.m3onSurfaceVariant;
implicitHeight: text.implicitHeight + Appearance.padding.normal * 2
implicitWidth: implicitHeight
CustomText {
id: text
anchors.centerIn: parent
color: {
const dayOfWeek = dayItem.model.date.getDay();
if (dayOfWeek === 0)
return DynamicColors.palette.m3tertiary;
return DynamicColors.palette.m3onSurfaceVariant;
}
horizontalAlignment: Text.AlignHCenter
opacity: dayItem.model.today || dayItem.model.month === internalGrid.month ? 1 : 0.4
text: internalGrid.locale.toString(dayItem.model.day)
}
}
}
CustomRect {
id: todayIndicator
property Item today
readonly property Item todayItem: internalGrid.contentItem.children.find(c => c.model.today) ?? null
clip: true
color: DynamicColors.palette.m3primary
implicitHeight: width
implicitWidth: today ? (Math.max(today.implicitWidth, today.implicitHeight) - Appearance.padding.extraSmall) : 0
opacity: todayItem ? 1 : 0
radius: Appearance.rounding.full
scale: todayItem ? 1 : 0.7
x: today ? today.x + (today.width - implicitWidth) / 2 : 0
y: today ? today.y + Appearance.padding.extraSmall / 2 : 0
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
Behavior on scale {
Anim {
type: Anim.FastSpatial
}
}
Behavior on x {
Anim {
}
}
Behavior on y {
Anim {
}
}
onTodayItemChanged: {
if (todayItem)
today = todayItem;
}
Coloriser {
colorizationColor: DynamicColors.palette.m3onPrimary
implicitHeight: internalGrid.height
implicitWidth: internalGrid.width
source: internalGrid
sourceColor: DynamicColors.palette.m3onSurface
x: -todayIndicator.x
y: -todayIndicator.y
} }
font.pointSize: Appearance.font.size.normal
font.weight: 500
horizontalAlignment: Text.AlignHCenter
opacity: dayItem.model.today || dayItem.model.month === grid.month ? 1 : 0.4
text: grid.locale.toString(dayItem.model.day)
} }
} }
} }
Loader { CustomRect {
id: grid1 id: todayIndicator
anchors.fill: parent property Item today
sourceComponent: gridComp readonly property Item todayItem: grid.contentItem.children.find(c => c.model.today) ?? null
visible: root.activeGrid === 1 || monthChangeAnim.running
transform: Translate { clip: true
id: grid1Translate color: DynamicColors.palette.m3primary
implicitHeight: today?.implicitHeight ?? 0
implicitWidth: today?.implicitWidth ?? 0
opacity: todayItem ? 1 : 0
radius: Appearance.rounding.full
scale: todayItem ? 1 : 0.7
x: today ? today.x + (today.width - implicitWidth) / 2 : 0
y: today?.y ?? 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on x {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
Behavior on y {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
} }
Binding { onTodayItemChanged: {
property: "month" if (todayItem)
target: grid1.item today = todayItem;
value: root.month1
} }
Binding { Coloriser {
property: "year" colorizationColor: DynamicColors.palette.m3onPrimary
target: grid1.item implicitHeight: grid.height
value: root.year1 implicitWidth: grid.width
} source: grid
} sourceColor: DynamicColors.palette.m3onSurface
x: -todayIndicator.x
Loader { y: -todayIndicator.y
id: grid2
anchors.fill: parent
sourceComponent: gridComp
visible: root.activeGrid === 2 || monthChangeAnim.running
transform: Translate {
id: grid2Translate
}
Binding {
property: "month"
target: grid2.item
value: root.month2
}
Binding {
property: "year"
target: grid2.item
value: root.year2
} }
} }
} }
+37 -74
View File
@@ -4,7 +4,6 @@ import Quickshell
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Shapes import QtQuick.Shapes
import QtQuick.Effects
import ZShell.Services import ZShell.Services
import qs.Daemons import qs.Daemons
import qs.Components import qs.Components
@@ -16,7 +15,7 @@ Item {
property real playerProgress: { property real playerProgress: {
const active = Players.active; const active = Players.active;
return active?.length ? (active.position % active.length) / active.length : 0; return active?.length ? active.position / active.length : 0;
} }
property int rowHeight: Appearance.padding.large + Config.dashboard.sizes.mediaProgressThickness + Appearance.spacing.small property int rowHeight: Appearance.padding.large + Config.dashboard.sizes.mediaProgressThickness + Appearance.spacing.small
@@ -46,82 +45,46 @@ Item {
Shape { Shape {
id: visualizer id: visualizer
readonly property real barW: Math.max(0, (width - gap * (bars - 1)) / bars)
readonly property int bars: Config.services.visualizerBars readonly property int bars: Config.services.visualizerBars
property color color: DynamicColors.palette.m3tertiary property color color: DynamicColors.palette.m3primary
property color fillColor: Qt.alpha(color, 0.25) readonly property real gap: Appearance.spacing.small
anchors.fill: layout anchors.fill: layout
anchors.leftMargin: -(shape.strokeWidth / 2)
layer.enabled: true
asynchronous: true asynchronous: true
data: visualizerBars.instances
preferredRendererType: Shape.CurveRenderer 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 = layout.height + shape.strokeWidth / 2;
const w = layout.width + shape.strokeWidth;
function x(i) {
return i * w / (n - 1);
}
function y(i) {
return h - values[i] * Config.dashboard.sizes.mediaVisualizerSize;
}
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 { Variants {
anchors.fill: visualizer id: visualizerBars
color: Qt.alpha(DynamicColors.palette.m3shadow, 0.2) model: Array.from({
layer.enabled: true length: Config.services.visualizerBars
layer.effect: MultiEffect { }, (_, i) => i)
maskEnabled: true
maskSource: visualizer ShapePath {
id: visualizerBar
readonly property real magnitude: value * Config.dashboard.sizes.mediaVisualiserSize
required property int modelData
readonly property real value: Math.max(1e-3, Audio.cava.values[modelData])
capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
startX: (visualizer.barW / 2) + modelData * (visualizer.barW + visualizer.gap)
startY: layout.y + layout.height
strokeColor: visualizer.color
strokeWidth: visualizer.barW
Behavior on strokeColor {
CAnim {
}
}
PathLine {
relativeX: 0
relativeY: -visualizerBar.magnitude
}
} }
} }
@@ -332,12 +295,12 @@ Item {
StateLayer { StateLayer {
id: controlState id: controlState
color: control.canUse ? DynamicColors.palette[`m3on${control.set_color}`] : DynamicColors.palette[`m3on${control.set_color}Container`] function onClicked(): void {
enabled: control.canUse
onClicked: {
control.onClicked(); control.onClicked();
} }
color: control.canUse ? DynamicColors.palette[`m3on${control.set_color}`] : DynamicColors.palette[`m3on${control.set_color}Container`]
disabled: !control.canUse
// radius: Appearance.rounding.full // radius: Appearance.rounding.full
} }
+59 -56
View File
@@ -1,89 +1,92 @@
import QtQuick import QtQuick
import QtQuick.Layouts
import ZShell.Services
import qs.Components import qs.Components
import qs.Helpers import qs.Helpers
import qs.Config import qs.Config
Item { Row {
id: root id: root
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.top: parent.top anchors.top: parent.top
implicitWidth: layout.implicitWidth + layout.anchors.margins * 4 padding: Appearance.padding.large
spacing: Appearance.spacing.large
ServiceRef { Ref {
service: Storage service: SystemUsage
} }
ServiceRef { Resource {
service: Memory color: DynamicColors.palette.m3primary
icon: "memory"
value: SystemUsage.cpuPerc
} }
ServiceRef { Resource {
service: Cpu color: DynamicColors.palette.m3secondary
icon: "memory_alt"
value: SystemUsage.memPerc
} }
ServiceRef { Resource {
service: Gpu color: DynamicColors.palette.m3tertiary
icon: "gamepad"
value: SystemUsage.gpuPerc
} }
ColumnLayout { Resource {
id: layout color: DynamicColors.palette.m3primary
icon: "host"
anchors.bottom: parent.bottom value: SystemUsage.gpuMemUsed
anchors.horizontalCenter: parent.horizontalCenter
anchors.margins: Appearance.padding.large
anchors.top: parent.top
spacing: Appearance.spacing.normal
Resource {
fgColor: DynamicColors.palette.m3primary
icon: "memory"
value: Cpu.percentage
}
Resource {
fgColor: DynamicColors.palette.m3secondary
icon: "memory_alt"
value: Memory.percentage
}
Resource {
fgColor: DynamicColors.palette.m3tertiary
icon: "gamepad"
value: Gpu.percentage
}
Resource {
fgColor: DynamicColors.palette.m3primary
icon: "host"
value: Gpu.memoryUsed / Gpu.memoryTotal
}
Resource {
fgColor: DynamicColors.palette.m3secondary
icon: "hard_disk"
value: Storage.percentage
}
} }
component Resource: CircularProgress { Resource {
color: DynamicColors.palette.m3secondary
icon: "hard_disk"
value: SystemUsage.storagePerc
}
component Resource: Item {
id: res id: res
required property color color
required property string icon required property string icon
required property real value
Layout.fillHeight: true anchors.bottom: parent.bottom
implicitSize: height anchors.margins: Appearance.padding.large
anchors.top: parent.top
implicitWidth: icon.implicitWidth
Behavior on clampedVal { Behavior on value {
Anim { Anim {
duration: Appearance.anim.durations.large
}
}
CustomRect {
anchors.bottom: icon.top
anchors.bottomMargin: Appearance.spacing.small
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
implicitWidth: Config.dashboard.sizes.resourceProgessThickness
radius: Appearance.rounding.full
CustomRect {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
color: res.color
implicitHeight: res.value * parent.height
radius: Appearance.rounding.full
} }
} }
MaterialIcon { MaterialIcon {
anchors.centerIn: parent id: icon
color: res.fgColor
anchors.bottom: parent.bottom
color: res.color
text: res.icon text: res.icon
} }
} }
+1 -1
View File
@@ -9,7 +9,7 @@ import QtQuick
Row { Row {
id: root id: root
required property PersistentProperties dashState required property PersistentProperties state
padding: 20 padding: 20
spacing: 12 spacing: 12
+1 -1
View File
@@ -32,7 +32,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
sourceComponent: Content { sourceComponent: Content {
dashState: root.dashState state: root.dashState
visibilities: root.visibilities visibilities: root.visibilities
} }
} }
+174 -211
View File
@@ -6,264 +6,227 @@ import qs.Components
import qs.Config import qs.Config
Item { Item {
id: contextMenu id: contextMenu
property real menuX: 0 anchors.fill: parent
property real menuY: 0 z: 999
property var targetAppEntry: null visible: false
property string targetFilePath: ""
property bool targetIsDir: false
property var targetPaths: []
signal openFileRequested(string path, bool isDir) property string targetFilePath: ""
signal renameRequested(string path) property bool targetIsDir: false
property var targetAppEntry: null
function close() { property var targetPaths: []
visible = false;
}
function openAt(mouseX, mouseY, path, isDir, appEnt, parentW, parentH, selectionArray) { signal openFileRequested(string path, bool isDir)
targetFilePath = path; signal renameRequested(string path)
targetIsDir = isDir;
targetAppEntry = appEnt;
targetPaths = (selectionArray && selectionArray.length > 0) ? selectionArray : [path]; property real menuX: 0
property real menuY: 0
menuX = Math.floor(Math.min(mouseX, parentW - popupBackground.implicitWidth)); CustomClippingRect {
menuY = Math.floor(Math.min(mouseY, parentH - popupBackground.implicitHeight)); id: popupBackground
readonly property real padding: Appearance.padding.small
visible = true; x: contextMenu.menuX
} y: contextMenu.menuY
anchors.fill: parent color: DynamicColors.tPalette.m3surface
visible: false radius: Appearance.rounding.normal
z: 999
CustomClippingRect { implicitWidth: menuLayout.implicitWidth + padding * 2
id: popupBackground implicitHeight: menuLayout.implicitHeight + padding * 2
readonly property real padding: Appearance.padding.small Behavior on opacity { Anim {} }
opacity: contextMenu.visible ? 1 : 0
color: DynamicColors.tPalette.m3surface ColumnLayout {
implicitHeight: menuLayout.implicitHeight + padding * 2 id: menuLayout
implicitWidth: menuLayout.implicitWidth + padding * 2 anchors.centerIn: parent
opacity: contextMenu.visible ? 1 : 0 spacing: 0
radius: Appearance.rounding.normal
x: contextMenu.menuX
y: contextMenu.menuY
Behavior on opacity { CustomRect {
Anim { Layout.preferredWidth: 160
} radius: popupBackground.radius - popupBackground.padding
} implicitHeight: openRow.implicitHeight + Appearance.padding.small * 2
ColumnLayout { RowLayout {
id: menuLayout id: openRow
spacing: 8
anchors.fill: parent
anchors.leftMargin: Appearance.padding.smaller
anchors.centerIn: parent MaterialIcon { text: "open_in_new"; font.pointSize: 20 }
spacing: 0 CustomText { text: "Open"; Layout.fillWidth: true }
}
CustomRect { StateLayer {
Layout.preferredWidth: 160 anchors.fill: parent
implicitHeight: openRow.implicitHeight + Appearance.padding.small * 2
radius: popupBackground.radius - popupBackground.padding
RowLayout { onClicked: {
id: openRow for (let i = 0; i < contextMenu.targetPaths.length; i++) {
let p = contextMenu.targetPaths[i];
if (p === contextMenu.targetFilePath) {
if (p.endsWith(".desktop") && contextMenu.targetAppEntry) contextMenu.targetAppEntry.execute()
else contextMenu.openFileRequested(p, contextMenu.targetIsDir)
} else {
Quickshell.execDetached(["xdg-open", p])
}
}
contextMenu.close()
}
}
}
anchors.fill: parent CustomRect {
anchors.leftMargin: Appearance.padding.smaller Layout.fillWidth: true
spacing: 8 radius: popupBackground.radius - popupBackground.padding
implicitHeight: openWithRow.implicitHeight + Appearance.padding.small * 2
MaterialIcon { RowLayout {
font.pointSize: 20 id: openWithRow
text: "open_in_new" spacing: 8
} anchors.fill: parent
anchors.leftMargin: Appearance.padding.smaller
CustomText { MaterialIcon { text: contextMenu.targetIsDir ? "terminal" : "apps"; font.pointSize: 20 }
Layout.fillWidth: true CustomText { text: contextMenu.targetIsDir ? "Open in terminal" : "Open with..."; Layout.fillWidth: true }
text: "Open" }
}
}
StateLayer { StateLayer {
anchors.fill: parent anchors.fill: parent
onClicked: {
for (let i = 0; i < contextMenu.targetPaths.length; i++) {
let p = contextMenu.targetPaths[i];
if (p === contextMenu.targetFilePath) {
if (p.endsWith(".desktop") && contextMenu.targetAppEntry)
contextMenu.targetAppEntry.execute();
else
contextMenu.openFileRequested(p, contextMenu.targetIsDir);
} else {
Quickshell.execDetached(["xdg-open", p]);
}
}
contextMenu.close();
}
}
}
CustomRect {
Layout.fillWidth: true
implicitHeight: openWithRow.implicitHeight + Appearance.padding.small * 2
radius: popupBackground.radius - popupBackground.padding
RowLayout {
id: openWithRow
anchors.fill: parent
anchors.leftMargin: Appearance.padding.smaller
spacing: 8
MaterialIcon {
font.pointSize: 20
text: contextMenu.targetIsDir ? "terminal" : "apps"
}
CustomText {
Layout.fillWidth: true
text: contextMenu.targetIsDir ? "Open in terminal" : "Open with..."
}
}
StateLayer {
anchors.fill: parent
onClicked: { onClicked: {
if (contextMenu.targetIsDir) { if (contextMenu.targetIsDir) {
Quickshell.execDetached([Config.general.apps.terminal, "--working-directory", contextMenu.targetFilePath]); Quickshell.execDetached([Config.general.apps.terminal, "--working-directory", contextMenu.targetFilePath])
} else { } else {
Quickshell.execDetached(["xdg-open", contextMenu.targetFilePath]); Quickshell.execDetached(["xdg-open", contextMenu.targetFilePath])
} }
contextMenu.close(); contextMenu.close()
} }
} }
} }
CustomRect { CustomRect {
Layout.bottomMargin: 4 Layout.fillWidth: true
Layout.fillWidth: true implicitHeight: 1
Layout.topMargin: 4 color: DynamicColors.palette.m3outlineVariant
color: DynamicColors.palette.m3outlineVariant Layout.topMargin: 4
implicitHeight: 1 Layout.bottomMargin: 4
} }
CustomRect { CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: copyPathRow.implicitHeight + Appearance.padding.small * 2 radius: popupBackground.radius - popupBackground.padding
radius: popupBackground.radius - popupBackground.padding implicitHeight: copyPathRow.implicitHeight + Appearance.padding.small * 2
RowLayout { RowLayout {
id: copyPathRow id: copyPathRow
spacing: 8
anchors.fill: parent
anchors.leftMargin: Appearance.padding.smaller
anchors.fill: parent MaterialIcon { text: "content_copy"; font.pointSize: 20 }
anchors.leftMargin: Appearance.padding.smaller CustomText { text: "Copy path"; Layout.fillWidth: true }
spacing: 8 }
MaterialIcon { StateLayer {
font.pointSize: 20 anchors.fill: parent
text: "content_copy"
}
CustomText { onClicked: {
Layout.fillWidth: true Quickshell.execDetached(["wl-copy", contextMenu.targetPaths.join("\n")])
text: "Copy path" contextMenu.close()
} }
} }
}
StateLayer { CustomRect {
anchors.fill: parent Layout.fillWidth: true
visible: contextMenu.targetPaths.length === 1
radius: popupBackground.radius - popupBackground.padding
implicitHeight: renameRow.implicitHeight + Appearance.padding.small * 2
onClicked: { RowLayout {
Quickshell.execDetached(["wl-copy", contextMenu.targetPaths.join("\n")]); id: renameRow
contextMenu.close(); spacing: 8
} anchors.fill: parent
} anchors.leftMargin: Appearance.padding.smaller
}
CustomRect { MaterialIcon { text: "edit"; font.pointSize: 20 }
Layout.fillWidth: true CustomText { text: "Rename"; Layout.fillWidth: true }
implicitHeight: renameRow.implicitHeight + Appearance.padding.small * 2 }
radius: popupBackground.radius - popupBackground.padding
visible: contextMenu.targetPaths.length === 1
RowLayout { StateLayer {
id: renameRow anchors.fill: parent
anchors.fill: parent onClicked: {
anchors.leftMargin: Appearance.padding.smaller contextMenu.renameRequested(contextMenu.targetFilePath)
spacing: 8 contextMenu.close()
}
}
}
MaterialIcon { Rectangle {
font.pointSize: 20 Layout.fillWidth: true
text: "edit" implicitHeight: 1
} color: DynamicColors.palette.m3outlineVariant
Layout.topMargin: 4
Layout.bottomMargin: 4
}
CustomText { CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
text: "Rename" radius: popupBackground.radius - popupBackground.padding
} implicitHeight: deleteRow.implicitHeight + Appearance.padding.small * 2
}
StateLayer { RowLayout {
anchors.fill: parent id: deleteRow
spacing: 8
anchors.fill: parent
anchors.leftMargin: Appearance.padding.smaller
onClicked: { MaterialIcon {
contextMenu.renameRequested(contextMenu.targetFilePath); text: "delete"
contextMenu.close(); font.pointSize: 20
} color: deleteButton.hovered ? DynamicColors.palette.m3onError : DynamicColors.palette.m3error
} }
}
Rectangle { CustomText {
Layout.bottomMargin: 4 text: "Move to trash"
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 4 color: deleteButton.hovered ? DynamicColors.palette.m3onError : DynamicColors.palette.m3error
color: DynamicColors.palette.m3outlineVariant }
implicitHeight: 1 }
}
CustomRect { StateLayer {
Layout.fillWidth: true id: deleteButton
implicitHeight: deleteRow.implicitHeight + Appearance.padding.small * 2 anchors.fill: parent
radius: popupBackground.radius - popupBackground.padding color: DynamicColors.tPalette.m3error
RowLayout { onClicked: {
id: deleteRow let cmd = ["gio", "trash"].concat(contextMenu.targetPaths)
Quickshell.execDetached(cmd)
contextMenu.close()
}
}
}
}
}
anchors.fill: parent function openAt(mouseX, mouseY, path, isDir, appEnt, parentW, parentH, selectionArray) {
anchors.leftMargin: Appearance.padding.smaller targetFilePath = path
spacing: 8 targetIsDir = isDir
targetAppEntry = appEnt
MaterialIcon { targetPaths = (selectionArray && selectionArray.length > 0) ? selectionArray : [path]
color: deleteButton.hovered ? DynamicColors.palette.m3onError : DynamicColors.palette.m3error
font.pointSize: 20
text: "delete"
}
CustomText { menuX = Math.floor(Math.min(mouseX, parentW - popupBackground.implicitWidth))
Layout.fillWidth: true menuY = Math.floor(Math.min(mouseY, parentH - popupBackground.implicitHeight))
color: deleteButton.hovered ? DynamicColors.palette.m3onError : DynamicColors.palette.m3error
text: "Move to trash"
}
}
StateLayer { visible = true
id: deleteButton }
anchors.fill: parent function close() {
color: DynamicColors.tPalette.m3error visible = false
}
onClicked: {
let cmd = ["gio", "trash"].concat(contextMenu.targetPaths);
Quickshell.execDetached(cmd);
contextMenu.close();
}
}
}
}
}
} }

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