100 Commits

Author SHA1 Message Date
Zach 6c5ca40b8e Merge pull request #23 from Zacharias-Brohn/settingsWindow
Merge settings window to main
2026-03-18 16:27:50 +01:00
Zach e65ec01b12 Merge pull request #22 from Zacharias-Brohn/nix-fixes-unstable
Nix fixes: minor changes
2026-03-18 16:22:04 +01:00
Zacharias-Brohn 9297fa4da2 settings fix 2026-03-18 13:21:18 +01:00
Zacharias-Brohn d885037739 border 2026-03-18 13:17:11 +01:00
Zacharias-Brohn 65703f3e71 border color 2026-03-18 13:04:29 +01:00
Zacharias-Brohn bc67da35e4 updates persistence 2026-03-18 11:37:14 +01:00
Zacharias-Brohn 159e10cc0f updates popout 2026-03-17 23:59:15 +01:00
Aram Markarov c4a3206ffd removing 'result' and adjusted .gitignore to not include result symlink folder 2026-03-17 21:37:21 +01:00
Aram Markarov 7a61cc4280 warning in README.md for nix users 2026-03-17 21:28:11 +01:00
Aram Markarov 9e8ee9b9de updated ideas.md 2026-03-17 21:26:36 +01:00
Zacharias-Brohn 152b363da2 updates popout 2026-03-17 18:46:10 +01:00
AramMarkarov f4c5ce08d2 preparation brightnesscontrol ddcutil and brightnessctl : missing osd functionality 2026-03-17 04:20:25 +01:00
Aram Markarov 46a1af82f5 README.md update to match new config location. 2026-03-16 16:01:03 +01:00
Zacharias-Brohn ca49b9c6f1 plans 2026-03-16 15:45:37 +01:00
Zacharias-Brohn 35fe6c1e5f added settings options 2026-03-16 15:34:11 +01:00
Aram Markarov b555c96de1 added question to md file 2026-03-16 15:20:51 +01:00
Aram Markarov 386040d38a updated plans with current issues to be fixed on settingsWindow 2026-03-16 15:11:10 +01:00
Zacharias-Brohn 3dc6c0ee3e remove required property from borders 2026-03-15 22:57:23 +01:00
Zacharias-Brohn a4e086192d drawing sliders 2026-03-15 22:41:10 +01:00
Zacharias-Brohn 9c955581fa drawing clear on right click 2026-03-15 18:50:26 +01:00
Zacharias-Brohn f7b7260780 settings 2026-03-14 17:29:24 +01:00
Zacharias-Brohn 8bc7826f26 dock 2026-03-13 20:07:03 +01:00
Zacharias-Brohn b7ca2f5c93 dock 2026-03-13 17:35:46 +01:00
inorishio 31df6a6cdf settings 2026-03-13 16:28:03 +01:00
Zacharias-Brohn 8f427d06d0 dock 2026-03-13 16:27:31 +01:00
Zacharias-Brohn 37e482a361 dock 2026-03-13 14:03:40 +01:00
Zacharias-Brohn b65117e213 dock 2026-03-12 19:24:23 +01:00
Aram Markarov 08b18880a0 added result folder (nix build file) to gitignore(updated to work) 2026-03-12 18:50:29 +01:00
Aram Markarov 042a92a881 added result folder (nix build file) to gitignore 2026-03-12 17:55:42 +01:00
Aram Markarov 42c4b66399 comments removed from a app2unit.nix file. 2026-03-12 17:39:10 +01:00
Zacharias-Brohn 0cd2b3dbfc dock 2026-03-12 16:30:53 +01:00
Zacharias-Brohn 0b935a3096 dock 2026-03-12 16:27:02 +01:00
Zacharias-Brohn 9e9708ed12 desktop icons 2026-03-12 14:45:20 +01:00
Zacharias-Brohn 851b78f0ff kek test 2026-03-12 10:04:27 +01:00
Zacharias-Brohn 401ccef90c slight changes 2026-03-11 19:49:19 +01:00
Zacharias-Brohn 646605cc9b slight changes 2026-03-11 19:47:14 +01:00
Zacharias-Brohn bab596850b slight changes, gpu usage down by 20% 2026-03-11 11:22:22 +01:00
Zacharias-Brohn 0375491dc8 wtf is happening 2026-03-10 22:42:06 +01:00
Zacharias-Brohn 203b19ce45 shader hotfix 2026-03-10 19:15:26 +01:00
Zacharias-Brohn 098db5e903 refactor done? 2026-03-10 15:39:29 +01:00
Zacharias-Brohn 26bc5cd7c3 start of refactor 2026-03-09 23:18:13 +01:00
Zacharias-Brohn 88d795a73f start of refactor 2026-03-09 22:17:34 +01:00
Zacharias-Brohn fb15c2421b fix screenshot picker showing up in screenshots 2026-03-09 13:05:45 +01:00
Zacharias-Brohn 1ee345f946 drawing 2026-03-09 12:47:09 +01:00
Zacharias-Brohn 720bc2808e back to opengl 2026-03-09 00:36:18 +01:00
Zacharias-Brohn a9fc2cbf69 vulkan, screenshotting broken 2026-03-09 00:35:18 +01:00
Zacharias-Brohn 902a1adbfe rounding 2026-03-08 10:06:04 +01:00
Zacharias-Brohn 1b0eb9fdb2 smart 2026-03-07 18:14:45 +01:00
Zacharias-Brohn 7eb7cecf1e smart 2026-03-07 18:13:07 +01:00
Zach 9bfa2f6cca Merge pull request #21 from Zacharias-Brohn/settingsWindow
Settings window
2026-03-07 17:54:39 +01:00
Zacharias-Brohn 1d84248295 smart 2026-03-07 17:52:46 +01:00
Zacharias-Brohn c0f4434fd4 smart 2026-03-07 17:49:05 +01:00
Zach 49b489e7d9 Merge pull request #20 from Zacharias-Brohn/settingsWindow
Settings window
2026-03-06 23:27:24 +01:00
Zach 5d27d603e5 Merge branch 'main' into settingsWindow 2026-03-06 23:27:15 +01:00
Zacharias-Brohn 889c5993df lol 2026-03-06 23:23:39 +01:00
Zacharias-Brohn eac056cf1e lol 2026-03-06 23:12:47 +01:00
Zacharias-Brohn 62b250303a lol 2026-03-06 23:05:34 +01:00
Zacharias-Brohn 6d872c4f8d lol 2026-03-06 22:59:09 +01:00
Aram Markarov 74e417a49c added jinja2 python dependency to zshell-cli nix file 2026-03-05 22:08:48 +01:00
Zacharias-Brohn 6371fa56f8 remove wallust 2026-03-05 21:36:26 +01:00
Zacharias-Brohn 79b5220081 test 2026-03-05 16:48:28 +01:00
Zacharias-Brohn 3ecddee5e9 test 2026-03-05 16:41:12 +01:00
Zacharias-Brohn de91e36228 test 2026-03-05 16:34:35 +01:00
Zacharias-Brohn 467fdc5e5e test 2026-03-05 16:32:15 +01:00
Zacharias-Brohn f5a3b6f98f test 2026-03-05 15:50:34 +01:00
Zacharias-Brohn 2baf91552d test 2026-03-05 15:48:24 +01:00
Zacharias-Brohn 76d5508072 test 2026-03-05 15:31:24 +01:00
Zacharias-Brohn 533f268184 test 2026-03-05 15:23:21 +01:00
Zacharias-Brohn 0b6b5d0491 test 2026-03-05 15:20:20 +01:00
Zacharias-Brohn 75df8e1134 test 2026-03-05 11:43:22 +01:00
Zacharias-Brohn bab9554a60 test 2026-03-05 10:39:08 +01:00
Zacharias-Brohn e195f58125 test 2026-03-04 22:52:08 +01:00
Zacharias-Brohn 71b871c976 test 2026-03-04 22:41:45 +01:00
Zacharias-Brohn c87443e72c test 2026-03-04 22:38:15 +01:00
Zacharias-Brohn 531dd35f50 test 2026-03-04 22:32:11 +01:00
Zacharias-Brohn f6a45d6a21 test 2026-03-04 22:29:08 +01:00
Zacharias-Brohn e31fb851fa test 2026-03-04 22:28:25 +01:00
Zacharias-Brohn 62b623c16d test 2026-03-04 22:26:26 +01:00
Zacharias-Brohn 52cf956323 test 2026-03-04 22:25:12 +01:00
Zacharias-Brohn 52a9049abb oops lol 2026-03-04 20:08:24 +01:00
Zacharias-Brohn a2c9ad6e29 qalc 2026-03-04 20:06:58 +01:00
Zacharias-Brohn 5c428b211f launcher logout icon 2026-03-03 23:13:09 +01:00
Zacharias-Brohn 9c8c48c5ee window title width fixed 2026-03-03 19:27:45 +01:00
Zacharias-Brohn d6ce5af55c window title width fixed 2026-03-03 19:18:10 +01:00
Zacharias-Brohn fb1cc51eda resource widget is no longer broken 2026-03-02 23:16:52 +01:00
Zacharias-Brohn 6e967f8fd6 activewindow 2026-03-02 20:00:16 +01:00
Zacharias-Brohn e5595d8b5d traywidget background 2026-03-02 18:43:16 +01:00
Zacharias-Brohn 7451c52684 resource widget is broken lol 2026-03-02 17:09:39 +01:00
Zacharias-Brohn 9065d693ef stuff 2026-03-02 14:42:03 +01:00
Zacharias-Brohn f0afc7c75a remove console.log 2026-03-02 13:39:24 +01:00
Zacharias-Brohn 9040713231 workspace rework 2026-03-02 13:38:58 +01:00
Zacharias-Brohn cda00f91a3 bar height 2026-03-01 22:21:35 +01:00
Zach 749358dd5b Merge pull request #19 from Zacharias-Brohn/settingsWindow2
Settings window2
2026-03-01 18:13:23 +01:00
Zacharias-Brohn f989f74282 menu switch 2026-03-01 18:12:43 +01:00
Zach 9fd3f8fd9e Merge pull request #18 from Zacharias-Brohn/horizontal-media-widget
Horizontal media widget
2026-02-25 22:22:03 +01:00
Zach a8dd730808 Merge pull request #17 from Zacharias-Brohn/horizontal-media-widget
update
2026-02-25 22:14:19 +01:00
Zach dcfaa21e32 Merge pull request #15 from Zacharias-Brohn/libcava/cava_integration
cava reintroduced : uses libcava or cava for nix : nixos might need fixing…
2026-02-25 22:00:44 +01:00
Zach e124819dcb Merge pull request #16 from Zacharias-Brohn/horizontal-media-widget
Horizontal media widget
2026-02-25 21:59:57 +01:00
Aram Markarov 7861ba5c51 cava reintroduced : uses libcava or cava for nix : needs nixos fixing, but could be isolated issue 2026-02-25 19:14:58 +01:00
Zacharias-Brohn 6faebc986d ideas 2026-02-25 17:30:25 +01:00
152 changed files with 10282 additions and 2152 deletions
+1
View File
@@ -1,3 +1,4 @@
./result/
.pyre/ .pyre/
.cache/ .cache/
.venv/ .venv/
+24
View File
@@ -0,0 +1,24 @@
import QtQuick
QtObject {
id: root
property real idx1: index
property int idx1Duration: 100
property real idx2: index
property int idx2Duration: 300
required property int index
Behavior on idx1 {
NumberAnimation {
duration: root.idx1Duration
easing.type: Easing.OutSine
}
}
Behavior on idx2 {
NumberAnimation {
duration: root.idx2Duration
easing.type: Easing.OutSine
}
}
}
+165
View File
@@ -0,0 +1,165 @@
import QtQuick
import QtQuick.Templates
import qs.Config
Slider {
id: root
property color color: DynamicColors.palette.m3secondary
required property string icon
property bool initialized: false
readonly property bool isHorizontal: orientation === Qt.Horizontal
readonly property bool isVertical: orientation === Qt.Vertical
property real multiplier: 100
property real oldValue
// Wrapper components can inject their own track visuals here.
property Component trackContent
// Keep current behavior for existing usages.
orientation: Qt.Vertical
background: CustomRect {
id: groove
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, 2)
height: root.availableHeight
radius: Appearance.rounding.full
width: root.availableWidth
x: root.leftPadding
y: root.topPadding
Loader {
id: trackLoader
anchors.fill: parent
sourceComponent: root.trackContent
onLoaded: {
if (!item)
return;
item.rootSlider = root;
item.groove = groove;
item.handleItem = handle;
}
}
}
handle: Item {
id: handle
property alias moving: icon.moving
implicitHeight: Math.min(root.width, root.height)
implicitWidth: Math.min(root.width, root.height)
x: root.isHorizontal ? root.leftPadding + root.visualPosition * (root.availableWidth - width) : root.leftPadding + (root.availableWidth - width) / 2
y: root.isVertical ? root.topPadding + root.visualPosition * (root.availableHeight - height) : root.topPadding + (root.availableHeight - height) / 2
Elevation {
anchors.fill: parent
level: handleInteraction.containsMouse ? 2 : 1
radius: rect.radius
}
CustomRect {
id: rect
anchors.fill: parent
color: DynamicColors.palette.m3inverseSurface
radius: Appearance.rounding.full
MouseArea {
id: handleInteraction
acceptedButtons: Qt.NoButton
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
}
MaterialIcon {
id: icon
property bool moving
function update(): void {
animate = !moving;
binding.when = moving;
font.pointSize = moving ? Appearance.font.size.small : Appearance.font.size.larger;
font.family = moving ? Appearance.font.family.sans : Appearance.font.family.material;
}
anchors.centerIn: parent
color: DynamicColors.palette.m3inverseOnSurface
text: root.icon
onMovingChanged: anim.restart()
Binding {
id: binding
property: "text"
target: icon
value: Math.round(root.value * root.multiplier)
when: false
}
SequentialAnimation {
id: anim
Anim {
duration: Appearance.anim.durations.normal / 2
easing.bezierCurve: Appearance.anim.curves.standardAccel
property: "scale"
target: icon
to: 0
}
ScriptAction {
script: icon.update()
}
Anim {
duration: Appearance.anim.durations.normal / 2
easing.bezierCurve: Appearance.anim.curves.standardDecel
property: "scale"
target: icon
to: 1
}
}
}
}
}
Behavior on value {
Anim {
duration: Appearance.anim.durations.large
}
}
onPressedChanged: handle.moving = pressed
onValueChanged: {
if (!initialized) {
initialized = true;
oldValue = value;
return;
}
if (Math.abs(value - oldValue) < 0.01)
return;
oldValue = value;
handle.moving = true;
stateChangeDelay.restart();
}
Timer {
id: stateChangeDelay
interval: 500
onTriggered: {
if (!root.pressed)
handle.moving = false;
}
}
}
+208
View File
@@ -0,0 +1,208 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.Config
Item {
id: root
readonly property real arcStartAngle: 0.75 * Math.PI
readonly property real arcSweep: 1.5 * Math.PI
property real currentHue: 0
property bool dragActive: false
required property var drawing
readonly property real handleAngle: hueToAngle(currentHue)
readonly property real handleCenterX: width / 2 + radius * Math.cos(handleAngle)
readonly property real handleCenterY: height / 2 + radius * Math.sin(handleAngle)
property real handleSize: 32
property real lastChromaticHue: 0
readonly property real radius: (Math.min(width, height) - handleSize) / 2
readonly property int segmentCount: 240
readonly property color thumbColor: DynamicColors.palette.m3inverseSurface
readonly property color thumbContentColor: DynamicColors.palette.m3inverseOnSurface
readonly property color trackColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, 2)
function hueToAngle(hue) {
return arcStartAngle + arcSweep * hue;
}
function normalizeAngle(angle) {
const tau = Math.PI * 2;
let a = angle % tau;
if (a < 0)
a += tau;
return a;
}
function pointIsOnTrack(x, y) {
const cx = width / 2;
const cy = height / 2;
const dx = x - cx;
const dy = y - cy;
const distance = Math.sqrt(dx * dx + dy * dy);
return distance >= radius - handleSize / 2 && distance <= radius + handleSize / 2;
}
function syncFromPenColor() {
if (!drawing)
return;
const c = drawing.penColor;
if (c.hsvSaturation > 0) {
currentHue = c.hsvHue;
lastChromaticHue = c.hsvHue;
} else {
currentHue = lastChromaticHue;
}
canvas.requestPaint();
}
function updateHueFromPoint(x, y, force = false) {
const cx = width / 2;
const cy = height / 2;
const dx = x - cx;
const dy = y - cy;
const distance = Math.sqrt(dx * dx + dy * dy);
if (!force && (distance < radius - handleSize / 2 || distance > radius + handleSize / 2))
return;
const angle = normalizeAngle(Math.atan2(dy, dx));
const start = normalizeAngle(arcStartAngle);
let relative = angle - start;
if (relative < 0)
relative += Math.PI * 2;
if (relative > arcSweep) {
const gap = Math.PI * 2 - arcSweep;
relative = relative < arcSweep + gap / 2 ? arcSweep : 0;
}
currentHue = relative / arcSweep;
lastChromaticHue = currentHue;
drawing.penColor = Qt.hsva(currentHue, drawing.penColor.hsvSaturation, drawing.penColor.hsvValue, drawing.penColor.a);
}
implicitHeight: 180
implicitWidth: 220
Component.onCompleted: syncFromPenColor()
onCurrentHueChanged: canvas.requestPaint()
onDrawingChanged: syncFromPenColor()
onHandleSizeChanged: canvas.requestPaint()
onHeightChanged: canvas.requestPaint()
onWidthChanged: canvas.requestPaint()
Connections {
function onPenColorChanged() {
root.syncFromPenColor();
}
target: root.drawing
}
Canvas {
id: canvas
anchors.fill: parent
renderStrategy: Canvas.Threaded
renderTarget: Canvas.Image
Component.onCompleted: requestPaint()
onPaint: {
const ctx = getContext("2d");
ctx.reset();
ctx.clearRect(0, 0, width, height);
const cx = width / 2;
const cy = height / 2;
const radius = root.radius;
const trackWidth = root.handleSize;
// Background track: always show the full hue spectrum
for (let i = 0; i < root.segmentCount; ++i) {
const t1 = i / root.segmentCount;
const t2 = (i + 1) / root.segmentCount;
const a1 = root.arcStartAngle + root.arcSweep * t1;
const a2 = root.arcStartAngle + root.arcSweep * t2;
ctx.beginPath();
ctx.arc(cx, cy, radius, a1, a2);
ctx.lineWidth = trackWidth;
ctx.lineCap = "round";
ctx.strokeStyle = Qt.hsla(t1, 1.0, 0.5, 1.0);
ctx.stroke();
}
}
}
Item {
id: handle
height: root.handleSize
width: root.handleSize
x: root.handleCenterX - width / 2
y: root.handleCenterY - height / 2
z: 1
Elevation {
anchors.fill: parent
level: handleHover.containsMouse ? 2 : 1
radius: rect.radius
}
Rectangle {
id: rect
anchors.fill: parent
color: root.thumbColor
radius: width / 2
MouseArea {
id: handleHover
acceptedButtons: Qt.NoButton
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
}
Rectangle {
anchors.centerIn: parent
color: root.drawing ? root.drawing.penColor : Qt.hsla(root.currentHue, 1.0, 0.5, 1.0)
height: width
radius: width / 2
width: parent.width - 12
}
}
}
MouseArea {
id: dragArea
acceptedButtons: Qt.LeftButton
anchors.fill: parent
hoverEnabled: true
onCanceled: {
root.dragActive = false;
}
onPositionChanged: mouse => {
if ((mouse.buttons & Qt.LeftButton) && root.dragActive)
root.updateHueFromPoint(mouse.x, mouse.y, true);
}
onPressed: mouse => {
root.dragActive = root.pointIsOnTrack(mouse.x, mouse.y);
if (root.dragActive)
root.updateHueFromPoint(mouse.x, mouse.y);
}
onReleased: {
root.dragActive = false;
}
}
}
+3 -2
View File
@@ -1,12 +1,13 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import qs.Config
Button { Button {
id: control id: control
required property color bgColor property color bgColor: DynamicColors.palette.m3primary
property int radius: 4 property int radius: 4
required property color textColor property color textColor: DynamicColors.palette.m3onPrimary
background: CustomRect { background: CustomRect {
color: control.bgColor color: control.bgColor
+169
View File
@@ -0,0 +1,169 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import qs.Config
ComboBox {
id: root
property int cornerRadius: Appearance.rounding.normal
property int fieldHeight: 42
property bool filled: true
property real focusRingOpacity: 0.70
property int hPadding: 16
property int menuCornerRadius: 16
property int menuRowHeight: 46
property int menuVisibleRows: 7
property bool preferPopupWindow: false
hoverEnabled: true
implicitHeight: fieldHeight
implicitWidth: 240
spacing: 8
// ---------- Field background (filled/outlined + state layers + focus ring) ----------
background: Item {
anchors.fill: parent
CustomRect {
id: container
anchors.fill: parent
color: DynamicColors.palette.m3surfaceVariant
radius: root.cornerRadius
StateLayer {
}
}
}
// ---------- Content ----------
contentItem: RowLayout {
anchors.fill: parent
anchors.leftMargin: root.hPadding
anchors.rightMargin: root.hPadding
spacing: 12
// Display text
CustomText {
Layout.fillWidth: true
color: root.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSurfaceVariant
elide: Text.ElideRight
font.pixelSize: 16
font.weight: Font.Medium
text: root.currentText
verticalAlignment: Text.AlignVCenter
}
// Indicator chevron (simple, replace with your icon system)
CustomText {
color: root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurfaceVariant
rotation: root.popup.visible ? 180 : 0
text: "▾"
transformOrigin: Item.Center
verticalAlignment: Text.AlignVCenter
Behavior on rotation {
NumberAnimation {
duration: 140
easing.type: Easing.OutCubic
}
}
}
}
popup: Popup {
id: p
implicitHeight: list.contentItem.height + Appearance.padding.small * 2
implicitWidth: root.width
modal: true
popupType: root.preferPopupWindow ? Popup.Window : Popup.Item
y: -list.currentIndex * (root.menuRowHeight + Appearance.spacing.small) - Appearance.padding.small
background: CustomRect {
color: DynamicColors.palette.m3surface
radius: root.menuCornerRadius
}
contentItem: ListView {
id: list
anchors.bottomMargin: Appearance.padding.small
anchors.fill: parent
anchors.topMargin: Appearance.padding.small
clip: true
currentIndex: root.currentIndex
model: root.delegateModel
spacing: Appearance.spacing.small
delegate: CustomRect {
required property int index
required property var modelData
anchors.horizontalCenter: parent.horizontalCenter
color: (index === root.currentIndex) ? DynamicColors.palette.m3primary : "transparent"
implicitHeight: root.menuRowHeight
implicitWidth: p.implicitWidth - Appearance.padding.small * 2
radius: Appearance.rounding.normal - Appearance.padding.small
RowLayout {
anchors.fill: parent
spacing: 10
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurface
elide: Text.ElideRight
font.pixelSize: 15
text: modelData
verticalAlignment: Text.AlignVCenter
}
CustomText {
color: DynamicColors.palette.m3onSurfaceVariant
text: "✓"
verticalAlignment: Text.AlignVCenter
visible: index === root.currentIndex
}
}
StateLayer {
onClicked: {
root.currentIndex = index;
p.close();
}
}
}
}
// Expressive-ish open/close motion: subtle scale+fade (tune to taste). :contentReference[oaicite:5]{index=5}
enter: Transition {
Anim {
from: 0
property: "opacity"
to: 1
}
Anim {
from: 0.98
property: "scale"
to: 1.0
}
}
exit: Transition {
Anim {
from: 1
property: "opacity"
to: 0
}
}
Elevation {
anchors.fill: parent
level: 2
radius: root.menuCornerRadius
z: -1
}
}
}
+1 -1
View File
@@ -5,7 +5,7 @@ import qs.Config
RadioButton { RadioButton {
id: root id: root
font.pointSize: 12 font.pointSize: Appearance.font.size.normal
implicitHeight: Math.max(implicitIndicatorHeight, implicitContentHeight) implicitHeight: Math.max(implicitIndicatorHeight, implicitContentHeight)
implicitWidth: implicitIndicatorWidth + implicitContentWidth + contentItem.anchors.leftMargin implicitWidth: implicitIndicatorWidth + implicitContentWidth + contentItem.anchors.leftMargin
+6 -3
View File
@@ -28,6 +28,7 @@ RowLayout {
CustomTextField { CustomTextField {
id: textField id: textField
implicitHeight: upButton.implicitHeight
inputMethodHints: Qt.ImhFormattedNumbersOnly inputMethodHints: Qt.ImhFormattedNumbersOnly
leftPadding: Appearance.padding.normal leftPadding: Appearance.padding.normal
padding: Appearance.padding.small padding: Appearance.padding.small
@@ -37,7 +38,7 @@ RowLayout {
background: CustomRect { background: CustomRect {
color: DynamicColors.tPalette.m3surfaceContainerHigh color: DynamicColors.tPalette.m3surfaceContainerHigh
implicitWidth: 100 implicitWidth: 100
radius: Appearance.rounding.small radius: Appearance.rounding.full
} }
validator: DoubleValidator { validator: DoubleValidator {
bottom: root.min bottom: root.min
@@ -82,10 +83,12 @@ RowLayout {
} }
CustomRect { CustomRect {
id: upButton
color: DynamicColors.palette.m3primary 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.small radius: Appearance.rounding.full
StateLayer { StateLayer {
id: upState id: upState
@@ -119,7 +122,7 @@ RowLayout {
color: DynamicColors.palette.m3primary 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.small radius: Appearance.rounding.full
StateLayer { StateLayer {
id: downState id: downState
+181
View File
@@ -0,0 +1,181 @@
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Helpers
Row {
id: root
enum Type {
Filled,
Tonal
}
property alias active: menu.active
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 disabledTextColor: Qt.alpha(DynamicColors.palette.m3onSurface, 0.38)
property alias expanded: menu.expanded
property string fallbackIcon
property string fallbackText
property real horizontalPadding: Appearance.padding.normal
property alias iconLabel: iconLabel
property alias label: label
property alias menu: menu
property alias menuItems: menu.items
property bool menuOnTop
property alias stateLayer: stateLayer
property color textColor: type == CustomSplitButton.Filled ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSecondaryContainer
property int type: CustomSplitButton.Filled
property real verticalPadding: Appearance.padding.smaller
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 {
bottomRightRadius: Appearance.rounding.small / 2
color: root.disabled ? root.disabledColor : root.color
implicitHeight: expandBtn.implicitHeight
implicitWidth: textRow.implicitWidth + root.horizontalPadding * 2
radius: implicitHeight / 2 * Math.min(1, Appearance.rounding.scale)
topRightRadius: Appearance.rounding.small / 2
StateLayer {
id: stateLayer
function onClicked(): void {
root.active?.clicked();
}
color: root.textColor
disabled: root.disabled
rect.bottomRightRadius: parent.bottomRightRadius
rect.topRightRadius: parent.topRightRadius
}
RowLayout {
id: textRow
anchors.centerIn: parent
anchors.horizontalCenterOffset: Math.floor(root.verticalPadding / 4)
spacing: Appearance.spacing.small
MaterialIcon {
id: iconLabel
Layout.alignment: Qt.AlignVCenter
animate: true
color: root.disabled ? root.disabledTextColor : root.textColor
fill: 1
text: root.active?.activeIcon ?? root.fallbackIcon
}
CustomText {
id: label
Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: implicitWidth
animate: true
clip: true
color: root.disabled ? root.disabledTextColor : root.textColor
text: root.active?.activeText ?? root.fallbackText
Behavior on Layout.preferredWidth {
Anim {
easing.bezierCurve: Appearance.anim.curves.emphasized
}
}
}
}
}
CustomRect {
id: expandBtn
property real rad: root.expanded ? implicitHeight / 2 * Math.min(1, Appearance.rounding.scale) : Appearance.rounding.small / 2
bottomLeftRadius: rad
color: root.disabled ? root.disabledColor : root.color
implicitHeight: expandIcon.implicitHeight + root.verticalPadding * 2
implicitWidth: implicitHeight
radius: implicitHeight / 2 * Math.min(1, Appearance.rounding.scale)
topLeftRadius: rad
Behavior on rad {
Anim {
}
}
StateLayer {
id: expandStateLayer
function onClicked(): void {
root.toggleDropdown();
}
color: root.textColor
disabled: root.disabled
rect.bottomLeftRadius: parent.bottomLeftRadius
rect.topLeftRadius: parent.topLeftRadius
}
MaterialIcon {
id: expandIcon
anchors.centerIn: parent
anchors.horizontalCenterOffset: root.expanded ? 0 : -Math.floor(root.verticalPadding / 4)
color: root.disabled ? root.disabledTextColor : root.textColor
rotation: root.expanded ? 180 : 0
text: "expand_more"
Behavior on anchors.horizontalCenterOffset {
Anim {
}
}
Behavior on rotation {
Anim {
}
}
}
Menu {
id: menu
anchors.bottomMargin: Appearance.spacing.small
anchors.right: parent.right
anchors.top: parent.bottom
anchors.topMargin: Appearance.spacing.small
states: State {
when: root.menuOnTop
AnchorChanges {
anchors.bottom: expandBtn.top
anchors.top: undefined
target: menu
}
}
}
}
}
+58
View File
@@ -0,0 +1,58 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Config
Item {
id: root
property alias active: splitButton.active
property bool enabled: true
property alias expanded: splitButton.expanded
property int expandedZ: 100
required property string label
property alias menuItems: splitButton.menuItems
property alias type: splitButton.type
signal selected(item: MenuItem)
Layout.fillWidth: true
Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2
clip: false
z: root.expanded ? expandedZ : -1
RowLayout {
id: row
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.normal
CustomText {
Layout.fillWidth: true
color: root.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.larger
text: root.label
z: root.expanded ? root.expandedZ : -1
}
CustomSplitButton {
id: splitButton
enabled: root.enabled
type: CustomSplitButton.Filled
z: root.expanded ? root.expandedZ : -1
menu.onItemSelected: item => {
root.selected(item);
splitButton.closeDropdown();
}
stateLayer.onClicked: {
splitButton.toggleDropdown();
}
}
}
}
+15
View File
@@ -0,0 +1,15 @@
import QtQuick
import QtQuick.Controls
import qs.Config
TextInput {
renderType: Text.NativeRendering
selectedTextColor: DynamicColors.palette.m3onSecondaryContainer
selectionColor: DynamicColors.tPalette.colSecondaryContainer
font {
family: Appearance?.font.family.sans ?? "sans-serif"
hintingPreference: Font.PreferFullHinting
pixelSize: Appearance?.font.size.normal ?? 15
}
}
+9
View File
@@ -0,0 +1,9 @@
import Quickshell
import Quickshell.Wayland
PanelWindow {
required property string name
WlrLayershell.namespace: `ZShell-${name}`
color: "transparent"
}
+16 -128
View File
@@ -1,141 +1,29 @@
import QtQuick import QtQuick
import QtQuick.Templates
import qs.Config import qs.Config
Slider { BaseStyledSlider {
id: root id: root
property color color: DynamicColors.palette.m3secondary trackContent: Component {
required property string icon Item {
property bool initialized property var groove
property real oldValue readonly property real handleHeight: handleItem ? handleItem.height : 0
property var handleItem
readonly property real handleWidth: handleItem ? handleItem.width : 0
orientation: Qt.Vertical // Set by BaseStyledSlider's Loader
property var rootSlider
background: CustomRect { anchors.fill: parent
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, 2)
radius: Appearance.rounding.full
CustomRect { CustomRect {
anchors.left: parent.left color: rootSlider?.color
anchors.right: parent.right height: rootSlider?.isVertical ? handleHeight + (1 - rootSlider?.visualPosition) * (groove?.height - handleHeight) : groove?.height
color: root.color radius: groove?.radius
implicitHeight: parent.height - y width: rootSlider?.isHorizontal ? handleWidth + rootSlider?.visualPosition * (groove?.width - handleWidth) : groove?.width
radius: parent.radius x: rootSlider?.isHorizontal ? (rootSlider?.mirrored ? groove?.width - width : 0) : 0
y: root.handle.y y: rootSlider?.isVertical ? groove?.height - height : 0
}
}
handle: Item {
id: handle
property alias moving: icon.moving
implicitHeight: root.width
implicitWidth: root.width
y: root.visualPosition * (root.availableHeight - height)
Elevation {
anchors.fill: parent
level: handleInteraction.containsMouse ? 2 : 1
radius: rect.radius
}
CustomRect {
id: rect
anchors.fill: parent
color: DynamicColors.palette.m3inverseSurface
radius: Appearance.rounding.full
MouseArea {
id: handleInteraction
acceptedButtons: Qt.NoButton
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
}
MaterialIcon {
id: icon
property bool moving
function update(): void {
animate = !moving;
binding.when = moving;
font.pointSize = moving ? Appearance.font.size.small : Appearance.font.size.larger;
font.family = moving ? Appearance.font.family.sans : Appearance.font.family.material;
}
anchors.centerIn: parent
color: DynamicColors.palette.m3inverseOnSurface
text: root.icon
onMovingChanged: anim.restart()
Binding {
id: binding
property: "text"
target: icon
value: Math.round(root.value * 100)
when: false
}
SequentialAnimation {
id: anim
Anim {
duration: Appearance.anim.durations.normal / 2
easing.bezierCurve: Appearance.anim.curves.standardAccel
property: "scale"
target: icon
to: 0
}
ScriptAction {
script: icon.update()
}
Anim {
duration: Appearance.anim.durations.normal / 2
easing.bezierCurve: Appearance.anim.curves.standardDecel
property: "scale"
target: icon
to: 1
} }
} }
} }
} }
}
Behavior on value {
Anim {
duration: Appearance.anim.durations.large
}
}
onPressedChanged: handle.moving = pressed
onValueChanged: {
if (!initialized) {
initialized = true;
return;
}
if (Math.abs(value - oldValue) < 0.01)
return;
oldValue = value;
handle.moving = true;
stateChangeDelay.restart();
}
Timer {
id: stateChangeDelay
interval: 500
onTriggered: {
if (!root.pressed)
handle.moving = false;
}
}
}
+47
View File
@@ -0,0 +1,47 @@
import QtQuick
import qs.Config
BaseStyledSlider {
id: root
property real alpha: 1.0
property real brightness: 1.0
property string channel: "saturation"
readonly property color currentColor: Qt.hsva(hue, channel === "saturation" ? value : saturation, channel === "brightness" ? value : brightness, alpha)
property real hue: 0.0
property real saturation: 1.0
from: 0
to: 1
trackContent: Component {
Item {
property var groove
property var handleItem
property var rootSlider
anchors.fill: parent
Rectangle {
anchors.fill: parent
antialiasing: true
color: "transparent"
radius: groove?.radius ?? 0
gradient: Gradient {
orientation: rootSlider?.isHorizontal ? Gradient.Horizontal : Gradient.Vertical
GradientStop {
color: root.channel === "saturation" ? Qt.hsva(root.hue, 0.0, root.brightness, root.alpha) : Qt.hsva(root.hue, root.saturation, 0.0, root.alpha)
position: 0.0
}
GradientStop {
color: root.channel === "saturation" ? Qt.hsva(root.hue, 1.0, root.brightness, root.alpha) : Qt.hsva(root.hue, root.saturation, 1.0, root.alpha)
position: 1.0
}
}
}
}
}
}
+1 -1
View File
@@ -5,7 +5,7 @@ CustomText {
property int grade: DynamicColors.light ? 0 : -25 property int grade: DynamicColors.light ? 0 : -25
font.family: "Material Symbols Rounded" font.family: "Material Symbols Rounded"
font.pointSize: 15 font.pointSize: Appearance.font.size.larger
font.variableAxes: ({ font.variableAxes: ({
FILL: fill.toFixed(1), FILL: fill.toFixed(1),
GRAD: grade, GRAD: grade,
+12 -4
View File
@@ -13,11 +13,11 @@ Elevation {
signal itemSelected(item: MenuItem) signal itemSelected(item: MenuItem)
implicitHeight: root.expanded ? column.implicitHeight : 0 implicitHeight: root.expanded ? column.implicitHeight + Appearance.padding.small * 2 : 0
implicitWidth: Math.max(200, column.implicitWidth) implicitWidth: Math.max(200, column.implicitWidth)
level: 2 level: 2
opacity: root.expanded ? 1 : 0 opacity: root.expanded ? 1 : 0
radius: Appearance.rounding.small / 2 radius: Appearance.rounding.normal
Behavior on implicitHeight { Behavior on implicitHeight {
Anim { Anim {
@@ -41,7 +41,8 @@ Elevation {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
spacing: 0 anchors.verticalCenter: parent.verticalCenter
spacing: 5
Repeater { Repeater {
model: root.items model: root.items
@@ -54,10 +55,16 @@ Elevation {
required property MenuItem modelData required property MenuItem modelData
Layout.fillWidth: true Layout.fillWidth: true
color: Qt.alpha(DynamicColors.palette.m3secondaryContainer, active ? 1 : 0)
implicitHeight: menuOptionRow.implicitHeight + Appearance.padding.normal * 2 implicitHeight: menuOptionRow.implicitHeight + Appearance.padding.normal * 2
implicitWidth: menuOptionRow.implicitWidth + Appearance.padding.normal * 2 implicitWidth: menuOptionRow.implicitWidth + Appearance.padding.normal * 2
CustomRect {
anchors.fill: parent
anchors.leftMargin: Appearance.padding.small
anchors.rightMargin: Appearance.padding.small
color: Qt.alpha(DynamicColors.palette.m3secondaryContainer, active ? 1 : 0)
radius: Appearance.rounding.normal - Appearance.padding.small
StateLayer { StateLayer {
function onClicked(): void { function onClicked(): void {
root.itemSelected(item.modelData); root.itemSelected(item.modelData);
@@ -68,6 +75,7 @@ Elevation {
color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
disabled: !root.expanded disabled: !root.expanded
} }
}
RowLayout { RowLayout {
id: menuOptionRow id: menuOptionRow
+76
View File
@@ -0,0 +1,76 @@
import QtQuick
Path {
id: root
required property real viewHeight
required property real viewWidth
startX: root.viewWidth / 2
startY: 0
PathAttribute {
name: "itemOpacity"
value: 0.25
}
PathLine {
x: root.viewWidth / 2
y: root.viewHeight * (1 / 6)
}
PathAttribute {
name: "itemOpacity"
value: 0.45
}
PathLine {
x: root.viewWidth / 2
y: root.viewHeight * (2 / 6)
}
PathAttribute {
name: "itemOpacity"
value: 0.70
}
PathLine {
x: root.viewWidth / 2
y: root.viewHeight * (3 / 6)
}
PathAttribute {
name: "itemOpacity"
value: 1.00
}
PathLine {
x: root.viewWidth / 2
y: root.viewHeight * (4 / 6)
}
PathAttribute {
name: "itemOpacity"
value: 0.70
}
PathLine {
x: root.viewWidth / 2
y: root.viewHeight * (5 / 6)
}
PathAttribute {
name: "itemOpacity"
value: 0.45
}
PathLine {
x: root.viewWidth / 2
y: root.viewHeight
}
PathAttribute {
name: "itemOpacity"
value: 0.25
}
}
+178
View File
@@ -0,0 +1,178 @@
import QtQuick
import QtQuick.Effects
import qs.Config
Elevation {
id: root
required property int currentIndex
property bool expanded
required property int from
property color insideTextColor: DynamicColors.palette.m3onPrimary
property int itemHeight
property int listHeight: 200
property color outsideTextColor: DynamicColors.palette.m3onSurfaceVariant
readonly property var spinnerModel: root.range(root.from, root.to)
required property int to
property Item triggerItem
signal itemSelected(item: int)
function range(first, last) {
let out = [];
for (let i = first; i <= last; ++i)
out.push(i);
return out;
}
implicitHeight: root.expanded ? view.implicitHeight : 0
level: root.expanded ? 2 : 0
radius: itemHeight / 2
visible: implicitHeight > 0
Behavior on implicitHeight {
Anim {
}
}
onExpandedChanged: {
if (!root.expanded)
root.itemSelected(view.currentIndex + 1);
}
Component {
id: spinnerDelegate
Item {
id: wrapper
readonly property color delegateTextColor: wrapper.PathView.view ? wrapper.PathView.view.delegateTextColor : "white"
required property var modelData
height: root.itemHeight
opacity: wrapper.PathView.itemOpacity
visible: wrapper.PathView.onPath
width: wrapper.PathView.view ? wrapper.PathView.view.width : 0
z: wrapper.PathView.isCurrentItem ? 100 : Math.round(wrapper.PathView.itemScale * 100)
CustomText {
anchors.centerIn: parent
color: wrapper.delegateTextColor
font.pointSize: Appearance.font.size.large
text: wrapper.modelData
}
}
}
CustomClippingRect {
anchors.fill: parent
color: DynamicColors.palette.m3surfaceContainer
radius: parent.radius
// Main visible spinner: normal/outside text color
PathView {
id: view
property color delegateTextColor: root.outsideTextColor
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
currentIndex: root.currentIndex - 1
delegate: spinnerDelegate
dragMargin: width
highlightRangeMode: PathView.StrictlyEnforceRange
implicitHeight: root.listHeight
model: root.spinnerModel
pathItemCount: 7
preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
snapMode: PathView.SnapToItem
path: PathMenu {
viewHeight: view.height
viewWidth: view.width
}
}
// The selection rectangle itself
CustomRect {
id: selectionRect
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3primary
height: root.itemHeight
radius: root.itemHeight / 2
width: parent.width
z: 2
}
// Hidden source: same PathView, but with the "inside selection" text color
Item {
id: selectedTextSource
anchors.fill: parent
layer.enabled: true
visible: false
PathView {
id: selectedTextView
property color delegateTextColor: root.insideTextColor
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
currentIndex: view.currentIndex
delegate: spinnerDelegate
dragMargin: view.dragMargin
highlightRangeMode: view.highlightRangeMode
implicitHeight: root.listHeight
interactive: false
model: view.model
// Keep this PathView visually locked to the real one
offset: view.offset
pathItemCount: view.pathItemCount
preferredHighlightBegin: view.preferredHighlightBegin
preferredHighlightEnd: view.preferredHighlightEnd
snapMode: view.snapMode
path: PathMenu {
viewHeight: selectedTextView.height
viewWidth: selectedTextView.width
}
}
}
// Mask matching the selection rectangle
Item {
id: selectionMask
anchors.fill: parent
layer.enabled: true
visible: false
CustomRect {
color: "white"
height: selectionRect.height
radius: selectionRect.radius
width: selectionRect.width
x: selectionRect.x
y: selectionRect.y
}
}
// Only show the "inside selection" text where the mask exists
MultiEffect {
anchors.fill: selectedTextSource
maskEnabled: true
maskInverted: false
maskSource: selectionMask
source: selectedTextSource
z: 3
}
}
}
+2
View File
@@ -71,6 +71,7 @@ JsonObject {
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
} }
component Rounding: JsonObject { component Rounding: JsonObject {
property int full: 1000 * scale property int full: 1000 * scale
@@ -78,6 +79,7 @@ JsonObject {
property int normal: 17 * scale property int normal: 17 * scale
property real scale: 1 property real scale: 1
property int small: 12 * scale property int small: 12 * scale
property int smallest: 8 * scale
} }
component Spacing: JsonObject { component Spacing: JsonObject {
property int large: 20 * scale property int large: 20 * scale
+2
View File
@@ -2,6 +2,7 @@ import Quickshell.Io
JsonObject { JsonObject {
property bool autoHide: false property bool autoHide: false
property int border: 8
property list<var> entries: [ property list<var> entries: [
{ {
id: "workspaces", id: "workspaces",
@@ -60,6 +61,7 @@ JsonObject {
enabled: true enabled: true
}, },
] ]
property int height: 34
property Popouts popouts: Popouts { property Popouts popouts: Popouts {
} }
property int rounding: 8 property int rounding: 8
+27 -4
View File
@@ -15,6 +15,7 @@ Singleton {
property alias barConfig: adapter.barConfig property alias barConfig: adapter.barConfig
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 general: adapter.general property alias general: adapter.general
property alias launcher: adapter.launcher property alias launcher: adapter.launcher
property alias lock: adapter.lock property alias lock: adapter.lock
@@ -32,6 +33,10 @@ Singleton {
recentSaveCooldown.restart(); recentSaveCooldown.restart();
} }
function saveNoToast(): void {
saveTimer.restart();
}
function serializeAppearance(): var { function serializeAppearance(): var {
return { return {
rounding: { rounding: {
@@ -55,8 +60,8 @@ Singleton {
} }
}, },
anim: { anim: {
mediaGifSpeedAdjustment: 300, mediaGifSpeedAdjustment: appearance.anim.mediaGifSpeedAdjustment,
sessionGifSpeed: 0.7, sessionGifSpeed: appearance.anim.sessionGifSpeed,
durations: { durations: {
scale: appearance.anim.durations.scale scale: appearance.anim.durations.scale
} }
@@ -80,6 +85,8 @@ Singleton {
return { return {
autoHide: barConfig.autoHide, autoHide: barConfig.autoHide,
rounding: barConfig.rounding, rounding: barConfig.rounding,
border: barConfig.border,
height: barConfig.height,
popouts: { popouts: {
tray: barConfig.popouts.tray, tray: barConfig.popouts.tray,
audio: barConfig.popouts.audio, audio: barConfig.popouts.audio,
@@ -113,7 +120,8 @@ Singleton {
osd: serializeOsd(), osd: serializeOsd(),
background: serializeBackground(), background: serializeBackground(),
launcher: serializeLauncher(), launcher: serializeLauncher(),
colors: serializeColors() colors: serializeColors(),
dock: serializeDock()
}; };
} }
@@ -149,13 +157,26 @@ Singleton {
}; };
} }
function serializeDock(): var {
return {
enable: dock.enable,
height: dock.height,
hoverToReveal: dock.hoverToReveal,
pinnedApps: dock.pinnedApps,
pinnedOnStartup: dock.pinnedOnStartup,
ignoredAppRegexes: dock.ignoredAppRegexes
};
}
function serializeGeneral(): var { function serializeGeneral(): var {
return { return {
logo: general.logo, logo: general.logo,
wallpaperPath: general.wallpaperPath, wallpaperPath: general.wallpaperPath,
desktopIcons: general.desktopIcons,
color: { color: {
wallust: general.color.wallust, wallust: general.color.wallust,
mode: general.color.mode, mode: general.color.mode,
smart: general.color.smart,
schemeGeneration: general.color.schemeGeneration, schemeGeneration: general.color.schemeGeneration,
scheduleDarkStart: general.color.scheduleDarkStart, scheduleDarkStart: general.color.scheduleDarkStart,
scheduleDarkEnd: general.color.scheduleDarkEnd, scheduleDarkEnd: general.color.scheduleDarkEnd,
@@ -168,7 +189,7 @@ Singleton {
explorer: general.apps.explorer explorer: general.apps.explorer
}, },
idle: { idle: {
timouts: general.idle.timeouts timeouts: general.idle.timeouts
} }
}; };
} }
@@ -381,6 +402,8 @@ Singleton {
} }
property DashboardConfig dashboard: DashboardConfig { property DashboardConfig dashboard: DashboardConfig {
} }
property DockConfig dock: DockConfig {
}
property General general: General { property General general: General {
} }
property Launcher launcher: Launcher { property Launcher launcher: Launcher {
+10
View File
@@ -0,0 +1,10 @@
import Quickshell.Io
JsonObject {
property bool enable: false
property real height: 60
property bool hoverToReveal: true
property list<string> ignoredAppRegexes: []
property list<string> pinnedApps: ["org.kde.dolphin", "kitty",]
property bool pinnedOnStartup: false
}
+6
View File
@@ -78,6 +78,12 @@ Singleton {
return Qt.hsla(c.hslHue, c.hslSaturation, 0.1, 1); return Qt.hsla(c.hslHue, c.hslSaturation, 0.1, 1);
} }
function setMode(mode: string): void {
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", mode]);
Config.general.color.mode = mode;
Config.save();
}
FileView { FileView {
path: `${Paths.state}/scheme.json` path: `${Paths.state}/scheme.json`
watchChanges: true watchChanges: true
+4
View File
@@ -6,6 +6,7 @@ JsonObject {
} }
property Color color: Color { property Color color: Color {
} }
property bool desktopIcons: false
property Idle idle: Idle { property Idle idle: Idle {
} }
property string logo: "" property string logo: ""
@@ -23,15 +24,18 @@ JsonObject {
property int scheduleDarkEnd: 0 property int scheduleDarkEnd: 0
property int scheduleDarkStart: 0 property int scheduleDarkStart: 0
property bool schemeGeneration: true property bool schemeGeneration: true
property bool smart: false
property bool wallust: false property bool wallust: false
} }
component Idle: JsonObject { component Idle: JsonObject {
property list<var> timeouts: [ property list<var> timeouts: [
{ {
name: "Lock",
timeout: 180, timeout: 180,
idleAction: "lock" idleAction: "lock"
}, },
{ {
name: "Screen",
timeout: 300, timeout: 300,
idleAction: "dpms off", idleAction: "dpms off",
activeAction: "dpms on" activeAction: "dpms on"
+26 -2
View File
@@ -6,11 +6,27 @@ JsonObject {
{ {
name: "Calculator", name: "Calculator",
icon: "calculate", icon: "calculate",
description: "Do simple math equations (powered by Qalc)", description: "Do simple math equations",
command: ["autocomplete", "calc"], command: ["autocomplete", "calc"],
enabled: true, enabled: true,
dangerous: false dangerous: false
}, },
{
name: "Light",
icon: "light_mode",
description: "Change to light mode",
command: ["setMode", "light"],
enabled: true,
dangerous: false
},
{
name: "Dark",
icon: "dark_mode",
description: "Change to dark mode",
command: ["setMode", "dark"],
enabled: true,
dangerous: false
},
{ {
name: "Wallpaper", name: "Wallpaper",
icon: "image", icon: "image",
@@ -19,6 +35,14 @@ JsonObject {
enabled: true, enabled: true,
dangerous: false dangerous: false
}, },
{
name: "Variant",
icon: "colors",
description: "Change the current scheme variant",
command: ["autocomplete", "variant"],
enabled: true,
dangerous: false
},
{ {
name: "Shutdown", name: "Shutdown",
icon: "power_settings_new", icon: "power_settings_new",
@@ -37,7 +61,7 @@ JsonObject {
}, },
{ {
name: "Logout", name: "Logout",
icon: "exit_to_app", icon: "logout",
description: "Log out of the current session", description: "Log out of the current session",
command: ["loginctl", "terminate-user", ""], command: ["loginctl", "terminate-user", ""],
enabled: true, enabled: true,
+27 -15
View File
@@ -11,8 +11,9 @@ import qs.Modules.Dashboard as Dashboard
import qs.Modules.Osd as Osd import qs.Modules.Osd as Osd
import qs.Modules.Launcher as Launcher import qs.Modules.Launcher as Launcher
import qs.Modules.Resources as Resources import qs.Modules.Resources as Resources
import qs.Modules.Drawing as Drawing
// import qs.Modules.Settings as Settings import qs.Modules.Settings as Settings
import qs.Modules.Dock as Dock
Shape { Shape {
id: root id: root
@@ -22,13 +23,15 @@ Shape {
required property PersistentProperties visibilities required property PersistentProperties visibilities
anchors.fill: parent anchors.fill: parent
// anchors.margins: 8 anchors.margins: Config.barConfig.border
anchors.topMargin: Config.barConfig.autoHide && !visibilities.bar ? 0 : bar.implicitHeight anchors.topMargin: bar.implicitHeight
asynchronous: true
preferredRendererType: Shape.CurveRenderer preferredRendererType: Shape.CurveRenderer
Behavior on anchors.topMargin { Drawing.Background {
Anim { startX: 0
} startY: wrapper.y - rounding
wrapper: root.panels.drawing
} }
Resources.Background { Resources.Background {
@@ -45,7 +48,8 @@ Shape {
Modules.Background { Modules.Background {
invertBottomRounding: wrapper.x <= 0 invertBottomRounding: wrapper.x <= 0
startX: wrapper.x - 8 rounding: root.panels.popouts.currentName.startsWith("updates") ? Appearance.rounding.normal : Appearance.rounding.smallest
startX: wrapper.x - rounding
startY: wrapper.y startY: wrapper.y
wrapper: root.panels.popouts wrapper: root.panels.popouts
} }
@@ -85,11 +89,19 @@ Shape {
wrapper: root.panels.sidebar wrapper: root.panels.sidebar
} }
// Settings.Background { Settings.Background {
// id: settings id: settings
//
// startX: (root.width - wrapper.width) / 2 - rounding startX: (root.width - wrapper.width) / 2 - rounding
// startY: 0 startY: 0
// wrapper: root.panels.settings wrapper: root.panels.settings
// } }
Dock.Background {
id: dock
startX: (root.width - wrapper.width) / 2 - rounding
startY: root.height
wrapper: root.panels.dock
}
} }
+186
View File
@@ -0,0 +1,186 @@
import QtQuick
Canvas {
id: root
property rect dirtyRect: Qt.rect(0, 0, 0, 0)
property bool frameQueued: false
property bool fullRepaintPending: true
property point lastPoint: Qt.point(0, 0)
property real minPointDistance: 2.0
property color penColor: "white"
property real penWidth: 4
property var pendingSegments: []
property bool strokeActive: false
property var strokes: []
function appendPoint(x, y) {
if (!strokeActive || strokes.length === 0)
return;
const dx = x - lastPoint.x;
const dy = y - lastPoint.y;
if ((dx * dx + dy * dy) < (minPointDistance * minPointDistance))
return;
const x1 = lastPoint.x;
const y1 = lastPoint.y;
const x2 = x;
const y2 = y;
strokes[strokes.length - 1].push(Qt.point(x2, y2));
pendingSegments.push({
dot: false,
x1: x1,
y1: y1,
x2: x2,
y2: y2
});
lastPoint = Qt.point(x2, y2);
queueDirty(segmentDirtyRect(x1, y1, x2, y2));
}
function beginStroke(x, y) {
const p = Qt.point(x, y);
strokes.push([p]);
lastPoint = p;
strokeActive = true;
pendingSegments.push({
dot: true,
x: x,
y: y
});
queueDirty(pointDirtyRect(x, y));
}
function clear() {
strokes = [];
pendingSegments = [];
dirtyRect = Qt.rect(0, 0, 0, 0);
fullRepaintPending = true;
markDirty(Qt.rect(0, 0, width, height));
}
function drawDot(ctx, x, y) {
ctx.beginPath();
ctx.arc(x, y, penWidth / 2, 0, Math.PI * 2);
ctx.fill();
}
function drawSegment(ctx, x1, y1, x2, y2) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
function endStroke() {
strokeActive = false;
}
function pointDirtyRect(x, y) {
const pad = penWidth + 2;
return Qt.rect(x - pad, y - pad, pad * 2, pad * 2);
}
function queueDirty(r) {
dirtyRect = unionRects(dirtyRect, r);
if (frameQueued)
return;
frameQueued = true;
requestAnimationFrame(function () {
frameQueued = false;
if (dirtyRect.width > 0 && dirtyRect.height > 0) {
markDirty(dirtyRect);
dirtyRect = Qt.rect(0, 0, 0, 0);
}
});
}
function replayAll(ctx) {
ctx.clearRect(0, 0, width, height);
for (const stroke of strokes) {
if (!stroke || stroke.length === 0)
continue;
if (stroke.length === 1) {
const p = stroke[0];
drawDot(ctx, p.x, p.y);
continue;
}
ctx.beginPath();
ctx.moveTo(stroke[0].x, stroke[0].y);
for (let i = 1; i < stroke.length; ++i)
ctx.lineTo(stroke[i].x, stroke[i].y);
ctx.stroke();
}
}
function requestFullRepaint() {
fullRepaintPending = true;
markDirty(Qt.rect(0, 0, width, height));
}
function segmentDirtyRect(x1, y1, x2, y2) {
const pad = penWidth + 2;
const left = Math.min(x1, x2) - pad;
const top = Math.min(y1, y2) - pad;
const right = Math.max(x1, x2) + pad;
const bottom = Math.max(y1, y2) + pad;
return Qt.rect(left, top, right - left, bottom - top);
}
function unionRects(a, b) {
if (a.width <= 0 || a.height <= 0)
return b;
if (b.width <= 0 || b.height <= 0)
return a;
const left = Math.min(a.x, b.x);
const top = Math.min(a.y, b.y);
const right = Math.max(a.x + a.width, b.x + b.width);
const bottom = Math.max(a.y + a.height, b.y + b.height);
return Qt.rect(left, top, right - left, bottom - top);
}
anchors.fill: parent
contextType: "2d"
renderStrategy: Canvas.Threaded
renderTarget: Canvas.Image
onHeightChanged: requestFullRepaint()
onPaint: region => {
const ctx = getContext("2d");
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.lineWidth = penWidth;
ctx.strokeStyle = penColor;
ctx.fillStyle = penColor;
if (fullRepaintPending) {
fullRepaintPending = false;
replayAll(ctx);
pendingSegments = [];
return;
}
for (const seg of pendingSegments) {
if (seg.dot)
drawDot(ctx, seg.x, seg.y);
else
drawSegment(ctx, seg.x1, seg.y1, seg.x2, seg.y2);
}
pendingSegments = [];
}
onWidthChanged: requestFullRepaint()
}
+58
View File
@@ -0,0 +1,58 @@
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
anchors.fill: root.visibilities.isDrawing ? parent : undefined
hoverEnabled: true
visible: root.visibilities.isDrawing
onPositionChanged: event => {
const x = event.x;
const y = event.y;
if (event.buttons & Qt.LeftButton)
root.drawing.appendPoint(x, y);
if (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.beginStroke(x, y);
return;
}
if (event.buttons & Qt.RightButton)
root.drawing.clear();
}
onReleased: {
if (root.visibilities.isDrawing)
root.drawing.endStroke();
}
}
+41
View File
@@ -0,0 +1,41 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import qs.Config
import qs.Components
Scope {
id: root
required property Item bar
required property ShellScreen screen
ExclusionZone {
anchors.top: true
exclusiveZone: root.bar.exclusiveZone
}
ExclusionZone {
anchors.left: true
}
ExclusionZone {
anchors.right: true
}
ExclusionZone {
anchors.bottom: true
}
component ExclusionZone: CustomWindow {
exclusiveZone: Config.barConfig.border
implicitHeight: 1
implicitWidth: 1
name: "Bar-Exclusion"
screen: root.screen
mask: Region {
}
}
}
+49 -115
View File
@@ -10,6 +10,8 @@ CustomMouseArea {
required property Item bar required property Item bar
property bool dashboardShortcutActive property bool dashboardShortcutActive
property point dragStart property point dragStart
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
@@ -18,15 +20,15 @@ CustomMouseArea {
required property PersistentProperties visibilities required property PersistentProperties visibilities
function inBottomPanel(panel: Item, x: real, y: real): bool { function inBottomPanel(panel: Item, x: real, y: real): bool {
return y > root.height - panel.height && withinPanelWidth(panel, x, y); return y > root.height - panel.height - Config.barConfig.border && withinPanelWidth(panel, x, y);
} }
function inLeftPanel(panel: Item, x: real, y: real): bool { function inLeftPanel(panel: Item, x: real, y: real): bool {
return x < panel.x + panel.width && withinPanelHeight(panel, x, y); return x < panel.x + panel.width + Config.barConfig.border && withinPanelHeight(panel, x, y);
} }
function inRightPanel(panel: Item, x: real, y: real): bool { function inRightPanel(panel: Item, x: real, y: real): bool {
return x > panel.x && withinPanelHeight(panel, x, y); return x > panel.x - Config.barConfig.border && withinPanelHeight(panel, x, y);
} }
function inTopPanel(panel: Item, x: real, y: real): bool { function inTopPanel(panel: Item, x: real, y: real): bool {
@@ -51,20 +53,10 @@ CustomMouseArea {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
propagateComposedEvents: true
// onPressed: event => {
// if ( root.popouts.hasCurrent && !inTopPanel( root.popouts, event.x, event.y )) {
// root.popouts.hasCurrent = false;
// } else if (root.visibilities.sidebar && !inRightPanel( panels.sidebar, event.x, event.y )) {
// root.visibilities.sidebar = false;
// } else if (root.visibilities.dashboard && !inTopPanel( panels.dashboard, event.x, event.y )) {
// root.visibilities.dashboard = false;
// }
// }
onContainsMouseChanged: { onContainsMouseChanged: {
if (!containsMouse) { if (!containsMouse) {
// Only hide if not activated by shortcut
if (!osdShortcutActive) { if (!osdShortcutActive) {
visibilities.osd = false; visibilities.osd = false;
root.panels.osd.hovered = false; root.panels.osd.hovered = false;
@@ -74,8 +66,8 @@ CustomMouseArea {
popouts.hasCurrent = false; popouts.hasCurrent = false;
} }
if (Config.barConfig.autoHide && !root.visibilities.sidebar && !root.visibilities.dashboard) if (Config.barConfig.autoHide)
root.visibilities.bar = false; bar.isHovered = false;
} }
} }
onPositionChanged: event => { onPositionChanged: event => {
@@ -87,168 +79,112 @@ CustomMouseArea {
const dragX = x - dragStart.x; const dragX = x - dragStart.x;
const dragY = y - dragStart.y; const dragY = y - dragStart.y;
// Show bar in non-exclusive mode on hover if (root.visibilities.isDrawing && !root.inLeftPanel(root.panels.drawing, x, y)) {
if (!visibilities.bar && Config.barConfig.autoHide && y < bar.implicitHeight + bar.anchors.topMargin) root.input.z = 2;
visibilities.bar = true; root.panels.drawing.expanded = false;
}
if (!visibilities.bar && Config.barConfig.autoHide && y < bar.implicitHeight)
bar.isHovered = true;
if (panels.sidebar.width === 0) { if (panels.sidebar.width === 0) {
// Show osd on hover
const showOsd = inRightPanel(panels.osd, x, y); const showOsd = inRightPanel(panels.osd, x, y);
// // Always update visibility based on hover if not in shortcut mode if (showOsd) {
if (!osdShortcutActive) {
visibilities.osd = showOsd;
root.panels.osd.hovered = showOsd;
} else if (showOsd) {
// If hovering over OSD area while in shortcut mode, transition to hover control
osdShortcutActive = false; osdShortcutActive = false;
root.panels.osd.hovered = true; root.panels.osd.hovered = true;
} }
// const showSidebar = pressed && dragStart.x > bar.implicitWidth + panels.sidebar.x;
//
// // Show/hide session on drag
// if (pressed && inRightPanel(panels.session, dragStart.x, dragStart.y) && withinPanelHeight(panels.session, x, y)) {
// if (dragX < -Config.session.dragThreshold)
// visibilities.session = true;
// else if (dragX > Config.session.dragThreshold)
// visibilities.session = false;
//
// // Show sidebar on drag if in session area and session is nearly fully visible
// if (showSidebar && panels.session.width >= panels.session.nonAnimWidth && dragX < -Config.sidebar.dragThreshold)
// visibilities.sidebar = true;
// } else if (showSidebar && dragX < -Config.sidebar.dragThreshold) {
// // Show sidebar on drag if not in session area
// visibilities.sidebar = true;
// }
} else { } else {
const outOfSidebar = x < width - panels.sidebar.width; const outOfSidebar = x < width - panels.sidebar.width;
// Show osd on hover
const showOsd = outOfSidebar && inRightPanel(panels.osd, x, y); const showOsd = outOfSidebar && inRightPanel(panels.osd, x, y);
// Always update visibility based on hover if not in shortcut mode
if (!osdShortcutActive) { if (!osdShortcutActive) {
visibilities.osd = showOsd; visibilities.osd = showOsd;
root.panels.osd.hovered = showOsd; root.panels.osd.hovered = showOsd;
} else if (showOsd) { } else if (showOsd) {
// If hovering over OSD area while in shortcut mode, transition to hover control
osdShortcutActive = false; osdShortcutActive = false;
root.panels.osd.hovered = true; root.panels.osd.hovered = true;
} }
//
// // Show/hide session on drag
// if (pressed && outOfSidebar && inRightPanel(panels.session, dragStart.x, dragStart.y) && withinPanelHeight(panels.session, x, y)) {
// if (dragX < -Config.session.dragThreshold)
// visibilities.session = true;
// else if (dragX > Config.session.dragThreshold)
// visibilities.session = false;
// }
//
// // Hide sidebar on drag
// if (pressed && inRightPanel(panels.sidebar, dragStart.x, 0) && dragX > Config.sidebar.dragThreshold)
// visibilities.sidebar = false;
} }
// Show launcher on hover, or show/hide on drag if hover is disabled if (!visibilities.dock && !visibilities.launcher && inBottomPanel(panels.dock, x, y))
// if (Config.launcher.showOnHover) { visibilities.dock = true;
// if (!visibilities.launcher && inBottomPanel(panels.launcher, x, y))
// visibilities.launcher = true;
// } else if (pressed && inBottomPanel(panels.launcher, dragStart.x, dragStart.y) && withinPanelWidth(panels.launcher, x, y)) {
// if (dragY < -Config.launcher.dragThreshold)
// visibilities.launcher = true;
// else if (dragY > Config.launcher.dragThreshold)
// visibilities.launcher = false;
// }
//
// // Show dashboard on hover
// const showDashboard = Config.dashboard.showOnHover && inTopPanel(panels.dashboard, x, y);
//
// // Always update visibility based on hover if not in shortcut mode
// if (!dashboardShortcutActive) {
// visibilities.dashboard = showDashboard;
// } else if (showDashboard) {
// // If hovering over dashboard area while in shortcut mode, transition to hover control
// dashboardShortcutActive = false;
// }
//
// // Show/hide dashboard on drag (for touchscreen devices)
// if (pressed && inTopPanel(panels.dashboard, dragStart.x, dragStart.y) && withinPanelWidth(panels.dashboard, x, y)) {
// if (dragY > Config.dashboard.dragThreshold)
// visibilities.dashboard = true;
// else if (dragY < -Config.dashboard.dragThreshold)
// visibilities.dashboard = false;
// }
//
// // Show utilities on hover
// const showUtilities = inBottomPanel(panels.utilities, x, y);
//
// // Always update visibility based on hover if not in shortcut mode
// if (!utilitiesShortcutActive) {
// visibilities.utilities = showUtilities;
// } else if (showUtilities) {
// // If hovering over utilities area while in shortcut mode, transition to hover control
// utilitiesShortcutActive = false;
// }
// Show popouts on hover if (y < root.bar.implicitHeight) {
if (y < bar.implicitHeight) { root.bar.checkPopout(x);
bar.checkPopout(x);
} }
} }
// Monitor individual visibility changes
Connections { Connections {
function onDashboardChanged() { function onDashboardChanged() {
if (root.visibilities.dashboard) { if (root.visibilities.dashboard) {
// Dashboard became visible, immediately check if this should be shortcut mode
const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY); const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY);
if (!inDashboardArea) { if (!inDashboardArea) {
root.dashboardShortcutActive = true; root.dashboardShortcutActive = true;
} }
root.visibilities.settings = false;
root.visibilities.sidebar = false; root.visibilities.sidebar = false;
root.popouts.hasCurrent = false; root.popouts.hasCurrent = false;
} else { } else {
// Dashboard hidden, clear shortcut flag
root.dashboardShortcutActive = false; root.dashboardShortcutActive = false;
// root.visibilities.bar = false;
} }
} }
function onIsDrawingChanged() {
if (!root.visibilities.isDrawing)
root.drawing.clear();
}
function onLauncherChanged() { function onLauncherChanged() {
// If launcher is hidden, clear shortcut flags for dashboard and OSD
if (!root.visibilities.launcher) { if (!root.visibilities.launcher) {
root.dashboardShortcutActive = false; root.dashboardShortcutActive = false;
root.osdShortcutActive = false; root.osdShortcutActive = false;
root.utilitiesShortcutActive = false; root.utilitiesShortcutActive = false;
// Also hide dashboard and OSD if they're not being hovered
const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY);
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY); const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
if (!inDashboardArea) {
root.visibilities.dashboard = false;
}
if (!inOsdArea) { if (!inOsdArea) {
root.visibilities.osd = false; root.visibilities.osd = false;
root.panels.osd.hovered = false; root.panels.osd.hovered = false;
} }
} }
if (root.visibilities.launcher) {
root.visibilities.dock = false;
root.visibilities.settings = false;
}
} }
function onOsdChanged() { function onOsdChanged() {
if (root.visibilities.osd) { if (root.visibilities.osd) {
// OSD became visible, immediately check if this should be shortcut mode
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY); const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
if (!inOsdArea) { if (!inOsdArea) {
root.osdShortcutActive = true; root.osdShortcutActive = true;
} }
} else { } else {
// OSD hidden, clear shortcut flag
root.osdShortcutActive = false; root.osdShortcutActive = false;
} }
} }
function onResourcesChanged() {
if (root.visibilities.resources && root.popouts.currentName.startsWith("audio")) {
root.popouts.hasCurrent = false;
}
if (root.visibilities.resources)
root.visibilities.settings = false;
}
function onSettingsChanged() {
if (root.visibilities.settings) {
root.visibilities.resources = false;
root.visibilities.dashboard = false;
root.panels.popouts.hasCurrent = false;
root.visibilities.launcher = false;
}
}
function onSidebarChanged() { function onSidebarChanged() {
if (root.visibilities.sidebar) { if (root.visibilities.sidebar) {
root.visibilities.dashboard = false; root.visibilities.dashboard = false;
@@ -258,13 +194,11 @@ CustomMouseArea {
function onUtilitiesChanged() { function onUtilitiesChanged() {
if (root.visibilities.utilities) { if (root.visibilities.utilities) {
// Utilities became visible, immediately check if this should be shortcut mode
const inUtilitiesArea = root.inBottomPanel(root.panels.utilities, root.mouseX, root.mouseY); const inUtilitiesArea = root.inBottomPanel(root.panels.utilities, root.mouseX, root.mouseY);
if (!inUtilitiesArea) { if (!inUtilitiesArea) {
root.utilitiesShortcutActive = true; root.utilitiesShortcutActive = true;
} }
} else { } else {
// Utilities hidden, clear shortcut flag
root.utilitiesShortcutActive = false; root.utilitiesShortcutActive = false;
} }
} }
+38 -17
View File
@@ -10,7 +10,9 @@ import qs.Modules.Osd as Osd
import qs.Components.Toast as Toasts import qs.Components.Toast as Toasts
import qs.Modules.Launcher as Launcher import qs.Modules.Launcher as Launcher
import qs.Modules.Resources as Resources 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.Dock as Dock
import qs.Config import qs.Config
Item { Item {
@@ -18,26 +20,24 @@ Item {
required property Item bar required property Item bar
readonly property alias dashboard: dashboard readonly property alias dashboard: dashboard
readonly property alias dock: dock
readonly property alias drawing: drawing
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
readonly property alias popouts: popouts readonly property alias popouts: popouts
readonly property alias resources: resources readonly property alias resources: resources
required property ShellScreen screen required property ShellScreen screen
// readonly property alias settings: settings readonly property alias settings: settings
readonly property alias sidebar: sidebar readonly property alias sidebar: sidebar
readonly property alias toasts: toasts readonly property alias toasts: toasts
readonly property alias utilities: utilities readonly property alias utilities: utilities
required property PersistentProperties visibilities required property PersistentProperties visibilities
anchors.fill: parent anchors.fill: parent
// anchors.margins: 8 anchors.margins: Config.barConfig.border
anchors.topMargin: Config.barConfig.autoHide && !visibilities.bar ? 0 : bar.implicitHeight anchors.topMargin: bar.implicitHeight
Behavior on anchors.topMargin {
Anim {
}
}
Resources.Wrapper { Resources.Wrapper {
id: resources id: resources
@@ -47,6 +47,16 @@ Item {
visibilities: root.visibilities visibilities: root.visibilities
} }
Drawing.Wrapper {
id: drawing
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
drawing: root.drawingItem
screen: root.screen
visibilities: root.visibilities
}
Osd.Wrapper { Osd.Wrapper {
id: osd id: osd
@@ -127,12 +137,23 @@ Item {
visibilities: root.visibilities visibilities: root.visibilities
} }
// Settings.Wrapper { Settings.Wrapper {
// id: settings id: settings
//
// anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
// anchors.top: parent.top anchors.top: parent.top
// panels: root panels: root
// visibilities: root.visibilities screen: root.screen
// } visibilities: root.visibilities
}
Dock.Wrapper {
id: dock
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
panels: root
screen: root.screen
visibilities: root.visibilities
}
} }
+64 -65
View File
@@ -21,30 +21,25 @@ Variants {
required property var modelData required property var modelData
PanelWindow { Exclusions {
id: bar bar: bar
screen: scope.modelData
}
CustomWindow {
id: win
readonly property bool hasFullscreen: Hypr.monitorFor(screen)?.activeWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen === 2)
property var root: Quickshell.shellDir property var root: Quickshell.shellDir
property bool trayMenuVisible: false
WlrLayershell.exclusionMode: ExclusionMode.Ignore WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.keyboardFocus: visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.resources ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None WlrLayershell.keyboardFocus: visibilities.dock || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.resources ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
WlrLayershell.namespace: "ZShell-Bar"
color: "transparent" color: "transparent"
contentItem.focus: true contentItem.focus: true
mask: visibilities.isDrawing ? null : region
name: "Bar"
screen: scope.modelData screen: scope.modelData
mask: Region {
id: region
height: bar.screen.height - backgroundRect.implicitHeight
intersection: Intersection.Xor
regions: popoutRegions.instances
width: bar.width
x: 0
y: Config.barConfig.autoHide && !visibilities.bar ? 4 : 34
}
contentItem.Keys.onEscapePressed: { contentItem.Keys.onEscapePressed: {
if (Config.barConfig.autoHide) if (Config.barConfig.autoHide)
visibilities.bar = false; visibilities.bar = false;
@@ -54,22 +49,23 @@ Variants {
visibilities.settings = false; visibilities.settings = false;
visibilities.resources = false; visibilities.resources = false;
} }
onHasFullscreenChanged: {
PanelWindow { visibilities.launcher = false;
id: exclusionZone visibilities.dashboard = false;
visibilities.osd = false;
WlrLayershell.exclusionMode: Config.barConfig.autoHide ? ExclusionMode.Ignore : ExclusionMode.Auto visibilities.settings = false;
WlrLayershell.layer: WlrLayer.Bottom visibilities.resources = false;
WlrLayershell.namespace: "ZShell-Bar-Exclusion"
color: "transparent"
implicitHeight: 34
screen: bar.screen
anchors {
left: true
right: true
top: true
} }
Region {
id: region
height: win.height - bar.implicitHeight - Config.barConfig.border
intersection: Intersection.Xor
regions: popoutRegions.instances
width: win.width - Config.barConfig.border * 2
x: Config.barConfig.border
y: bar.implicitHeight
} }
anchors { anchors {
@@ -90,16 +86,16 @@ Variants {
height: modelData.height height: modelData.height
intersection: Intersection.Subtract intersection: Intersection.Subtract
width: modelData.width width: modelData.width
x: modelData.x x: modelData.x + Config.barConfig.border
y: modelData.y + backgroundRect.implicitHeight y: modelData.y + bar.implicitHeight
} }
} }
HyprlandFocusGrab { HyprlandFocusGrab {
id: focusGrab id: focusGrab
active: visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu")) active: visibilities.dock || visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu"))
windows: [bar] windows: [win]
onCleared: { onCleared: {
visibilities.launcher = false; visibilities.launcher = false;
@@ -108,6 +104,7 @@ Variants {
visibilities.osd = false; visibilities.osd = false;
visibilities.settings = false; visibilities.settings = false;
visibilities.resources = false; visibilities.resources = false;
visibilities.dock = false;
panels.popouts.hasCurrent = false; panels.popouts.hasCurrent = false;
} }
} }
@@ -117,6 +114,8 @@ Variants {
property bool bar property bool bar
property bool dashboard property bool dashboard
property bool dock
property bool isDrawing
property bool launcher property bool launcher
property bool notif: NotifServer.popups.length > 0 property bool notif: NotifServer.popups.length > 0
property bool osd property bool osd
@@ -130,7 +129,7 @@ Variants {
Binding { Binding {
property: "bar" property: "bar"
target: visibilities target: visibilities
value: visibilities.sidebar || visibilities.dashboard || visibilities.osd || visibilities.notif || visibilities.resources value: visibilities.sidebar || visibilities.dashboard || visibilities.osd || visibilities.notif || visibilities.resources || visibilities.settings || bar.isHovered
when: Config.barConfig.autoHide when: Config.barConfig.autoHide
} }
@@ -146,62 +145,63 @@ Variants {
} }
Border { Border {
bar: backgroundRect bar: bar
visibilities: visibilities visibilities: visibilities
} }
Backgrounds { Backgrounds {
bar: backgroundRect bar: bar
panels: panels panels: panels
visibilities: visibilities visibilities: visibilities
z: 1
} }
} }
Drawing {
id: drawing
anchors.fill: parent
z: 2
}
DrawingInput {
id: input
bar: bar
drawing: drawing
panels: panels
popout: panels.drawing
visibilities: visibilities
z: 2
}
Interactions { Interactions {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
bar: barLoader bar: bar
drawing: drawing
input: input
panels: panels panels: panels
popouts: panels.popouts popouts: panels.popouts
screen: scope.modelData screen: scope.modelData
visibilities: visibilities visibilities: visibilities
z: 1
Panels { Panels {
id: panels id: panels
bar: backgroundRect bar: bar
drawingItem: drawing
screen: scope.modelData screen: scope.modelData
visibilities: visibilities visibilities: visibilities
} }
CustomRect { BarLoader {
id: backgroundRect id: bar
property Wrapper popouts: panels.popouts
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: Config.barConfig.autoHide && !visibilities.bar ? -30 : 0
color: "transparent"
implicitHeight: 34
radius: 0
Behavior on anchors.topMargin {
Anim {
}
}
Behavior on color {
CAnim {
}
}
BarLoader {
id: barLoader
anchors.fill: parent
bar: bar
popouts: panels.popouts popouts: panels.popouts
screen: scope.modelData screen: scope.modelData
visibilities: visibilities visibilities: visibilities
@@ -210,4 +210,3 @@ Variants {
} }
} }
} }
}
+150
View File
@@ -0,0 +1,150 @@
pragma Singleton
import Quickshell
import Quickshell.Io
import "../scripts/levendist.js" as Levendist
import "../scripts/fuzzysort.js" as Fuzzy
import qs.Config
Singleton {
id: root
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 => ({
name: Fuzzy.prepare(`${a.name} `),
entry: a
}))
property var regexSubstitutions: [
{
"regex": /^steam_app_(\d+)$/,
"replace": "steam_icon_$1"
},
{
"regex": /Minecraft.*/,
"replace": "minecraft"
},
{
"regex": /.*polkit.*/,
"replace": "system-lock-screen"
},
{
"regex": /gcr.prompter/,
"replace": "system-lock-screen"
}
]
readonly property real scoreGapThreshold: 0.1
readonly property real scoreThreshold: 0.6
property var substitutions: ({
"code-url-handler": "visual-studio-code",
"Code": "visual-studio-code",
"gnome-tweaks": "org.gnome.tweaks",
"pavucontrol-qt": "pavucontrol",
"wps": "wps-office2019-kprometheus",
"wpsoffice": "wps-office2019-kprometheus",
"footclient": "foot"
})
function bestFuzzyEntry(search: string, preppedList: list<var>, key: string): var {
const results = Fuzzy.go(search, preppedList, {
key: key,
threshold: root.scoreThreshold,
limit: 2
});
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) {
if (!str || str.length == 0)
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])
return substitutions[str];
if (substitutions[str.toLowerCase()])
return substitutions[str.toLowerCase()];
for (let i = 0; i < regexSubstitutions.length; i++) {
const substitution = regexSubstitutions[i];
const replacedName = str.replace(substitution.regex, substitution.replace);
if (replacedName != str)
return replacedName;
}
const lowercased = str.toLowerCase();
if (iconExists(lowercased))
return lowercased;
const reverseDomainNameAppName = getReverseDomainNameAppName(str);
if (iconExists(reverseDomainNameAppName))
return reverseDomainNameAppName;
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) {
if (!iconName || iconName.length == 0)
return false;
return (Quickshell.iconPath(iconName, true).length > 0) && !iconName.includes("image-missing");
}
}
+201
View File
@@ -0,0 +1,201 @@
pragma Singleton
import Quickshell
Singleton {
id: root
function getAppId(fileName) {
return fileName.endsWith(".desktop") ? fileName.replace(".desktop", "") : null;
}
function getFileType(fileName, isDir) {
if (isDir)
return "directory";
let ext = fileName.includes('.') ? fileName.split('.').pop().toLowerCase() : "";
if (ext === "desktop")
return "desktop";
const map = {
"image": ["png", "jpg", "jpeg", "svg", "gif", "bmp", "webp", "ico", "tiff", "tif", "heic", "heif", "raw", "psd", "ai", "xcf"],
"video": ["mp4", "mkv", "webm", "avi", "mov", "flv", "wmv", "m4v", "mpg", "mpeg", "3gp", "vob", "ogv", "ts"],
"audio": ["mp3", "wav", "flac", "aac", "ogg", "m4a", "wma", "opus", "alac", "mid", "midi", "amr"],
"archive": ["zip", "tar", "gz", "rar", "7z", "xz", "bz2", "tgz", "iso", "img", "dmg", "deb", "rpm", "apk"],
"document": ["pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "odt", "ods", "odp", "rtf", "epub", "mobi", "djvu"],
"text": ["txt", "md", "rst", "tex", "log", "json", "xml", "yaml", "yml", "toml", "ini", "conf", "cfg", "env", "csv", "tsv"],
"code": ["qml", "cpp", "c", "h", "hpp", "py", "js", "ts", "jsx", "tsx", "java", "rs", "go", "rb", "php", "cs", "swift", "kt", "sh", "bash", "zsh", "fish", "html", "htm", "css", "scss", "sass", "less", "vue", "svelte", "sql", "graphql", "lua", "pl", "dart", "r", "dockerfile", "make"],
"executable": ["exe", "msi", "bat", "cmd", "appimage", "run", "bin", "out", "so", "dll"],
"font": ["ttf", "otf", "woff", "woff2"]
};
for (const [type, extensions] of Object.entries(map)) {
if (extensions.includes(ext))
return type;
}
return "unknown";
}
function getIconName(fileName, isDir) {
if (isDir)
return "folder";
let ext = fileName.includes('.') ? fileName.split('.').pop().toLowerCase() : "";
const map = {
// Images
"png": "image-x-generic",
"jpg": "image-x-generic",
"jpeg": "image-x-generic",
"svg": "image-svg+xml",
"gif": "image-x-generic",
"bmp": "image-x-generic",
"webp": "image-x-generic",
"ico": "image-x-generic",
"tiff": "image-x-generic",
"tif": "image-x-generic",
"heic": "image-x-generic",
"heif": "image-x-generic",
"raw": "image-x-generic",
"psd": "image-vnd.adobe.photoshop",
"ai": "application-illustrator",
"xcf": "image-x-xcf",
// Vidéos
"mp4": "video-x-generic",
"mkv": "video-x-generic",
"webm": "video-x-generic",
"avi": "video-x-generic",
"mov": "video-x-generic",
"flv": "video-x-generic",
"wmv": "video-x-generic",
"m4v": "video-x-generic",
"mpg": "video-x-generic",
"mpeg": "video-x-generic",
"3gp": "video-x-generic",
"vob": "video-x-generic",
"ogv": "video-x-generic",
"ts": "video-x-generic",
// Audio
"mp3": "audio-x-generic",
"wav": "audio-x-generic",
"flac": "audio-x-generic",
"aac": "audio-x-generic",
"ogg": "audio-x-generic",
"m4a": "audio-x-generic",
"wma": "audio-x-generic",
"opus": "audio-x-generic",
"alac": "audio-x-generic",
"mid": "audio-midi",
"midi": "audio-midi",
"amr": "audio-x-generic",
// Archives & Images
"zip": "application-zip",
"tar": "application-x-tar",
"gz": "application-gzip",
"rar": "application-vnd.rar",
"7z": "application-x-7z-compressed",
"xz": "application-x-xz",
"bz2": "application-x-bzip2",
"tgz": "application-x-compressed-tar",
"iso": "application-x-cd-image",
"img": "application-x-cd-image",
"dmg": "application-x-apple-diskimage",
"deb": "application-vnd.debian.binary-package",
"rpm": "application-x-rpm",
"apk": "application-vnd.android.package-archive",
// Documents
"pdf": "application-pdf",
"doc": "application-msword",
"docx": "application-vnd.openxmlformats-officedocument.wordprocessingml.document",
"xls": "application-vnd.ms-excel",
"xlsx": "application-vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"ppt": "application-vnd.ms-powerpoint",
"pptx": "application-vnd.openxmlformats-officedocument.presentationml.presentation",
"odt": "application-vnd.oasis.opendocument.text",
"ods": "application-vnd.oasis.opendocument.spreadsheet",
"odp": "application-vnd.oasis.opendocument.presentation",
"rtf": "application-rtf",
"epub": "application-epub+zip",
"mobi": "application-x-mobipocket-ebook",
"djvu": "image-vnd.djvu",
"csv": "text-csv",
"tsv": "text-tab-separated-values",
// Data & Config
"txt": "text-x-generic",
"md": "text-markdown",
"rst": "text-x-rst",
"tex": "text-x-tex",
"log": "text-x-log",
"json": "application-json",
"xml": "text-xml",
"yaml": "text-x-yaml",
"yml": "text-x-yaml",
"toml": "text-x-toml",
"ini": "text-x-generic",
"conf": "text-x-generic",
"cfg": "text-x-generic",
"env": "text-x-generic",
// Code
"qml": "text-x-qml",
"cpp": "text-x-c++src",
"c": "text-x-csrc",
"h": "text-x-chdr",
"hpp": "text-x-c++hdr",
"py": "text-x-python",
"js": "text-x-javascript",
"ts": "text-x-typescript",
"jsx": "text-x-javascript",
"tsx": "text-x-typescript",
"java": "text-x-java",
"rs": "text-x-rust",
"go": "text-x-go",
"rb": "text-x-ruby",
"php": "application-x-php",
"cs": "text-x-csharp",
"swift": "text-x-swift",
"kt": "text-x-kotlin",
"sh": "application-x-shellscript",
"bash": "application-x-shellscript",
"zsh": "application-x-shellscript",
"fish": "application-x-shellscript",
"html": "text-html",
"htm": "text-html",
"css": "text-css",
"scss": "text-x-scss",
"sass": "text-x-sass",
"less": "text-x-less",
"vue": "text-html",
"svelte": "text-html",
"sql": "application-x-sql",
"graphql": "text-x-generic",
"lua": "text-x-lua",
"pl": "text-x-perl",
"dart": "text-x-dart",
"r": "text-x-r",
"dockerfile": "text-x-generic",
"make": "text-x-makefile",
// Executables
"exe": "application-x-executable",
"msi": "application-x-msi",
"bat": "application-x-ms-dos-executable",
"cmd": "application-x-ms-dos-executable",
"appimage": "application-x-executable",
"run": "application-x-executable",
"bin": "application-x-executable",
"out": "application-x-executable",
"so": "application-x-sharedlib",
"dll": "application-x-sharedlib",
// Fonts
"ttf": "font-x-generic",
"otf": "font-x-generic",
"woff": "font-x-generic",
"woff2": "font-x-generic"
};
return map[ext] || "text-x-generic";
}
}
+70
View File
@@ -0,0 +1,70 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Components
import qs.Config
Singleton {
id: root
property list<var> apps: {
var map = new Map();
const pinnedApps = Config.dock?.pinnedApps ?? [];
for (const appId of pinnedApps) {
if (!map.has(appId.toLowerCase()))
map.set(appId.toLowerCase(), ({
pinned: true,
toplevels: []
}));
}
if (pinnedApps.length > 0) {
map.set("SEPARATOR", {
pinned: false,
toplevels: []
});
}
var values = [];
for (const [key, value] of map) {
values.push(appEntryComp.createObject(null, {
appId: key,
toplevels: value.toplevels,
pinned: value.pinned
}));
}
return values;
}
function isPinned(appId) {
return Config.dock.pinnedApps.indexOf(appId) !== -1;
}
function togglePin(appId) {
if (root.isPinned(appId)) {
Config.dock.pinnedApps = Config.dock.pinnedApps.filter(id => id !== appId);
} else {
Config.dock.pinnedApps = Config.dock.pinnedApps.concat([appId]);
}
}
Component {
id: appEntryComp
TaskbarAppEntry {
}
}
component TaskbarAppEntry: QtObject {
id: wrapper
required property string appId
required property bool pinned
required property list<var> toplevels
}
}
+28
View File
@@ -0,0 +1,28 @@
pragma Singleton
import Quickshell
Singleton {
id: root
function fileNameForPath(str) {
if (typeof str !== "string")
return "";
const trimmed = trimFileProtocol(str);
return trimmed.split(/[\\/]/).pop();
}
function trimFileExt(str) {
if (typeof str !== "string")
return "";
const trimmed = trimFileProtocol(str);
const lastDot = trimmed.lastIndexOf(".");
if (lastDot > -1 && lastDot > trimmed.lastIndexOf("/")) {
return trimmed.slice(0, lastDot);
}
return trimmed;
}
function trimFileProtocol(str) {
return str.startsWith("file://") ? str.slice(7) : str;
}
}
+1 -1
View File
@@ -33,7 +33,7 @@ Singleton {
} }
function applyLightMode() { function applyLightMode() {
if (Config.general.color.neovimColors) { if (Config.general.color.schemeGeneration) {
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${WallpaperPath.currentWallpaperPath}`, "--thumbnail-path", `${Paths.cache}/imagecache/thumbnail.jpg`, "--output", `${Paths.state}/scheme.json`, "--scheme", `${Config.colors.schemeType}`, "--mode", "light"]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${WallpaperPath.currentWallpaperPath}`, "--thumbnail-path", `${Paths.cache}/imagecache/thumbnail.jpg`, "--output", `${Paths.state}/scheme.json`, "--scheme", `${Config.colors.schemeType}`, "--mode", "light"]);
} else { } else {
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${DynamicColors.scheme}:${DynamicColors.flavour}`, "--output", `${Paths.state}/scheme.json`, "--mode", "light"]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${DynamicColors.scheme}:${DynamicColors.flavour}`, "--output", `${Paths.state}/scheme.json`, "--mode", "light"]);
+16 -3
View File
@@ -141,7 +141,7 @@ MouseArea {
sy = ssy; sy = ssy;
ex = x; ex = x;
ey = y; ey = y;
} else { } else if (!saveTimer.running) {
checkClientRects(x, y); checkClientRects(x, y);
} }
} }
@@ -154,7 +154,7 @@ MouseArea {
return; return;
if (root.loader.freeze) { if (root.loader.freeze) {
save(); saveTimer.start();
} else { } else {
overlay.visible = border.visible = false; overlay.visible = border.visible = false;
screencopy.visible = false; screencopy.visible = false;
@@ -162,6 +162,16 @@ MouseArea {
} }
} }
Timer {
id: saveTimer
interval: 25
repeat: false
running: false
onTriggered: root.save()
}
SequentialAnimation { SequentialAnimation {
id: closeAnim id: closeAnim
@@ -217,8 +227,9 @@ MouseArea {
paintCursor: false paintCursor: false
onHasContentChanged: { onHasContentChanged: {
if (hasContent && !root.loader.freeze) { if (hasContent) {
overlay.visible = border.visible = true; overlay.visible = border.visible = true;
if (!root.loader.freeze)
root.save(); root.save();
} }
} }
@@ -233,6 +244,7 @@ MouseArea {
layer.enabled: true layer.enabled: true
opacity: 0.3 opacity: 0.3
radius: root.realRounding radius: root.realRounding
visible: false
layer.effect: MultiEffect { layer.effect: MultiEffect {
maskEnabled: true maskEnabled: true
@@ -270,6 +282,7 @@ MouseArea {
implicitHeight: selectionRect.implicitHeight + root.realBorderWidth * 2 implicitHeight: selectionRect.implicitHeight + root.realBorderWidth * 2
implicitWidth: selectionRect.implicitWidth + root.realBorderWidth * 2 implicitWidth: selectionRect.implicitWidth + root.realBorderWidth * 2
radius: root.realRounding > 0 ? root.realRounding + root.realBorderWidth : 0 radius: root.realRounding > 0 ? root.realRounding + root.realBorderWidth : 0
visible: false
x: selectionRect.x - root.realBorderWidth x: selectionRect.x - root.realBorderWidth
y: selectionRect.y - root.realBorderWidth y: selectionRect.y - root.realBorderWidth
+60
View File
@@ -0,0 +1,60 @@
pragma Singleton
import QtQuick
QtObject {
id: root
property Item activeMenu: null
property Item activeTrigger: null
function close(menu) {
if (!menu)
return;
if (activeMenu === menu) {
activeMenu = null;
activeTrigger = null;
}
menu.expanded = false;
}
function closeActive() {
if (activeMenu)
activeMenu.expanded = false;
activeMenu = null;
activeTrigger = null;
}
function forget(menu) {
if (activeMenu === menu) {
activeMenu = null;
activeTrigger = null;
}
}
function hit(item, scenePos) {
if (!item || !item.visible)
return false;
const p = item.mapFromItem(null, scenePos.x, scenePos.y);
return item.contains(p);
}
function open(menu, trigger) {
if (activeMenu && activeMenu !== menu)
activeMenu.expanded = false;
activeMenu = menu;
activeTrigger = trigger || null;
menu.expanded = true;
}
function toggle(menu, trigger) {
if (activeMenu === menu && menu.expanded)
close(menu);
else
open(menu, trigger);
}
}
+73 -12
View File
@@ -17,7 +17,6 @@ Singleton {
property var disks: [] property var disks: []
property real gpuMemTotal: 0 property real gpuMemTotal: 0
property real gpuMemUsed property real gpuMemUsed
property string gpuName: ""
property real gpuPerc property real gpuPerc
property real gpuTemp property real gpuTemp
readonly property string gpuType: Config.services.gpuType.toUpperCase() || autoGpuType readonly property string gpuType: Config.services.gpuType.toUpperCase() || autoGpuType
@@ -80,8 +79,29 @@ Singleton {
onTriggered: { onTriggered: {
stat.reload(); stat.reload();
meminfo.reload(); meminfo.reload();
storage.running = true; if (root.gpuType === "GENERIC")
gpuUsage.running = true; gpuUsage.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; sensors.running = true;
} }
} }
@@ -112,10 +132,13 @@ Singleton {
const totalDiff = total - root.lastCpuTotal; const totalDiff = total - root.lastCpuTotal;
const idleDiff = idle - root.lastCpuIdle; const idleDiff = idle - root.lastCpuIdle;
root.cpuPerc = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0; const newCpuPerc = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0;
root.lastCpuTotal = total; root.lastCpuTotal = total;
root.lastCpuIdle = idle; root.lastCpuIdle = idle;
if (Math.abs(newCpuPerc - root.cpuPerc) >= 0.01)
root.cpuPerc = newCpuPerc;
} }
} }
} }
@@ -127,8 +150,14 @@ Singleton {
onLoaded: { onLoaded: {
const data = text(); const data = text();
root.memTotal = parseInt(data.match(/MemTotal: *(\d+)/)[1], 10) || 1; const total = parseInt(data.match(/MemTotal: *(\d+)/)[1], 10) || 1;
root.memUsed = (root.memTotal - parseInt(data.match(/MemAvailable: *(\d+)/)[1], 10)) || 0; 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;
} }
} }
@@ -274,22 +303,54 @@ Singleton {
} }
} }
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 { Process {
id: gpuUsage id: gpuUsage
command: root.gpuType === "GENERIC" ? ["sh", "-c", "cat /sys/class/drm/card*/device/gpu_busy_percent"] : root.gpuType === "NVIDIA" ? ["nvidia-smi", "--query-gpu=utilization.gpu,temperature.gpu,memory.used", "--format=csv,noheader,nounits"] : ["echo"] command: root.gpuType === "GENERIC" ? ["sh", "-c", "cat /sys/class/drm/card*/device/gpu_busy_percent"] : ["echo"]
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
console.log("this is running");
if (root.gpuType === "GENERIC") { if (root.gpuType === "GENERIC") {
const percs = text.trim().split("\n"); const percs = text.trim().split("\n");
const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0); const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0);
root.gpuPerc = sum / percs.length / 100; root.gpuPerc = sum / percs.length / 100;
} else if (root.gpuType === "NVIDIA") {
const [usage, temp, mem] = text.trim().split(",");
root.gpuPerc = parseInt(usage, 10) / 100;
root.gpuTemp = parseInt(temp, 10);
root.gpuMemUsed = parseInt(mem, 10) / root.gpuMemTotal;
} else { } else {
root.gpuPerc = 0; root.gpuPerc = 0;
root.gpuTemp = 0; root.gpuTemp = 0;
@@ -314,7 +375,7 @@ Singleton {
// If AMD Tdie pattern failed, try fallback on Tctl // If AMD Tdie pattern failed, try fallback on Tctl
cpuTemp = text.match(/Tctl:\s+((\+|-)[0-9.]+)(°| )C/); cpuTemp = text.match(/Tctl:\s+((\+|-)[0-9.]+)(°| )C/);
if (cpuTemp) if (cpuTemp && Math.abs(parseFloat(cpuTemp[1]) - root.cpuTemp) >= 0.5)
root.cpuTemp = parseFloat(cpuTemp[1]); root.cpuTemp = parseFloat(cpuTemp[1]);
if (root.gpuType !== "GENERIC") if (root.gpuType !== "GENERIC")
+103
View File
@@ -0,0 +1,103 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Config
Singleton {
id: root
property var apps: {
const pinnedApps = uniq((Config.dock.pinnedApps ?? []).map(normalizeId));
const openMap = buildOpenMap();
const openIds = [...openMap.keys()];
const sessionOrder = uniq(root.unpinnedOrder.map(normalizeId));
const orderedUnpinned = sessionOrder.filter(id => openIds.includes(id) && !pinnedApps.includes(id)).concat(openIds.filter(id => !pinnedApps.includes(id) && !sessionOrder.includes(id)));
const out = [];
for (const appId of pinnedApps) {
out.push({
appId,
pinned: true,
toplevels: openMap.get(appId) ?? []
});
}
if (pinnedApps.length > 0) {
out.push({
appId: root.separatorId,
pinned: false,
toplevels: []
});
}
for (const appId of orderedUnpinned) {
out.push({
appId,
pinned: false,
toplevels: openMap.get(appId) ?? []
});
}
return out;
}
readonly property string separatorId: "__dock_separator__"
property var unpinnedOrder: []
function buildOpenMap() {
const ignoredRegexes = (Config.dock.ignoredAppRegexes ?? []).map(pattern => new RegExp(pattern, "i"));
return ToplevelManager.toplevels.values.reduce((map, toplevel) => {
if (ignoredRegexes.some(re => re.test(toplevel.appId)))
return map;
const appId = normalizeId(toplevel.appId);
if (!appId)
return map;
map.set(appId, (map.get(appId) ?? []).concat([toplevel]));
return map;
}, new Map());
}
function commitVisualOrder(ids) {
const orderedIds = uniq(ids.map(normalizeId));
const separatorIndex = orderedIds.indexOf(root.separatorId);
const pinnedApps = (separatorIndex === -1 ? [] : orderedIds.slice(0, separatorIndex)).filter(id => id !== root.separatorId);
const visibleUnpinned = orderedIds.slice(separatorIndex === -1 ? 0 : separatorIndex + 1).filter(id => id !== root.separatorId);
Config.dock.pinnedApps = pinnedApps;
root.unpinnedOrder = visibleUnpinned.concat(root.unpinnedOrder.map(normalizeId).filter(id => !pinnedApps.includes(id) && !visibleUnpinned.includes(id)));
Config.saveNoToast();
}
function isPinned(appId) {
return uniq((Config.dock.pinnedApps ?? []).map(normalizeId)).includes(normalizeId(appId));
}
function normalizeId(appId) {
if (appId === root.separatorId)
return root.separatorId;
return String(appId ?? "").toLowerCase();
}
function togglePin(appId) {
const id = normalizeId(appId);
const pinnedApps = uniq((Config.dock.pinnedApps ?? []).map(normalizeId));
const pinned = pinnedApps.includes(id);
Config.dock.pinnedApps = pinned ? pinnedApps.filter(x => x !== id) : pinnedApps.concat([id]);
root.unpinnedOrder = pinned ? [id].concat(root.unpinnedOrder.map(normalizeId).filter(x => x !== id)) : root.unpinnedOrder.map(normalizeId).filter(x => x !== id);
}
function uniq(ids) {
return (ids ?? []).filter((id, i, arr) => id && arr.indexOf(id) === i);
}
}
+123
View File
@@ -0,0 +1,123 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Paths
Singleton {
id: root
property int availableUpdates: 0
property bool loaded
property double now: Date.now()
property var updates: ({})
function formatUpdateTime(timestamp) {
const diffMs = root.now - timestamp;
const minuteMs = 60 * 1000;
const hourMs = 60 * minuteMs;
const dayMs = 24 * hourMs;
if (diffMs < minuteMs)
return "just now";
if (diffMs < hourMs)
return Math.floor(diffMs / minuteMs) + " min ago";
if (diffMs < 48 * hourMs)
return Math.floor(diffMs / hourMs) + " hr ago";
return Qt.formatDateTime(new Date(timestamp), "dd hh:mm");
}
onUpdatesChanged: {
if (!root.loaded)
return;
saveTimer.restart();
availableUpdates = Object.keys(updates).length;
}
Timer {
interval: 1
repeat: true
running: true
onTriggered: {
if (!root.loaded)
return;
updatesProc.running = true;
interval = 5000;
}
}
Timer {
interval: 60000
repeat: true
running: true
onTriggered: root.now = Date.now()
}
Process {
id: updatesProc
command: ["checkupdates"]
running: false
stdout: StdioCollector {
onStreamFinished: {
const output = this.text;
const lines = output.trim().split("\n").filter(line => line.length > 0);
const oldMap = root.updates;
const now = Date.now();
root.updates = lines.reduce((acc, pkg) => {
acc[pkg] = oldMap[pkg] ?? now;
return acc;
}, {});
root.availableUpdates = lines.length;
}
}
}
Timer {
id: saveTimer
interval: 1000
onTriggered: storage.setText(JSON.stringify(root.updates))
}
FileView {
id: storage
path: `${Paths.state}/updates.json`
onLoadFailed: err => {
if (err === FileViewError.FileNotFound) {
root.updates = ({});
root.loaded = true;
setText("{}");
return;
}
root.updates = ({});
root.loaded = true;
}
onLoaded: {
try {
const data = JSON.parse(text());
root.updates = data && typeof data === "object" && !Array.isArray(data) ? data : {};
} catch (e) {
root.updates = ({});
}
root.loaded = true;
}
}
}
+3 -5
View File
@@ -19,22 +19,20 @@ Searcher {
function preview(path: string): void { function preview(path: string): void {
previewPath = path; previewPath = path;
if (Config.general.color.schemeGeneration) if (Config.general.color.schemeGeneration)
Quickshell.execDetached(["sh", "-c", `zshell-cli scheme generate --image-path ${previewPath} --thumbnail-path ${Paths.cache}/imagecache/thumbnail.jpg --output ${Paths.state}/scheme.json --scheme ${Config.colors.schemeType} --mode ${Config.general.color.mode}`]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${previewPath}`, "--scheme", `${Config.colors.schemeType}`, "--mode", `${Config.general.color.mode}`]);
showPreview = true; showPreview = true;
} }
function setWallpaper(path: string): void { function setWallpaper(path: string): void {
actualCurrent = path; actualCurrent = path;
WallpaperPath.currentWallpaperPath = path; WallpaperPath.currentWallpaperPath = path;
if (Config.general.color.wallust) Quickshell.execDetached(["zshell-cli", "wallpaper", "lockscreen", "--input-image", `${root.actualCurrent}`, "--output-path", `${Paths.state}/lockscreen_bg.png`, "--blur-amount", `${Config.lock.blurAmount}`]);
Wallust.generateColors(WallpaperPath.currentWallpaperPath);
Quickshell.execDetached(["sh", "-c", `zshell-cli wallpaper lockscreen --input-image=${root.actualCurrent} --output-path=${Paths.state}/lockscreen_bg.png --blur-amount=${Config.lock.blurAmount}`]);
} }
function stopPreview(): void { function stopPreview(): void {
showPreview = false; showPreview = false;
if (Config.general.color.schemeGeneration) if (Config.general.color.schemeGeneration)
Quickshell.execDetached(["sh", "-c", `zshell-cli scheme generate --image-path ${root.actualCurrent} --thumbnail-path ${Paths.cache}/imagecache/thumbnail.jpg --output ${Paths.state}/scheme.json --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}`]);
} }
extraOpts: useFuzzy ? ({}) : ({ extraOpts: useFuzzy ? ({}) : ({
+11 -17
View File
@@ -7,15 +7,16 @@ import qs.Modules
import qs.Config import qs.Config
import qs.Components import qs.Components
Item { CustomRect {
id: root id: root
property color barColor: DynamicColors.palette.m3primary property color barColor: DynamicColors.palette.m3primary
property color textColor: DynamicColors.palette.m3onSurface property color textColor: DynamicColors.palette.m3onSurface
anchors.bottom: parent.bottom color: DynamicColors.tPalette.m3surfaceContainer
anchors.top: parent.top implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
implicitWidth: 150 implicitWidth: 150
radius: Appearance.rounding.full
Behavior on implicitWidth { Behavior on implicitWidth {
NumberAnimation { NumberAnimation {
@@ -24,34 +25,27 @@ Item {
} }
} }
CustomRect { Component.onCompleted: console.log(root.height, root.implicitHeight)
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.tPalette.m3surfaceContainer
height: 22
radius: height / 2
}
RowLayout { RowLayout {
id: layout id: layout
anchors.fill: parent anchors.left: parent.left
anchors.leftMargin: Appearance.padding.small anchors.leftMargin: Appearance.padding.small
anchors.rightMargin: Appearance.padding.small * 2
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: root.implicitWidth - Appearance.padding.small * 3
MaterialIcon { MaterialIcon {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
animate: true animate: true
color: Audio.muted ? DynamicColors.palette.m3error : root.textColor color: Audio.muted ? DynamicColors.palette.m3error : root.textColor
font.pointSize: 14 font.pointSize: Appearance.font.size.larger
text: Audio.muted ? "volume_off" : "volume_up" text: Audio.muted ? "volume_off" : "volume_up"
} }
CustomRect { CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
color: "#50ffffff" color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
implicitHeight: 4 implicitHeight: 4
radius: 20 radius: 20
@@ -74,13 +68,13 @@ Item {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
animate: true animate: true
color: (Audio.sourceMuted ?? false) ? DynamicColors.palette.m3error : root.textColor color: (Audio.sourceMuted ?? false) ? DynamicColors.palette.m3error : root.textColor
font.pointSize: 14 font.pointSize: Appearance.font.size.larger
text: Audio.sourceMuted ? "mic_off" : "mic" text: Audio.sourceMuted ? "mic_off" : "mic"
} }
CustomRect { CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
color: "#50ffffff" color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
implicitHeight: 4 implicitHeight: 4
radius: 20 radius: 20
+1 -1
View File
@@ -9,7 +9,7 @@ ShapePath {
readonly property bool flatten: wrapper.height < rounding * 2 readonly property bool flatten: wrapper.height < rounding * 2
property real ibr: invertBottomRounding ? -1 : 1 property real ibr: invertBottomRounding ? -1 : 1
required property bool invertBottomRounding required property bool invertBottomRounding
readonly property real rounding: 8 property real rounding: Appearance.rounding.smallest
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property Wrapper wrapper required property Wrapper wrapper
+218
View File
@@ -0,0 +1,218 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Modules
import qs.Config
import qs.Helpers
import qs.Modules.UPower
import qs.Modules.Network
import qs.Modules.Updates
RowLayout {
id: root
required property Wrapper popouts
required property ShellScreen screen
readonly property int vPadding: 6
required property PersistentProperties visibilities
function checkPopout(x: real): void {
const ch = childAt(x, height / 2) as WrappedLoader;
if (!ch || ch?.id === "spacer") {
if (!popouts.currentName.startsWith("traymenu"))
popouts.hasCurrent = false;
return;
}
if (visibilities.sidebar || visibilities.dashboard || visibilities.resources || visibilities.settings)
return;
const id = ch.id;
const top = ch.x;
const item = ch.item;
const itemWidth = item.implicitWidth;
if (id === "audio" && Config.barConfig.popouts.audio) {
popouts.currentName = "audio";
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true;
} else if (id === "network" && Config.barConfig.popouts.network) {
popouts.currentName = "network";
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true;
} else if (id === "upower" && Config.barConfig.popouts.upower) {
popouts.currentName = "upower";
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true;
} else if (id === "updates") {
popouts.currentName = "updates";
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true;
}
}
spacing: Appearance.spacing.small
Repeater {
id: repeater
// model: Config.barConfig.entries.filted(n => n.index > 50).sort(n => n.index)
model: Config.barConfig.entries
DelegateChooser {
role: "id"
DelegateChoice {
roleValue: "spacer"
delegate: WrappedLoader {
Layout.fillWidth: true
}
}
DelegateChoice {
roleValue: "workspaces"
delegate: WrappedLoader {
sourceComponent: Workspaces {
screen: root.screen
}
}
}
DelegateChoice {
roleValue: "audio"
delegate: WrappedLoader {
sourceComponent: AudioWidget {
}
}
}
DelegateChoice {
roleValue: "tray"
delegate: WrappedLoader {
sourceComponent: TrayWidget {
loader: root
popouts: root.popouts
}
}
}
DelegateChoice {
roleValue: "resources"
delegate: WrappedLoader {
sourceComponent: Resources {
visibilities: root.visibilities
}
}
}
DelegateChoice {
roleValue: "updates"
delegate: WrappedLoader {
sourceComponent: UpdatesWidget {
}
}
}
DelegateChoice {
roleValue: "notifBell"
delegate: WrappedLoader {
sourceComponent: NotifBell {
popouts: root.popouts
visibilities: root.visibilities
}
}
}
DelegateChoice {
roleValue: "clock"
delegate: WrappedLoader {
sourceComponent: Clock {
loader: root
popouts: root.popouts
visibilities: root.visibilities
}
}
}
DelegateChoice {
roleValue: "activeWindow"
delegate: WrappedLoader {
sourceComponent: WindowTitle {
bar: root
}
}
}
DelegateChoice {
roleValue: "upower"
delegate: WrappedLoader {
sourceComponent: UPowerWidget {
}
}
}
DelegateChoice {
roleValue: "network"
delegate: WrappedLoader {
sourceComponent: NetworkWidget {
}
}
}
DelegateChoice {
roleValue: "media"
delegate: WrappedLoader {
sourceComponent: MediaWidget {
}
}
}
}
}
component WrappedLoader: Loader {
required property bool enabled
required property string id
required property int index
function findFirstEnabled(): Item {
const count = repeater.count;
for (let i = 0; i < count; i++) {
const item = repeater.itemAt(i);
if (item?.enabled)
return item;
}
return null;
}
function findLastEnabled(): Item {
for (let i = repeater.count - 1; i >= 0; i--) {
const item = repeater.itemAt(i);
if (item?.enabled)
return item;
}
return null;
}
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: findFirstEnabled() === this ? root.vPadding : 0
Layout.rightMargin: findLastEnabled() === this ? root.vPadding : 0
active: enabled
visible: enabled
}
}
+43 -194
View File
@@ -11,223 +11,72 @@ import qs.Helpers
import qs.Modules.UPower import qs.Modules.UPower
import qs.Modules.Network import qs.Modules.Network
RowLayout { Item {
id: root id: root
required property PanelWindow bar readonly property int contentHeight: Config.barConfig.height + padding * 2
readonly property int exclusiveZone: Config.barConfig.autoHide ? Config.barConfig.border : contentHeight
property bool isHovered
readonly property int padding: Math.max(Appearance.padding.smaller, Config.barConfig.border)
required property Wrapper popouts required property Wrapper popouts
required property ShellScreen screen required property ShellScreen screen
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
function checkPopout(x: real): void { function checkPopout(x: real): void {
const ch = childAt(x, 2) as WrappedLoader; content.item?.checkPopout(x);
if (!ch || ch?.id === "spacer") {
if (!popouts.currentName.startsWith("traymenu"))
popouts.hasCurrent = false;
return;
} }
if (visibilities.sidebar || visibilities.dashboard || visibilities.resources) implicitHeight: Config.barConfig.border
return; visible: height > Config.barConfig.border
const id = ch.id; states: State {
const top = ch.x; name: "visible"
const item = ch.item; when: root.shouldBeVisible
const itemWidth = item.implicitWidth;
if (id === "audio" && Config.barConfig.popouts.audio) { PropertyChanges {
popouts.currentName = "audio"; root.implicitHeight: root.contentHeight
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true;
} else if (id === "network" && Config.barConfig.popouts.network) {
popouts.currentName = "network";
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true;
} else if (id === "upower" && Config.barConfig.popouts.upower) {
popouts.currentName = "upower";
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true;
} }
} }
transitions: [
Transition {
from: ""
to: "visible"
anchors.fill: parent Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitHeight"
target: root
}
},
Transition {
from: "visible"
to: ""
CustomShortcut { Anim {
name: "toggle-overview" duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
onPressed: { property: "implicitHeight"
Hyprland.refreshWorkspaces(); target: root
Hyprland.refreshMonitors();
if (root.popouts.hasCurrent && root.popouts.currentName === "overview") {
root.popouts.hasCurrent = false;
} else {
root.popouts.currentName = "overview";
root.popouts.currentCenter = root.width / 2;
root.popouts.hasCurrent = true;
}
} }
} }
]
Repeater { Loader {
id: repeater id: content
model: Config.barConfig.entries active: root.shouldBeVisible || root.visible
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
DelegateChooser { sourceComponent: Bar {
role: "id" height: root.contentHeight
DelegateChoice {
roleValue: "spacer"
delegate: WrappedLoader {
Layout.fillWidth: true
}
}
DelegateChoice {
roleValue: "workspaces"
delegate: WrappedLoader {
sourceComponent: Workspaces {
bar: root.bar
}
}
}
DelegateChoice {
roleValue: "audio"
delegate: WrappedLoader {
sourceComponent: AudioWidget {
}
}
}
DelegateChoice {
roleValue: "tray"
delegate: WrappedLoader {
sourceComponent: TrayWidget {
bar: root.bar
loader: root
popouts: root.popouts popouts: root.popouts
} screen: root.screen
}
}
DelegateChoice {
roleValue: "resources"
delegate: WrappedLoader {
sourceComponent: Resources {
visibilities: root.visibilities visibilities: root.visibilities
} }
} }
} }
DelegateChoice {
roleValue: "updates"
delegate: WrappedLoader {
sourceComponent: UpdatesWidget {
}
}
}
DelegateChoice {
roleValue: "notifBell"
delegate: WrappedLoader {
sourceComponent: NotifBell {
popouts: root.popouts
visibilities: root.visibilities
}
}
}
DelegateChoice {
roleValue: "clock"
delegate: WrappedLoader {
sourceComponent: Clock {
loader: root
popouts: root.popouts
visibilities: root.visibilities
}
}
}
DelegateChoice {
roleValue: "activeWindow"
delegate: WrappedLoader {
sourceComponent: WindowTitle {
bar: root
monitor: Brightness.getMonitorForScreen(root.screen)
}
}
}
DelegateChoice {
roleValue: "upower"
delegate: WrappedLoader {
sourceComponent: UPowerWidget {
}
}
}
DelegateChoice {
roleValue: "network"
delegate: WrappedLoader {
sourceComponent: NetworkWidget {
}
}
}
DelegateChoice {
roleValue: "media"
delegate: WrappedLoader {
sourceComponent: MediaWidget {
}
}
}
}
}
component WrappedLoader: Loader {
required property bool enabled
required property string id
required property int index
function findFirstEnabled(): Item {
const count = repeater.count;
for (let i = 0; i < count; i++) {
const item = repeater.itemAt(i);
if (item?.enabled)
return item;
}
return null;
}
function findLastEnabled(): Item {
for (let i = repeater.count - 1; i >= 0; i--) {
const item = repeater.itemAt(i);
if (item?.enabled)
return item;
}
return null;
}
Layout.alignment: Qt.AlignVCenter
Layout.fillHeight: true
Layout.leftMargin: findFirstEnabled() === this ? root.vPadding : 0
Layout.rightMargin: findLastEnabled() === this ? root.vPadding : 0
active: enabled
visible: enabled
}
}
+7 -10
View File
@@ -3,7 +3,6 @@ pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import QtQuick import QtQuick
import QtQuick.Effects import QtQuick.Effects
import qs.Modules
import qs.Config import qs.Config
import qs.Components import qs.Components
@@ -17,7 +16,8 @@ Item {
CustomRect { CustomRect {
anchors.fill: parent anchors.fill: parent
color: Config.barConfig.autoHide && !root.visibilities.bar ? "transparent" : DynamicColors.palette.m3surface anchors.margins: -1
color: DynamicColors.palette.m3surface
layer.enabled: true layer.enabled: true
layer.effect: MultiEffect { layer.effect: MultiEffect {
@@ -38,14 +38,11 @@ Item {
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
anchors.topMargin: Config.barConfig.autoHide && !root.visibilities.bar ? 4 : root.bar.implicitHeight anchors.margins: Config.barConfig.border + 1
topLeftRadius: 8 anchors.topMargin: root.bar.implicitHeight + 1
topRightRadius: 8 radius: Config.barConfig.border > 0 ? Config.barConfig.rounding : 0
topLeftRadius: Config.barConfig.rounding
Behavior on anchors.topMargin { topRightRadius: Config.barConfig.rounding
Anim {
}
}
} }
} }
} }
-73
View File
@@ -1,73 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Helpers
RowLayout {
spacing: 12
Rectangle {
Layout.preferredHeight: 40
Layout.preferredWidth: 40
color: "transparent"
radius: 1000
MaterialIcon {
anchors.centerIn: parent
color: DynamicColors.palette.m3onSurface
fill: 1
font.pointSize: 24
text: "arrow_back_2"
}
StateLayer {
onClicked: {
if (Calendar.displayMonth === 0) {
Calendar.displayMonth = 11;
Calendar.displayYear -= 1;
} else {
Calendar.displayMonth -= 1;
}
}
}
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurface
font.pointSize: 14
font.weight: 600
horizontalAlignment: Text.AlignHCenter
text: new Date(Calendar.displayYear, Calendar.displayMonth, 1).toLocaleDateString(Qt.locale(), "MMMM yyyy")
}
Rectangle {
Layout.preferredHeight: 40
Layout.preferredWidth: 40
color: "transparent"
radius: 1000
MaterialIcon {
anchors.centerIn: parent
color: DynamicColors.palette.m3onSurface
fill: 1
font.pointSize: 24
rotation: 180
text: "arrow_back_2"
}
StateLayer {
onClicked: {
if (Calendar.displayMonth === 11) {
Calendar.displayMonth = 0;
Calendar.displayYear += 1;
} else {
Calendar.displayMonth += 1;
}
}
}
}
}
-77
View File
@@ -1,77 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Helpers
Item {
id: root
required property Item wrapper
implicitHeight: layout.childrenRect.height + layout.anchors.margins * 2
implicitWidth: layout.childrenRect.width + layout.anchors.margins * 2
ColumnLayout {
id: layout
anchors.centerIn: parent
anchors.margins: 16
spacing: 16
// Header with month/year and navigation
CalendarHeader {
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
}
// Calendar grid
RowLayout {
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
spacing: 12
ColumnLayout {
Layout.alignment: Qt.AlignTop
Layout.preferredHeight: childrenRect.height
Layout.preferredWidth: weekNumberColumn.width
spacing: 8
Item {
Layout.preferredHeight: dayOfWeekRow.height
}
WeekNumberColumn {
id: weekNumberColumn
Layout.alignment: Qt.AlignTop
Layout.preferredHeight: weekNumbers.values.length * 44
}
}
ColumnLayout {
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
spacing: 8
DayOfWeekRow {
id: dayOfWeekRow
Layout.fillWidth: true
Layout.preferredHeight: 30
locale: Qt.locale()
}
MonthGrid {
Layout.preferredHeight: childrenRect.height
Layout.preferredWidth: childrenRect.width
locale: Qt.locale()
wrapper: root.wrapper
}
}
}
}
}
-42
View File
@@ -1,42 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Helpers
RowLayout {
id: root
required property var locale
spacing: 4
Repeater {
model: 7
Item {
readonly property string dayName: {
// Get the day name for this column
const dayIndex = (index + Calendar.weekStartDay) % 7;
return root.locale.dayName(dayIndex, Locale.ShortFormat);
}
required property int index
Layout.fillWidth: true
Layout.preferredHeight: 30
CustomText {
anchors.centerIn: parent
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: 11
font.weight: 500
horizontalAlignment: Text.AlignHCenter
opacity: 0.8
text: parent.dayName
verticalAlignment: Text.AlignVCenter
}
}
}
}
-118
View File
@@ -1,118 +0,0 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Helpers
GridLayout {
id: root
required property var locale
required property Item wrapper
columnSpacing: 4
columns: 7
rowSpacing: 4
uniformCellHeights: true
uniformCellWidths: true
Repeater {
id: repeater
model: ScriptModel {
values: Calendar.getWeeksForMonth(Calendar.displayMonth, Calendar.displayYear)
Behavior on values {
SequentialAnimation {
id: switchAnim
ParallelAnimation {
Anim {
from: 1.0
property: "opacity"
to: 0.0
}
Anim {
from: 1.0
property: "scale"
to: 0.8
}
}
PropertyAction {
}
ParallelAnimation {
Anim {
from: 0.0
property: "opacity"
to: 1.0
}
Anim {
from: 0.8
property: "scale"
to: 1.0
}
}
}
}
}
Rectangle {
required property int index
required property var modelData
Layout.preferredHeight: width
Layout.preferredWidth: 40
color: {
if (modelData.isToday) {
return DynamicColors.palette.m3primaryContainer;
}
return "transparent";
}
radius: 1000
Behavior on color {
ColorAnimation {
duration: 200
}
}
CustomText {
anchors.centerIn: parent
color: {
if (parent.modelData.isToday) {
return DynamicColors.palette.m3onPrimaryContainer;
}
return DynamicColors.palette.m3onSurface;
}
horizontalAlignment: Text.AlignHCenter
opacity: parent.modelData.isCurrentMonth ? 1.0 : 0.4
text: parent.modelData.day.toString()
verticalAlignment: Text.AlignVCenter
Behavior on color {
ColorAnimation {
duration: 200
}
}
Behavior on opacity {
NumberAnimation {
duration: 200
}
}
}
}
}
component Anim: NumberAnimation {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
target: root
}
}
-45
View File
@@ -1,45 +0,0 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Helpers
ColumnLayout {
id: root
readonly property var weekNumbers: Calendar.getWeekNumbers(Calendar.displayMonth, Calendar.displayYear)
spacing: 4
Repeater {
model: ScriptModel {
values: root.weekNumbers
}
Item {
id: weekItem
required property int index
required property var modelData
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: 40
Layout.preferredWidth: 20
CustomText {
id: weekText
anchors.centerIn: parent
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: 10
horizontalAlignment: Text.AlignHCenter
opacity: 0.5
text: weekItem.modelData
verticalAlignment: Text.AlignVCenter
}
}
}
}
+5 -12
View File
@@ -6,23 +6,17 @@ import qs.Modules
import qs.Helpers as Helpers import qs.Helpers as Helpers
import qs.Components import qs.Components
Item { CustomRect {
id: root id: root
required property RowLayout loader required property RowLayout loader
required property Wrapper popouts required property Wrapper popouts
required property PersistentProperties visibilities required property PersistentProperties visibilities
anchors.bottom: parent.bottom color: DynamicColors.tPalette.m3surfaceContainer
anchors.top: parent.top implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
implicitWidth: timeText.contentWidth + 5 * 2 implicitWidth: timeText.contentWidth + Appearance.padding.normal * 2
radius: Appearance.rounding.full
CustomRect {
anchors.bottomMargin: 3
anchors.fill: parent
anchors.topMargin: 3
color: "transparent"
radius: 4
CustomText { CustomText {
id: timeText id: timeText
@@ -45,4 +39,3 @@ Item {
} }
} }
} }
}
+9 -9
View File
@@ -5,10 +5,10 @@ import Quickshell.Services.SystemTray
import QtQuick import QtQuick
import qs.Config import qs.Config
import qs.Components import qs.Components
import qs.Modules.Calendar
import qs.Modules.WSOverview import qs.Modules.WSOverview
import qs.Modules.Network import qs.Modules.Network
import qs.Modules.UPower import qs.Modules.UPower
import qs.Modules.Updates
Item { Item {
id: root id: root
@@ -69,14 +69,6 @@ Item {
} }
} }
Popout {
name: "calendar"
sourceComponent: CalendarPopup {
wrapper: root.wrapper
}
}
Popout { Popout {
name: "overview" name: "overview"
@@ -101,6 +93,14 @@ Item {
wrapper: root.wrapper wrapper: root.wrapper
} }
} }
Popout {
name: "updates"
sourceComponent: UpdatesPopout {
wrapper: root.wrapper
}
}
} }
component Popout: Loader { component Popout: Loader {
+3 -4
View File
@@ -7,7 +7,7 @@ ShapePath {
id: root id: root
readonly property bool flatten: wrapper.height < rounding * 2 readonly property bool flatten: wrapper.height < rounding * 2
readonly property real rounding: 8 readonly property real rounding: Appearance.rounding.normal
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property Wrapper wrapper required property Wrapper wrapper
@@ -45,16 +45,15 @@ ShapePath {
} }
PathArc { PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height) radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding relativeX: root.rounding
relativeY: -root.roundingY relativeY: root.roundingY
} }
PathLine { PathLine {
relativeX: 0 relativeX: 0
relativeY: -(root.wrapper.height - root.roundingY * 2) relativeY: -(root.wrapper.height)
} }
PathArc { PathArc {
+1 -1
View File
@@ -40,7 +40,7 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
color: "transparent" color: "transparent"
radius: 6 radius: Appearance.rounding.normal - anchors.margins
Item { Item {
id: view id: view
+4
View File
@@ -72,6 +72,9 @@ Item {
} }
} }
CustomClippingRect {
anchors.fill: parent
Loader { Loader {
id: content id: content
@@ -86,3 +89,4 @@ Item {
} }
} }
} }
}
@@ -0,0 +1,183 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Quickshell.Hyprland
import qs.Components
import qs.Config
import qs.Paths
import qs.Helpers
Item {
id: root
anchors.fill: parent
z: 998
visible: false
property real menuX: 0
property real menuY: 0
MouseArea {
anchors.fill: parent
onClicked: root.close()
}
CustomClippingRect {
id: popupBackground
readonly property real padding: 4
x: root.menuX
y: root.menuY
color: DynamicColors.tPalette.m3surface
radius: Appearance.rounding.normal
implicitWidth: menuLayout.implicitWidth + padding * 2
implicitHeight: menuLayout.implicitHeight + padding * 2
Behavior on opacity { Anim {} }
opacity: root.visible ? 1 : 0
ColumnLayout {
id: menuLayout
anchors.centerIn: parent
spacing: 0
CustomRect {
Layout.preferredWidth: 200
radius: popupBackground.radius - popupBackground.padding
implicitHeight: openTerminalRow.implicitHeight + Appearance.padding.small * 2
RowLayout {
id: openTerminalRow
spacing: 8
anchors.fill: parent
anchors.leftMargin: Appearance.padding.smaller
MaterialIcon { text: "terminal"; font.pointSize: 20 }
CustomText { text: "Open terminal"; Layout.fillWidth: true }
}
StateLayer {
anchors.fill: parent
onClicked: {
Quickshell.execDetached([Config.general.apps.terminal, "--working-directory", FileUtils.trimFileProtocol(Paths.desktop)])
root.close()
}
}
}
CustomRect {
Layout.fillWidth: true
implicitHeight: 1
color: DynamicColors.palette.m3outlineVariant
Layout.topMargin: 4
Layout.bottomMargin: 4
}
CustomRect {
Layout.fillWidth: true
radius: popupBackground.radius - popupBackground.padding
implicitHeight: settingsRow.implicitHeight + Appearance.padding.small * 2
RowLayout {
id: settingsRow
spacing: 8
anchors.fill: parent
anchors.leftMargin: Appearance.padding.smaller
MaterialIcon { text: "settings"; font.pointSize: 20 }
CustomText { text: "ZShell settings"; Layout.fillWidth: true }
}
StateLayer {
anchors.fill: parent
onClicked: {
const visibilities = Visibilities.getForActive();
visibilities.settings = true;
root.close()
}
}
}
CustomRect {
Layout.fillWidth: true
implicitHeight: 1
color: DynamicColors.palette.m3outlineVariant
Layout.topMargin: 4
Layout.bottomMargin: 4
}
CustomRect {
Layout.fillWidth: true
radius: popupBackground.radius - popupBackground.padding
implicitHeight: logoutRow.implicitHeight + Appearance.padding.small * 2
RowLayout {
id: logoutRow
spacing: 8
anchors.fill: parent
anchors.leftMargin: Appearance.padding.smaller
MaterialIcon { text: "logout"; font.pointSize: 20 }
CustomText { text: "Logout"; Layout.fillWidth: true }
}
StateLayer {
anchors.fill: parent
onClicked: {
Hyprland.dispatch("global quickshell:sessionOpen")
root.close()
}
}
}
// CustomRect {
// Layout.fillWidth: true
// implicitHeight: 1
// color: DynamicColors.palette.m3outlineVariant
// Layout.topMargin: 4
// Layout.bottomMargin: 4
// }
//
// CustomRect {
// Layout.fillWidth: true
// radius: popupBackground.radius - popupBackground.padding
// implicitHeight: desktopIconsRow.implicitHeight + Appearance.padding.small * 2
//
// RowLayout {
// id: desktopIconsRow
// spacing: 8
// anchors.fill: parent
// anchors.leftMargin: Appearance.padding.smaller
//
// MaterialIcon { text: Config.options.background.showDesktopIcons ? "visibility_off" : "visibility"; font.pointSize: 20 }
// CustomText { text: Config.options.background.showDesktopIcons ? "Hide icons" : "Show icons"; Layout.fillWidth: true }
// }
//
// StateLayer {
// anchors.fill: parent
//
// onClicked: {
// Config.options.background.showDesktopIcons = !Config.options.background.showDesktopIcons
// root.close()
// }
// }
// }
}
}
function openAt(mouseX, mouseY, parentW, parentH) {
menuX = Math.floor(Math.min(mouseX, parentW - popupBackground.implicitWidth))
menuY = Math.floor(Math.min(mouseY, parentH - popupBackground.implicitHeight))
visible = true
}
function close() {
visible = false
}
}
@@ -0,0 +1,232 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import qs.Components
import qs.Config
Item {
id: contextMenu
anchors.fill: parent
z: 999
visible: false
property string targetFilePath: ""
property bool targetIsDir: false
property var targetAppEntry: null
property var targetPaths: []
signal openFileRequested(string path, bool isDir)
signal renameRequested(string path)
property real menuX: 0
property real menuY: 0
CustomClippingRect {
id: popupBackground
readonly property real padding: Appearance.padding.small
x: contextMenu.menuX
y: contextMenu.menuY
color: DynamicColors.tPalette.m3surface
radius: Appearance.rounding.normal
implicitWidth: menuLayout.implicitWidth + padding * 2
implicitHeight: menuLayout.implicitHeight + padding * 2
Behavior on opacity { Anim {} }
opacity: contextMenu.visible ? 1 : 0
ColumnLayout {
id: menuLayout
anchors.centerIn: parent
spacing: 0
CustomRect {
Layout.preferredWidth: 160
radius: popupBackground.radius - popupBackground.padding
implicitHeight: openRow.implicitHeight + Appearance.padding.small * 2
RowLayout {
id: openRow
spacing: 8
anchors.fill: parent
anchors.leftMargin: Appearance.padding.smaller
MaterialIcon { text: "open_in_new"; font.pointSize: 20 }
CustomText { text: "Open"; Layout.fillWidth: true }
}
StateLayer {
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
radius: popupBackground.radius - popupBackground.padding
implicitHeight: openWithRow.implicitHeight + Appearance.padding.small * 2
RowLayout {
id: openWithRow
spacing: 8
anchors.fill: parent
anchors.leftMargin: Appearance.padding.smaller
MaterialIcon { text: contextMenu.targetIsDir ? "terminal" : "apps"; font.pointSize: 20 }
CustomText { text: contextMenu.targetIsDir ? "Open in terminal" : "Open with..."; Layout.fillWidth: true }
}
StateLayer {
anchors.fill: parent
onClicked: {
if (contextMenu.targetIsDir) {
Quickshell.execDetached([Config.general.apps.terminal, "--working-directory", contextMenu.targetFilePath])
} else {
Quickshell.execDetached(["xdg-open", contextMenu.targetFilePath])
}
contextMenu.close()
}
}
}
CustomRect {
Layout.fillWidth: true
implicitHeight: 1
color: DynamicColors.palette.m3outlineVariant
Layout.topMargin: 4
Layout.bottomMargin: 4
}
CustomRect {
Layout.fillWidth: true
radius: popupBackground.radius - popupBackground.padding
implicitHeight: copyPathRow.implicitHeight + Appearance.padding.small * 2
RowLayout {
id: copyPathRow
spacing: 8
anchors.fill: parent
anchors.leftMargin: Appearance.padding.smaller
MaterialIcon { text: "content_copy"; font.pointSize: 20 }
CustomText { text: "Copy path"; Layout.fillWidth: true }
}
StateLayer {
anchors.fill: parent
onClicked: {
Quickshell.execDetached(["wl-copy", contextMenu.targetPaths.join("\n")])
contextMenu.close()
}
}
}
CustomRect {
Layout.fillWidth: true
visible: contextMenu.targetPaths.length === 1
radius: popupBackground.radius - popupBackground.padding
implicitHeight: renameRow.implicitHeight + Appearance.padding.small * 2
RowLayout {
id: renameRow
spacing: 8
anchors.fill: parent
anchors.leftMargin: Appearance.padding.smaller
MaterialIcon { text: "edit"; font.pointSize: 20 }
CustomText { text: "Rename"; Layout.fillWidth: true }
}
StateLayer {
anchors.fill: parent
onClicked: {
contextMenu.renameRequested(contextMenu.targetFilePath)
contextMenu.close()
}
}
}
Rectangle {
Layout.fillWidth: true
implicitHeight: 1
color: DynamicColors.palette.m3outlineVariant
Layout.topMargin: 4
Layout.bottomMargin: 4
}
CustomRect {
Layout.fillWidth: true
radius: popupBackground.radius - popupBackground.padding
implicitHeight: deleteRow.implicitHeight + Appearance.padding.small * 2
RowLayout {
id: deleteRow
spacing: 8
anchors.fill: parent
anchors.leftMargin: Appearance.padding.smaller
MaterialIcon {
text: "delete"
font.pointSize: 20
color: deleteButton.hovered ? DynamicColors.palette.m3onError : DynamicColors.palette.m3error
}
CustomText {
text: "Move to trash"
Layout.fillWidth: true
color: deleteButton.hovered ? DynamicColors.palette.m3onError : DynamicColors.palette.m3error
}
}
StateLayer {
id: deleteButton
anchors.fill: parent
color: DynamicColors.tPalette.m3error
onClicked: {
let cmd = ["gio", "trash"].concat(contextMenu.targetPaths)
Quickshell.execDetached(cmd)
contextMenu.close()
}
}
}
}
}
function openAt(mouseX, mouseY, path, isDir, appEnt, parentW, parentH, selectionArray) {
targetFilePath = path
targetIsDir = isDir
targetAppEntry = appEnt
targetPaths = (selectionArray && selectionArray.length > 0) ? selectionArray : [path]
menuX = Math.floor(Math.min(mouseX, parentW - popupBackground.implicitWidth))
menuY = Math.floor(Math.min(mouseY, parentH - popupBackground.implicitHeight))
visible = true
}
function close() {
visible = false
}
}
@@ -0,0 +1,274 @@
import QtQuick
import Quickshell
import Quickshell.Widgets
import qs.Config
import qs.Components
import qs.Helpers
Item {
id: delegateRoot
property var appEntry: fileName.endsWith(".desktop") ? DesktopEntries.byId(DesktopUtils.getAppId(fileName)) : null
property bool fileIsDir: model.isDir
property string fileName: model.fileName
property string filePath: model.filePath
property int gridX: model.gridX
property int gridY: model.gridY
property bool isSnapping: snapAnimX.running || snapAnimY.running
property bool lassoActive
property string resolvedIcon: {
if (fileName.endsWith(".desktop")) {
if (appEntry && appEntry.icon && appEntry.icon !== "")
return appEntry.icon;
return AppSearch.guessIcon(DesktopUtils.getAppId(fileName));
} else if (DesktopUtils.getFileType(fileName, fileIsDir) === "image") {
return "file://" + filePath;
} else {
return DesktopUtils.getIconName(fileName, fileIsDir);
}
}
function compensateAndSnap(absVisX, absVisY) {
dragContainer.x = absVisX - delegateRoot.x;
dragContainer.y = absVisY - delegateRoot.y;
snapAnimX.start();
snapAnimY.start();
}
function getDragX() {
return dragContainer.x;
}
function getDragY() {
return dragContainer.y;
}
height: root.cellHeight
width: root.cellWidth
x: gridX * root.cellWidth
y: gridY * root.cellHeight
Behavior on x {
enabled: !mouseArea.drag.active && !isSnapping && !root.selectedIcons.includes(filePath)
Anim {
}
}
Behavior on y {
enabled: !mouseArea.drag.active && !isSnapping && !root.selectedIcons.includes(filePath)
Anim {
}
}
Item {
id: dragContainer
height: parent.height
width: parent.width
states: State {
when: mouseArea.drag.active
PropertyChanges {
opacity: 0.8
scale: 1.1
target: dragContainer
z: 100
}
}
transform: Translate {
x: (root.selectedIcons.includes(filePath) && root.dragLeader !== "" && root.dragLeader !== filePath) ? root.groupDragX : 0
y: (root.selectedIcons.includes(filePath) && root.dragLeader !== "" && root.dragLeader !== filePath) ? root.groupDragY : 0
}
transitions: Transition {
Anim {
}
}
onXChanged: {
if (mouseArea.drag.active) {
root.dragLeader = filePath;
root.groupDragX = x;
}
}
onYChanged: {
if (mouseArea.drag.active) {
root.dragLeader = filePath;
root.groupDragY = y;
}
}
PropertyAnimation {
id: snapAnimX
duration: 250
easing.type: Easing.OutCubic
property: "x"
target: dragContainer
to: 0
}
PropertyAnimation {
id: snapAnimY
duration: 250
easing.type: Easing.OutCubic
property: "y"
target: dragContainer
to: 0
}
Column {
anchors.centerIn: parent
spacing: 6
IconImage {
anchors.horizontalCenter: parent.horizontalCenter
implicitSize: 48
source: {
if (delegateRoot.resolvedIcon.startsWith("file://") || delegateRoot.resolvedIcon.startsWith("/")) {
return delegateRoot.resolvedIcon;
} else {
return Quickshell.iconPath(delegateRoot.resolvedIcon, fileIsDir ? "folder" : "text-x-generic");
}
}
}
Item {
height: 40
width: 88
CustomText {
anchors.fill: parent
color: "white"
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
maximumLineCount: 2
style: Text.Outline
styleColor: "black"
text: (appEntry && appEntry.name !== "") ? appEntry.name : fileName
visible: !renameLoader.active
wrapMode: Text.Wrap
}
Loader {
id: renameLoader
active: root.editingFilePath === filePath
anchors.centerIn: parent
height: 24
width: 110
sourceComponent: CustomTextInput {
anchors.fill: parent
anchors.margins: 2
color: "white"
horizontalAlignment: Text.AlignHCenter
text: fileName
wrapMode: Text.Wrap
Component.onCompleted: {
forceActiveFocus();
selectAll();
}
Keys.onPressed: function (event) {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
if (text.trim() !== "" && text !== fileName) {
let newName = text.trim();
let newPath = filePath.substring(0, filePath.lastIndexOf('/') + 1) + newName;
Quickshell.execDetached(["mv", filePath, newPath]);
}
root.editingFilePath = "";
event.accepted = true;
} else if (event.key === Qt.Key_Escape) {
root.editingFilePath = "";
event.accepted = true;
}
}
onActiveFocusChanged: {
if (!activeFocus && root.editingFilePath === filePath) {
root.editingFilePath = "";
}
}
}
}
}
}
CustomRect {
anchors.fill: parent
anchors.margins: 4
color: "white"
opacity: root.selectedIcons.includes(filePath) ? 0.2 : 0.0
radius: 8
Behavior on opacity {
Anim {
}
}
}
MouseArea {
id: mouseArea
acceptedButtons: Qt.LeftButton | Qt.RightButton
anchors.fill: parent
cursorShape: root.lassoActive ? undefined : Qt.PointingHandCursor
drag.target: dragContainer
hoverEnabled: true
onClicked: mouse => {
root.forceActiveFocus();
if (mouse.button === Qt.RightButton) {
if (!root.selectedIcons.includes(filePath)) {
root.selectedIcons = [filePath];
}
let pos = mapToItem(root, mouse.x, mouse.y);
root.contextMenu.openAt(pos.x, pos.y, filePath, fileIsDir, appEntry, root.width, root.height, root.selectedIcons);
} else {
root.selectedIcons = [filePath];
root.contextMenu.close();
}
}
onDoubleClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
if (filePath.endsWith(".desktop") && appEntry)
appEntry.execute();
else
root.exec(filePath, fileIsDir);
}
}
onPressed: mouse => {
if (mouse.button === Qt.LeftButton && !root.selectedIcons.includes(filePath)) {
root.selectedIcons = [filePath];
}
}
onReleased: {
if (drag.active) {
let absoluteX = delegateRoot.x + dragContainer.x;
let absoluteY = delegateRoot.y + dragContainer.y;
let snapX = Math.max(0, Math.round(absoluteX / root.cellWidth));
let snapY = Math.max(0, Math.round(absoluteY / root.cellHeight));
root.performMassDrop(filePath, snapX, snapY);
}
}
CustomRect {
anchors.fill: parent
anchors.margins: 4
color: "white"
opacity: parent.containsMouse ? 0.1 : 0.0
radius: 8
Behavior on opacity {
Anim {
}
}
}
}
}
}
+206
View File
@@ -0,0 +1,206 @@
import QtQuick
import Quickshell
import qs.Modules
import qs.Helpers
import qs.Config
import qs.Components
import qs.Paths
import ZShell.Services
Item {
id: root
property int cellHeight: 110
property int cellWidth: 100
property var contextMenu: desktopMenu
property string dragLeader: ""
property string editingFilePath: ""
property real groupDragX: 0
property real groupDragY: 0
property bool lassoActive: false
property var selectedIcons: []
property real startX: 0
property real startY: 0
function exec(filePath, isDir) {
const cmd = ["xdg-open", filePath];
Quickshell.execDetached(cmd);
}
function performMassDrop(leaderPath, targetX, targetY) {
let maxCol = Math.max(0, Math.floor(gridArea.width / cellWidth) - 1);
let maxRow = Math.max(0, Math.floor(gridArea.height / cellHeight) - 1);
let visuals = [];
for (let i = 0; i < gridArea.children.length; i++) {
let child = gridArea.children[i];
if (child.filePath && root.selectedIcons.includes(child.filePath)) {
let isLeader = (root.dragLeader === child.filePath);
let offsetX = isLeader ? child.getDragX() : root.groupDragX;
let offsetY = isLeader ? child.getDragY() : root.groupDragY;
visuals.push({
childRef: child,
absX: child.x + offsetX,
absY: child.y + offsetY
});
}
}
desktopModel.massMove(root.selectedIcons, leaderPath, targetX, targetY, maxCol, maxRow);
for (let i = 0; i < visuals.length; i++) {
visuals[i].childRef.compensateAndSnap(visuals[i].absX, visuals[i].absY);
}
root.dragLeader = "";
root.groupDragX = 0;
root.groupDragY = 0;
}
focus: true
Keys.onPressed: event => {
if (event.key === Qt.Key_F2 && selectedIcons.length > 0)
editingFilePath = selectedIcons[0];
}
DesktopModel {
id: desktopModel
Component.onCompleted: loadDirectory(FileUtils.trimFileProtocol(Paths.desktop))
}
CustomRect {
id: lasso
function hideLasso() {
fadeIn.stop();
fadeOut.start();
root.lassoActive = false;
}
function showLasso() {
root.lassoActive = true;
fadeOut.stop();
visible = true;
fadeIn.start();
}
border.color: DynamicColors.palette.m3primary
border.width: 1
color: DynamicColors.tPalette.m3primary
opacity: 0
radius: Appearance.rounding.small
visible: false
z: 99
NumberAnimation {
id: fadeIn
duration: 120
from: 0
property: "opacity"
target: lasso
to: 1
}
SequentialAnimation {
id: fadeOut
NumberAnimation {
duration: 120
from: lasso.opacity
property: "opacity"
target: lasso
to: 0
}
ScriptAction {
script: lasso.visible = false
}
}
}
MouseArea {
acceptedButtons: Qt.LeftButton | Qt.RightButton
anchors.fill: parent
onPositionChanged: mouse => {
if (lasso.visible) {
lasso.x = Math.floor(Math.min(mouse.x, root.startX));
lasso.y = Math.floor(Math.min(mouse.y, root.startY));
lasso.width = Math.floor(Math.abs(mouse.x - root.startX));
lasso.height = Math.floor(Math.abs(mouse.y - root.startY));
let minCol = Math.floor((lasso.x - gridArea.x) / cellWidth);
let maxCol = Math.floor((lasso.x + lasso.width - gridArea.x) / cellWidth);
let minRow = Math.floor((lasso.y - gridArea.y) / cellHeight);
let maxRow = Math.floor((lasso.y + lasso.height - gridArea.y) / cellHeight);
let newSelection = [];
for (let i = 0; i < gridArea.children.length; i++) {
let child = gridArea.children[i];
if (child.filePath !== undefined && child.gridX >= minCol && child.gridX <= maxCol && child.gridY >= minRow && child.gridY <= maxRow) {
newSelection.push(child.filePath);
}
}
root.selectedIcons = newSelection;
}
}
onPressed: mouse => {
root.editingFilePath = "";
desktopMenu.close();
if (mouse.button === Qt.RightButton) {
root.selectedIcons = [];
bgContextMenu.openAt(mouse.x, mouse.y, root.width, root.height);
} else {
bgContextMenu.close();
root.selectedIcons = [];
root.startX = Math.floor(mouse.x);
root.startY = Math.floor(mouse.y);
lasso.x = Math.floor(mouse.x);
lasso.y = Math.floor(mouse.y);
lasso.width = 0;
lasso.height = 0;
lasso.showLasso();
}
}
onReleased: {
lasso.hideLasso();
}
}
Item {
id: gridArea
anchors.fill: parent
anchors.margins: 20
anchors.topMargin: 40
visible: true
Repeater {
model: desktopModel
delegate: DesktopIconDelegate {
property int itemIndex: index
lassoActive: root.lassoActive
}
}
}
DesktopIconContextMenu {
id: desktopMenu
onOpenFileRequested: (path, isDir) => root.exec(path, isDir)
onRenameRequested: path => {
root.editingFilePath = path;
}
}
BackgroundContextMenu {
id: bgContextMenu
}
}
+66
View File
@@ -0,0 +1,66 @@
import QtQuick
import QtQuick.Shapes
import qs.Components
import qs.Config
ShapePath {
id: root
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real rounding: Appearance.rounding.normal
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathLine {
relativeX: 0
relativeY: -(root.wrapper.height - root.roundingY * 2)
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathLine {
relativeX: root.wrapper.width - root.rounding * 2
relativeY: 0
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.roundingY * 2
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
}
+320
View File
@@ -0,0 +1,320 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import QtQml.Models
import qs.Modules.Dock.Parts
import qs.Components
import qs.Helpers
import qs.Config
Item {
id: root
readonly property int dockContentWidth: TaskbarApps.apps.reduce((sum, app, i) => sum + (app.appId === TaskbarApps.separatorId ? 1 : Config.dock.height) + (i > 0 ? dockRow.spacing : 0), 0)
property bool dragActive: false
property real dragHeight: Config.dock.height
property real dragStartX: 0
property real dragStartY: 0
property real dragWidth: Config.dock.height
property real dragX: 0
property real dragY: 0
property string draggedAppId: ""
property var draggedModelData: null
property bool dropAnimating: false
readonly property int padding: Appearance.padding.small
required property var panels
property var pendingCommitIds: []
readonly property int rounding: Appearance.rounding.large
required property ShellScreen screen
required property PersistentProperties visibilities
property var visualIds: []
function beginVisualDrag(appId, modelData, item) {
const pos = item.mapToItem(root, 0, 0);
root.visualIds = TaskbarApps.apps.map(app => app.appId);
root.draggedAppId = appId;
root.draggedModelData = modelData;
root.dragWidth = item.width;
root.dragHeight = item.height;
root.dragStartX = pos.x;
root.dragStartY = pos.y;
root.dragX = pos.x;
root.dragY = pos.y;
root.dragActive = true;
root.dropAnimating = false;
root.pendingCommitIds = [];
}
function endVisualDrag() {
const ids = root.visualIds.slice();
const finalIndex = root.visualIds.indexOf(root.draggedAppId);
const finalItem = dockRow.itemAtIndex(finalIndex);
// Stop sending drag events now, but keep the proxy alive while it settles.
root.dragActive = false;
// In a dock, the destination delegate should normally be instantiated.
// If not, just finish immediately.
if (!finalItem) {
root.pendingCommitIds = ids;
root.finishVisualDrag();
return;
}
const pos = finalItem.mapToItem(root, 0, 0);
root.pendingCommitIds = ids;
root.dropAnimating = true;
settleX.to = pos.x;
settleY.to = pos.y;
settleAnim.start();
}
function finishVisualDrag() {
const ids = root.pendingCommitIds.slice();
root.dragActive = false;
root.dropAnimating = false;
root.draggedAppId = "";
root.draggedModelData = null;
root.visualIds = [];
root.pendingCommitIds = [];
TaskbarApps.commitVisualOrder(ids);
}
function moveArrayItem(list, from, to) {
const next = list.slice();
const [item] = next.splice(from, 1);
next.splice(to, 0, item);
return next;
}
function previewVisualMove(from, hovered, before) {
let to = hovered + (before ? 0 : 1);
if (to > from)
to -= 1;
to = Math.max(0, Math.min(visualModel.items.count - 1, to));
if (from === to)
return;
visualModel.items.move(from, to);
root.visualIds = moveArrayItem(root.visualIds, from, to);
}
implicitHeight: Config.dock.height + root.padding * 2
implicitWidth: dockRow.contentWidth + root.padding * 2
ParallelAnimation {
id: settleAnim
onFinished: root.finishVisualDrag()
Anim {
id: settleX
duration: Appearance.anim.durations.normal
property: "dragX"
target: root
}
Anim {
id: settleY
duration: Appearance.anim.durations.normal
property: "dragY"
target: root
}
}
Component {
id: dockDelegate
DropArea {
id: slot
readonly property string appId: modelData.appId
readonly property bool isSeparator: appId === TaskbarApps.separatorId
required property var modelData
function previewReorder(drag) {
const source = drag.source;
if (!source || !source.appId || source.appId === appId)
return;
const from = source.visualIndex;
const hovered = slot.DelegateModel.itemsIndex;
if (from < 0 || hovered < 0)
return;
root.previewVisualMove(from, hovered, drag.x < width / 2);
}
height: Config.dock.height
width: isSeparator ? 1 : Config.dock.height
ListView.onRemove: removeAnim.start()
onEntered: drag => previewReorder(drag)
onPositionChanged: drag => previewReorder(drag)
SequentialAnimation {
id: removeAnim
ScriptAction {
script: slot.ListView.delayRemove = true
}
ParallelAnimation {
Anim {
property: "opacity"
target: slot
to: 0
}
Anim {
property: "scale"
target: slot
to: 0.5
}
}
ScriptAction {
script: {
slot.ListView.delayRemove = false;
}
}
}
DockAppButton {
id: button
anchors.centerIn: parent
appListRoot: root
appToplevel: modelData
visibilities: root.visibilities
visible: root.draggedAppId !== slot.appId
}
DragHandler {
id: dragHandler
enabled: !slot.isSeparator
grabPermissions: PointerHandler.CanTakeOverFromAnything
target: null
xAxis.enabled: true
yAxis.enabled: false
onActiveChanged: {
if (active) {
root.beginVisualDrag(slot.appId, slot.modelData, button);
} else if (root.draggedAppId === slot.appId) {
dragProxy.Drag.drop();
root.endVisualDrag();
}
}
onActiveTranslationChanged: {
if (!active || root.draggedAppId !== slot.appId)
return;
root.dragX = root.dragStartX + activeTranslation.x;
root.dragY = root.dragStartY + activeTranslation.y;
}
}
}
}
DelegateModel {
id: visualModel
delegate: dockDelegate
model: ScriptModel {
objectProp: "appId"
values: TaskbarApps.apps
}
}
CustomListView {
id: dockRow
property bool enableAddAnimation: false
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.top: parent.top
boundsBehavior: Flickable.StopAtBounds
height: Config.dock.height
implicitWidth: root.dockContentWidth + Config.dock.height
interactive: !(root.dragActive || root.dropAnimating)
model: visualModel
orientation: ListView.Horizontal
spacing: Appearance.padding.smaller
add: Transition {
ParallelAnimation {
Anim {
duration: dockRow.enableAddAnimation ? Appearance.anim.durations.normal : 0
from: 0
property: "opacity"
to: 1
}
Anim {
duration: dockRow.enableAddAnimation ? Appearance.anim.durations.normal : 0
from: 0.5
property: "scale"
to: 1
}
}
}
displaced: Transition {
Anim {
duration: Appearance.anim.durations.small
properties: "x,y"
}
}
move: Transition {
Anim {
duration: Appearance.anim.durations.small
properties: "x,y"
}
}
Component.onCompleted: {
Qt.callLater(() => enableAddAnimation = true);
}
}
Item {
id: dragProxy
property string appId: root.draggedAppId
property int visualIndex: root.visualIds.indexOf(root.draggedAppId)
Drag.active: root.dragActive
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
Drag.source: dragProxy
height: root.dragHeight
visible: (root.dragActive || root.dropAnimating) && !!root.draggedModelData
width: root.dragWidth
x: root.dragX
y: root.dragY
z: 9999
DockAppButton {
anchors.fill: parent
appListRoot: root
appToplevel: root.draggedModelData
enabled: false
visibilities: root.visibilities
}
}
}
+91
View File
@@ -0,0 +1,91 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import qs.Components
import qs.Helpers
import qs.Config
CustomRect {
id: root
property bool appIsActive: appToplevel?.toplevels.find(t => (t.activated == true)) !== undefined
property var appListRoot
property var appToplevel
property real countDotHeight: 4
property real countDotWidth: 10
property var desktopEntry: DesktopEntries.heuristicLookup(appToplevel?.appId)
property real iconSize: implicitHeight - 20
readonly property bool isSeparator: appToplevel?.appId === "__dock_separator__"
property int lastFocused: -1
required property PersistentProperties visibilities
implicitHeight: Config.dock.height
implicitWidth: isSeparator ? 1 : implicitHeight
radius: Appearance.rounding.normal - Appearance.padding.small
Loader {
active: !isSeparator
anchors.centerIn: parent
sourceComponent: ColumnLayout {
IconImage {
id: icon
Layout.alignment: Qt.AlignHCenter
implicitSize: root.iconSize
source: Quickshell.iconPath(AppSearch.guessIcon(appToplevel?.appId), "image-missing")
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 3
Repeater {
model: Math.min(appToplevel?.toplevels.length, 3)
delegate: Rectangle {
required property int index
color: appIsActive ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3primary
implicitHeight: root.countDotHeight
implicitWidth: (appToplevel?.toplevels.length <= 3) ? root.countDotWidth : root.countDotHeight // Circles when too many
radius: Appearance.rounding.full
}
}
}
}
}
StateLayer {
onClicked: {
if (appToplevel?.toplevels.length === 0) {
root.desktopEntry?.execute();
root.visibilities.dock = false;
return;
}
lastFocused = (lastFocused + 1) % appToplevel?.toplevels.length;
appToplevel?.toplevels[lastFocused].activate();
root.visibilities.dock = false;
}
}
Connections {
function onApplicationsChanged() {
root.desktopEntry = DesktopEntries.heuristicLookup(appToplevel?.appId);
}
target: DesktopEntries
}
Loader {
active: isSeparator
sourceComponent: DockSeparator {
}
anchors {
fill: parent
}
}
}
+12
View File
@@ -0,0 +1,12 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
CustomRect {
Layout.bottomMargin: dockRow.padding + Appearance.rounding.normal
Layout.fillHeight: true
Layout.topMargin: dockRow.padding + Appearance.rounding.normal
color: DynamicColors.palette.m3outlineVariant
implicitWidth: 1
}
+107
View File
@@ -0,0 +1,107 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import qs.Components
import qs.Config
Item {
id: root
property int contentHeight
required property var panels
required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.dock
required property PersistentProperties visibilities
implicitHeight: 0
implicitWidth: content.implicitWidth
visible: height > 0
Behavior on implicitWidth {
Anim {
duration: Appearance.anim.durations.small
}
}
onShouldBeActiveChanged: {
if (shouldBeActive) {
timer.stop();
hideAnim.stop();
showAnim.start();
} else {
showAnim.stop();
hideAnim.start();
}
}
SequentialAnimation {
id: showAnim
Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
property: "implicitHeight"
target: root
to: root.contentHeight
}
ScriptAction {
script: root.implicitHeight = Qt.binding(() => content.implicitHeight)
}
}
SequentialAnimation {
id: hideAnim
ScriptAction {
script: root.implicitHeight = root.implicitHeight
}
Anim {
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
property: "implicitHeight"
target: root
to: 0
}
}
Timer {
id: timer
interval: Appearance.anim.durations.small
onRunningChanged: {
if (running && !root.shouldBeActive) {
content.visible = false;
content.active = true;
} else {
content.active = Qt.binding(() => root.shouldBeActive || root.visible);
content.visible = true;
if (showAnim.running) {
showAnim.stop();
showAnim.start();
}
}
}
}
Loader {
id: content
active: false
anchors.left: parent.left
anchors.top: parent.top
visible: false
sourceComponent: Content {
panels: root.panels
screen: root.screen
visibilities: root.visibilities
Component.onCompleted: root.contentHeight = implicitHeight
}
Component.onCompleted: timer.start()
}
}
+66
View File
@@ -0,0 +1,66 @@
import QtQuick
import QtQuick.Shapes
import qs.Components
import qs.Config
ShapePath {
id: root
readonly property bool flatten: wrapper.width < rounding * 2
readonly property real rounding: Appearance.rounding.normal
readonly property real roundingX: flatten ? wrapper.width / 2 : rounding
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
relativeX: root.roundingX
relativeY: root.rounding
}
PathLine {
relativeX: root.wrapper.width - root.roundingX * 2
relativeY: 0
}
PathArc {
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
relativeX: root.roundingX
relativeY: root.rounding
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.rounding * 2
}
PathArc {
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
relativeX: -root.roundingX
relativeY: root.rounding
}
PathLine {
relativeX: -(root.wrapper.width - root.roundingX * 2)
relativeY: 0
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
relativeX: -root.roundingX
relativeY: root.rounding
}
}
+151
View File
@@ -0,0 +1,151 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Components
Item {
id: root
readonly property var colors: ["#ef4444", "#f97316", "#eab308", "#22c55e", "#06b6d4", "#3b82f6", "#a855f7", "#ec4899", "#ffffff", "#000000"]
required property Canvas drawing
required property var visibilities
function syncFromPenColor() {
if (!drawing)
return;
if (!saturationSlider.pressed)
saturationSlider.value = drawing.penColor.hsvSaturation;
if (!brightnessSlider.pressed)
brightnessSlider.value = drawing.penColor.hsvValue;
}
function updatePenColorFromHsv() {
if (!drawing)
return;
drawing.penColor = Qt.hsva(huePicker.currentHue, saturationSlider.value, brightnessSlider.value, drawing.penColor.a);
}
implicitHeight: column.height + Appearance.padding.larger * 2
implicitWidth: huePicker.implicitWidth + Appearance.padding.normal * 2
Component.onCompleted: syncFromPenColor()
Connections {
function onPenColorChanged() {
root.syncFromPenColor();
}
target: root.drawing
}
Column {
id: column
anchors.centerIn: parent
spacing: 12
ColorArcPicker {
id: huePicker
drawing: root.drawing
}
GradientSlider {
id: saturationSlider
brightness: brightnessSlider.value
channel: "saturation"
from: 0
hue: huePicker.currentHue
icon: "\ue40a"
implicitHeight: 30
implicitWidth: palette.width
orientation: Qt.Horizontal
to: 1
onMoved: root.updatePenColorFromHsv()
}
GradientSlider {
id: brightnessSlider
channel: "brightness"
from: 0
hue: huePicker.currentHue
icon: "\ue1ac"
implicitHeight: 30
implicitWidth: palette.width
orientation: Qt.Horizontal
saturation: saturationSlider.value
to: 1
onMoved: root.updatePenColorFromHsv()
}
GridLayout {
id: palette
anchors.left: parent.left
anchors.right: parent.right
columns: 5
rowSpacing: 8
rows: 2
Repeater {
model: root.colors
delegate: Item {
id: colorCircle
required property color modelData
readonly property bool selected: Qt.colorEqual(root.drawing.penColor, modelData)
Layout.fillWidth: true
height: 28
CustomRect {
anchors.centerIn: parent
border.color: Qt.rgba(0, 0, 0, 0.25)
border.width: Qt.colorEqual(modelData, "#ffffff") ? 1 : 0
color: colorCircle.modelData
height: 20
radius: width / 2
width: 20
}
CustomRect {
anchors.centerIn: parent
border.color: selected ? "#ffffff" : Qt.rgba(1, 1, 1, 0.28)
border.width: selected ? 3 : 1
color: "transparent"
height: parent.height
radius: width / 2
width: parent.height
StateLayer {
onClicked: root.drawing.penColor = colorCircle.modelData
}
}
}
}
}
FilledSlider {
from: 1
icon: "border_color"
implicitHeight: 30
implicitWidth: palette.width
multiplier: 1
orientation: Qt.Horizontal
to: 45
value: root.drawing.penWidth
onMoved: root.drawing.penWidth = value
}
}
}
+133
View File
@@ -0,0 +1,133 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import qs.Components
import qs.Helpers
import qs.Config
import qs.Daemons
Item {
id: root
required property Canvas drawing
property bool expanded: true
required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.isDrawing
required property var visibilities
implicitHeight: content.implicitHeight
implicitWidth: 0
visible: width > 0
states: [
State {
name: "hidden"
when: !root.shouldBeActive
PropertyChanges {
root.implicitWidth: 0
}
PropertyChanges {
icon.opacity: 0
}
PropertyChanges {
content.opacity: 0
}
},
State {
name: "collapsed"
when: root.shouldBeActive && !root.expanded
PropertyChanges {
root.implicitWidth: icon.implicitWidth
}
PropertyChanges {
icon.opacity: 1
}
PropertyChanges {
content.opacity: 0
}
},
State {
name: "visible"
when: root.shouldBeActive && root.expanded
PropertyChanges {
root.implicitWidth: content.implicitWidth
}
PropertyChanges {
icon.opacity: 0
}
PropertyChanges {
content.opacity: 1
}
}
]
transitions: [
Transition {
from: "*"
to: "*"
ParallelAnimation {
Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitWidth"
target: root
}
Anim {
duration: Appearance.anim.durations.small
property: "opacity"
target: icon
}
Anim {
duration: Appearance.anim.durations.small
property: "opacity"
target: content
}
}
}
]
onVisibleChanged: {
if (!visible)
root.expanded = true;
}
Loader {
id: icon
active: Qt.binding(() => root.shouldBeActive || root.visible)
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
height: content.contentItem.height
opacity: 1
sourceComponent: MaterialIcon {
font.pointSize: Appearance.font.size.larger
text: "arrow_forward_ios"
}
}
Loader {
id: content
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
sourceComponent: Content {
drawing: root.drawing
visibilities: root.visibilities
}
Component.onCompleted: active = Qt.binding(() => root.shouldBeActive || root.visible)
}
}
+16
View File
@@ -131,6 +131,14 @@ CustomListView {
model.values: [0] model.values: [0]
root.delegate: calcItem root.delegate: calcItem
} }
},
State {
name: "variant"
PropertyChanges {
model.values: SchemeVariants.query(search.text)
root.delegate: variantItem
}
} }
] ]
transitions: Transition { transitions: Transition {
@@ -211,4 +219,12 @@ CustomListView {
list: root list: root
} }
} }
Component {
id: variantItem
VariantItem {
list: root
}
}
} }
-5
View File
@@ -123,11 +123,6 @@ Item {
search.text = ""; search.text = "";
} }
function onSessionChanged(): void {
if (!root.visibilities.session)
search.forceActiveFocus();
}
target: root.visibilities target: root.visibilities
} }
} }
+74
View File
@@ -0,0 +1,74 @@
import QtQuick
import qs.Components
import qs.Modules.Launcher.Services
import qs.Config
Item {
id: root
required property var list
required property SchemeVariants.Variant modelData
anchors.left: parent?.left
anchors.right: parent?.right
implicitHeight: Config.launcher.sizes.itemHeight
StateLayer {
function onClicked(): void {
root.modelData?.onClicked(root.list);
}
radius: Appearance.rounding.normal
}
Item {
anchors.fill: parent
anchors.leftMargin: Appearance.padding.larger
anchors.margins: Appearance.padding.smaller
anchors.rightMargin: Appearance.padding.larger
MaterialIcon {
id: icon
anchors.verticalCenter: parent.verticalCenter
font.pointSize: Appearance.font.size.extraLarge
text: root.modelData?.icon ?? ""
}
Column {
anchors.left: icon.right
anchors.leftMargin: Appearance.spacing.larger
anchors.verticalCenter: icon.verticalCenter
spacing: 0
width: parent.width - icon.width - anchors.leftMargin - (current.active ? current.width + Appearance.spacing.normal : 0)
CustomText {
font.pointSize: Appearance.font.size.normal
text: root.modelData?.name ?? ""
}
CustomText {
anchors.left: parent.left
anchors.right: parent.right
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: root.modelData?.description ?? ""
}
}
Loader {
id: current
active: root.modelData?.variant === Config.colors.schemeType
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
sourceComponent: MaterialIcon {
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.large
text: "check"
}
}
}
}
+1 -1
View File
@@ -42,7 +42,7 @@ Searcher {
list.search.text = `${Config.launcher.actionPrefix}${command[1]} `; list.search.text = `${Config.launcher.actionPrefix}${command[1]} `;
} else if (command[0] === "setMode" && command.length > 1) { } else if (command[0] === "setMode" && command.length > 1) {
list.visibilities.launcher = false; list.visibilities.launcher = false;
Colours.setMode(command[1]); DynamicColors.setMode(command[1]);
} else { } else {
list.visibilities.launcher = false; list.visibilities.launcher = false;
Quickshell.execDetached(command); Quickshell.execDetached(command);
@@ -0,0 +1,88 @@
pragma Singleton
import Quickshell
import QtQuick
import qs.Modules.Launcher
import qs.Config
import qs.Helpers
Searcher {
id: root
function transformSearch(search: string): string {
return search.slice(`${Config.launcher.actionPrefix}variant `.length);
}
useFuzzy: Config.launcher.useFuzzy.variants
list: [
Variant {
description: qsTr("Maximum chroma at each tone.")
icon: "sentiment_very_dissatisfied"
name: qsTr("Vibrant")
variant: "vibrant"
},
Variant {
description: qsTr("Pastel palette with a low chroma.")
icon: "android"
name: qsTr("Tonal Spot")
variant: "tonalspot"
},
Variant {
description: qsTr("Hue-shifted, artistic or playful colors.")
icon: "compare_arrows"
name: qsTr("Expressive")
variant: "expressive"
},
Variant {
description: qsTr("Preserve source color exactly.")
icon: "compare"
name: qsTr("Fidelity")
variant: "fidelity"
},
Variant {
description: qsTr("Almost identical to fidelity.")
icon: "sentiment_calm"
name: qsTr("Content")
variant: "content"
},
Variant {
description: qsTr("The seed colour's hue does not appear in the theme.")
icon: "nutrition"
name: qsTr("Fruit Salad")
variant: "fruit-salad"
},
Variant {
description: qsTr("Like Fruit Salad but different hues.")
icon: "looks"
name: qsTr("Rainbow")
variant: "rainbow"
},
Variant {
description: qsTr("Close to grayscale, a hint of chroma.")
icon: "contrast"
name: qsTr("Neutral")
variant: "neutral"
},
Variant {
description: qsTr("All colours are grayscale, no chroma.")
icon: "filter_b_and_w"
name: qsTr("Monochrome")
variant: "monochrome"
}
]
component Variant: QtObject {
required property string description
required property string icon
required property string name
required property string variant
function onClicked(list: AppList): void {
list.visibilities.launcher = false;
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--scheme", variant]);
Config.colors.schemeType = variant;
Config.save();
}
}
}
+4 -2
View File
@@ -10,8 +10,10 @@ Item {
property int contentHeight property int contentHeight
readonly property real maxHeight: { readonly property real maxHeight: {
let max = screen.height - Appearance.spacing.large; let max = screen.height - Appearance.spacing.large * 2;
if (visibilities.dashboard) if (visibilities.resources)
max -= panels.resources.nonAnimHeight;
if (visibilities.dashboard && panels.dashboard.x < root.x + root.implicitWidth)
max -= panels.dashboard.nonAnimHeight; max -= panels.dashboard.nonAnimHeight;
return max; return max;
} }
+15
View File
@@ -164,6 +164,21 @@ WlSessionLockSurface {
implicitWidth: size implicitWidth: size
scale: 0 scale: 0
// MultiEffect {
// anchors.fill: lockBg
// autoPaddingEnabled: false
// blur: 1
// blurEnabled: true
// blurMax: 64
// maskEnabled: true
// maskSource: lockBg
//
// source: ShaderEffectSource {
// sourceItem: background
// sourceRect: Qt.rect(lockBg.x, lockBg.y, lockBg.width, lockBg, height)
// }
// }
CustomRect { CustomRect {
id: lockBg id: lockBg
+6 -18
View File
@@ -5,30 +5,22 @@ import qs.Daemons
import qs.Config import qs.Config
import qs.Helpers import qs.Helpers
Item { CustomRect {
id: root id: root
readonly property string currentMedia: (Players.active?.trackTitle ?? qsTr("No media")) || qsTr("Unknown title") readonly property string currentMedia: (Players.active?.trackTitle ?? qsTr("No media")) || qsTr("Unknown title")
readonly property int textWidth: Math.min(metrics.width, 200) readonly property int textWidth: Math.min(metrics.width, 200)
anchors.bottom: parent.bottom color: DynamicColors.tPalette.m3surfaceContainer
anchors.top: parent.top implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
implicitWidth: layout.implicitWidth + Appearance.padding.normal * 2 implicitWidth: layout.implicitWidth + Appearance.padding.normal * 2
radius: Appearance.rounding.full
Behavior on implicitWidth { Behavior on implicitWidth {
Anim { Anim {
} }
} }
CustomRect {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: 22
radius: Appearance.rounding.full
}
TextMetrics { TextMetrics {
id: metrics id: metrics
@@ -39,11 +31,7 @@ Item {
RowLayout { RowLayout {
id: layout id: layout
anchors.bottom: parent.bottom anchors.centerIn: parent
anchors.horizontalCenter: parent.horizontalCenter
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.normal
anchors.top: parent.top
Behavior on implicitWidth { Behavior on implicitWidth {
Anim { Anim {
@@ -53,7 +41,7 @@ Item {
MaterialIcon { MaterialIcon {
animate: true animate: true
color: Players.active?.isPlaying ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurface color: Players.active?.isPlaying ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurface
font.pointSize: 14 font.pointSize: Appearance.font.size.larger
text: Players.active?.isPlaying ? "music_note" : "music_off" text: Players.active?.isPlaying ? "music_note" : "music_off"
} }
+6 -13
View File
@@ -5,22 +5,16 @@ import qs.Config
import qs.Helpers import qs.Helpers
import qs.Components import qs.Components
Item { CustomRect {
id: root id: root
required property Wrapper popouts required property Wrapper popouts
required property PersistentProperties visibilities required property PersistentProperties visibilities
anchors.bottom: parent.bottom color: DynamicColors.tPalette.m3surfaceContainer
anchors.top: parent.top implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
implicitWidth: 30 implicitWidth: implicitHeight
radius: Appearance.rounding.full
CustomRect {
anchors.bottomMargin: 3
anchors.fill: parent
anchors.topMargin: 3
color: "transparent"
radius: 4
MaterialIcon { MaterialIcon {
id: notificationCenterIcon id: notificationCenterIcon
@@ -30,7 +24,7 @@ Item {
anchors.centerIn: parent anchors.centerIn: parent
color: iconColor color: iconColor
font.family: "Material Symbols Rounded" font.family: "Material Symbols Rounded"
font.pointSize: 16 font.pointSize: Appearance.font.size.larger
text: HasNotifications.hasNotifications ? "\uf4fe" : "\ue7f4" text: HasNotifications.hasNotifications ? "\uf4fe" : "\ue7f4"
Behavior on color { Behavior on color {
@@ -47,4 +41,3 @@ Item {
} }
} }
} }
}
+1 -1
View File
@@ -15,7 +15,7 @@ ShapePath {
readonly property real utilsWidthDiff: panels.utilities.width - wrapper.width readonly property real utilsWidthDiff: panels.utilities.width - wrapper.width
required property Wrapper wrapper required property Wrapper wrapper
fillColor: flatten ? "transparent" : DynamicColors.palette.m3surface fillColor: DynamicColors.palette.m3surface
strokeWidth: -1 strokeWidth: -1
Behavior on fillColor { Behavior on fillColor {
+1 -1
View File
@@ -14,7 +14,7 @@ Item {
states: State { states: State {
name: "hidden" name: "hidden"
when: root.visibilities.sidebar when: root.visibilities.sidebar || root.visibilities.dashboard || (root.panels.popouts.hasCurrent && root.panels.popouts.currentName.startsWith("traymenu"))
PropertyChanges { PropertyChanges {
root.implicitHeight: 0 root.implicitHeight: 0
+4
View File
@@ -113,6 +113,9 @@ Item {
} }
} }
CustomClippingRect {
anchors.fill: parent
Loader { Loader {
id: content id: content
@@ -132,3 +135,4 @@ Item {
Component.onCompleted: active = Qt.binding(() => root.shouldBeActive || root.visible) Component.onCompleted: active = Qt.binding(() => root.shouldBeActive || root.visible)
} }
} }
}
+35 -85
View File
@@ -4,112 +4,62 @@ import QtQuick.Shapes
import qs.Components import qs.Components
import qs.Config import qs.Config
Item { RowLayout {
id: root id: root
property color borderColor: warning ? DynamicColors.palette.m3onError : mainColor property color accentColor: warning ? DynamicColors.palette.m3error : mainColor
property real animatedPercentage: 0
readonly property real arcStartAngle: 0.75 * Math.PI
readonly property real arcSweep: 1.5 * Math.PI
property string icon
required property color mainColor required property color mainColor
required property double percentage required property double percentage
property bool shown: true property bool shown: true
property color usageColor: warning ? DynamicColors.palette.m3error : mainColor property color usageColor: warning ? DynamicColors.palette.m3error : mainColor
property bool warning: percentage * 100 >= warningThreshold property bool warning: percentage * 100 >= warningThreshold
property int warningThreshold: 100 property int warningThreshold: 80
clip: true percentage: 0
implicitHeight: 22
implicitWidth: resourceRowLayout.x < 0 ? 0 : resourceRowLayout.implicitWidth
visible: width > 0 && height > 0
Behavior on percentage { Behavior on animatedPercentage {
NumberAnimation { Anim {
duration: 300
easing.type: Easing.InOutQuad
} }
} }
RowLayout { Component.onCompleted: animatedPercentage = percentage
id: resourceRowLayout onPercentageChanged: {
const next = percentage;
spacing: 2 if (Math.abs(next - animatedPercentage) >= 0.05)
x: shown ? 0 : -resourceRowLayout.width animatedPercentage = next;
anchors {
verticalCenter: parent.verticalCenter
} }
Item { MaterialIcon {
Layout.alignment: Qt.AlignVCenter id: icon
implicitHeight: root.implicitHeight
implicitWidth: 14
Rectangle { color: DynamicColors.palette.m3onSurface
id: backgroundCircle font.pointSize: Appearance.font.size.larger
text: root.icon
anchors.centerIn: parent
border.color: "#404040"
border.width: 1
color: "#40000000"
height: 14
radius: height / 2
width: 14
} }
Shape { CustomClippingRect {
anchors.fill: backgroundCircle Layout.preferredHeight: root.height - Appearance.padding.small
preferredRendererType: Shape.CurveRenderer Layout.preferredWidth: 4
smooth: true color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
radius: Appearance.rounding.full
ShapePath { CustomRect {
fillColor: root.usageColor id: fill
startX: backgroundCircle.width / 2
startY: backgroundCircle.height / 2
strokeWidth: 0
Behavior on fillColor { anchors.fill: parent
CAnim { antialiasing: false
} color: root.mainColor
} implicitHeight: Math.ceil(root.percentage * parent.height)
radius: Appearance.rounding.full
PathLine { transform: Scale {
x: backgroundCircle.width / 2 origin.y: fill.height
y: 0 + (1 / 2) yScale: Math.max(0.001, root.animatedPercentage)
}
PathAngleArc {
centerX: backgroundCircle.width / 2
centerY: backgroundCircle.height / 2
radiusX: backgroundCircle.width / 2 - (1 / 2)
radiusY: backgroundCircle.height / 2 - (1 / 2)
startAngle: -90
sweepAngle: 360 * root.percentage
}
PathLine {
x: backgroundCircle.width / 2
y: backgroundCircle.height / 2
}
}
ShapePath {
capStyle: ShapePath.FlatCap
fillColor: "transparent"
strokeColor: root.borderColor
strokeWidth: 1
Behavior on strokeColor {
CAnim {
}
}
PathAngleArc {
centerX: backgroundCircle.width / 2
centerY: backgroundCircle.height / 2
radiusX: backgroundCircle.width / 2 - (1 / 2)
radiusY: backgroundCircle.height / 2 - (1 / 2)
startAngle: -90
sweepAngle: 360 * root.percentage
}
}
} }
} }
} }
-77
View File
@@ -1,77 +0,0 @@
import Quickshell
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Components
Item {
id: root
property color barColor: DynamicColors.palette.m3primary
required property string details
required property string iconString
required property double percentage
required property string resourceName
property color textColor: DynamicColors.palette.m3onSurface
property color warningBarColor: DynamicColors.palette.m3error
required property int warningThreshold
Layout.preferredHeight: columnLayout.implicitHeight
Layout.preferredWidth: 158
ColumnLayout {
id: columnLayout
anchors.fill: parent
spacing: 4
Row {
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
spacing: 6
MaterialIcon {
color: root.textColor
font.family: "Material Symbols Rounded"
font.pointSize: 28
text: root.iconString
}
CustomText {
anchors.verticalCenter: parent.verticalCenter
color: root.textColor
font.pointSize: 12
text: root.resourceName
}
}
Rectangle {
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
Layout.preferredHeight: 6
color: "#40000000"
radius: height / 2
Rectangle {
color: root.percentage * 100 >= root.warningThreshold ? root.warningBarColor : root.barColor
height: parent.height
radius: height / 2
width: parent.width * Math.min(root.percentage, 1)
Behavior on width {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
}
}
CustomText {
Layout.alignment: Qt.AlignLeft
color: root.textColor
font.pointSize: 10
text: root.details
}
}
}
-59
View File
@@ -1,59 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Config
Item {
id: popoutWindow
required property var wrapper
implicitHeight: contentColumn.implicitHeight + 10
implicitWidth: contentColumn.implicitWidth + 10 * 2
// ShadowRect {
// anchors.fill: contentRect
// radius: 8
// }
ColumnLayout {
id: contentColumn
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: 10
ResourceDetail {
details: qsTr("%1 of %2 MB used").arg(Math.round(ResourceUsage.memoryUsed * 0.001)).arg(Math.round(ResourceUsage.memoryTotal * 0.001))
iconString: "\uf7a3"
percentage: ResourceUsage.memoryUsedPercentage
resourceName: qsTr("Memory Usage")
warningThreshold: 95
}
ResourceDetail {
details: qsTr("%1% used").arg(Math.round(ResourceUsage.cpuUsage * 100))
iconString: "\ue322"
percentage: ResourceUsage.cpuUsage
resourceName: qsTr("CPU Usage")
warningThreshold: 95
}
ResourceDetail {
details: qsTr("%1% used").arg(Math.round(ResourceUsage.gpuUsage * 100))
iconString: "\ue30f"
percentage: ResourceUsage.gpuUsage
resourceName: qsTr("GPU Usage")
warningThreshold: 95
}
ResourceDetail {
details: qsTr("%1% used").arg(Math.round(ResourceUsage.gpuMemUsage * 100))
iconString: "\ue30d"
percentage: ResourceUsage.gpuMemUsage
resourceName: qsTr("VRAM Usage")
warningThreshold: 95
}
}
}
-123
View File
@@ -1,123 +0,0 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Config
Singleton {
id: root
property string autoGpuType: "NONE"
property double cpuUsage: 0
property double gpuMemUsage: 0
readonly property string gpuType: Config.services.gpuType.toUpperCase() || autoGpuType
property double gpuUsage: 0
property double memoryFree: 1
property double memoryTotal: 1
property double memoryUsed: memoryTotal - memoryFree
property double memoryUsedPercentage: memoryUsed / memoryTotal
property var previousCpuStats
property double swapFree: 1
property double swapTotal: 1
property double swapUsed: swapTotal - swapFree
property double swapUsedPercentage: swapTotal > 0 ? (swapUsed / swapTotal) : 0
property double totalMem: 0
Timer {
interval: 1
repeat: true
running: true
onTriggered: {
// Reload files
fileMeminfo.reload();
fileStat.reload();
// Parse memory and swap usage
const textMeminfo = fileMeminfo.text();
memoryTotal = Number(textMeminfo.match(/MemTotal: *(\d+)/)?.[1] ?? 1);
memoryFree = Number(textMeminfo.match(/MemAvailable: *(\d+)/)?.[1] ?? 0);
swapTotal = Number(textMeminfo.match(/SwapTotal: *(\d+)/)?.[1] ?? 1);
swapFree = Number(textMeminfo.match(/SwapFree: *(\d+)/)?.[1] ?? 0);
// Parse CPU usage
const textStat = fileStat.text();
const cpuLine = textStat.match(/^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/);
if (cpuLine) {
const stats = cpuLine.slice(1).map(Number);
const total = stats.reduce((a, b) => a + b, 0);
const idle = stats[3];
if (previousCpuStats) {
const totalDiff = total - previousCpuStats.total;
const idleDiff = idle - previousCpuStats.idle;
cpuUsage = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0;
}
previousCpuStats = {
total,
idle
};
}
if (root.gpuType === "NVIDIA") {
processGpu.running = true;
}
interval = 3000;
}
}
FileView {
id: fileMeminfo
path: "/proc/meminfo"
}
FileView {
id: fileStat
path: "/proc/stat"
}
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" && totalMem === 0
stdout: StdioCollector {
onStreamFinished: {
totalMem = Number(this.text.trim());
oneshotMem.running = false;
}
}
}
Process {
id: processGpu
command: ["nvidia-smi", "--query-gpu=utilization.gpu,memory.used", "--format=csv,noheader,nounits"]
running: false
stdout: StdioCollector {
onStreamFinished: {
const parts = this.text.trim().split(", ");
gpuUsage = Number(parts[0]) / 100;
gpuMemUsage = Number(parts[1]) / totalMem;
}
}
}
}
+20 -48
View File
@@ -3,94 +3,66 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell.Wayland import qs.Helpers
import qs.Modules import qs.Modules
import qs.Config import qs.Config
import qs.Effects
import qs.Components import qs.Components
Item { CustomRect {
id: root id: root
required property PersistentProperties visibilities required property PersistentProperties visibilities
clip: true clip: true
implicitHeight: 34
implicitWidth: rowLayout.implicitWidth + Appearance.padding.small * 2
CustomRect {
id: backgroundRect
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: 22 implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
implicitWidth: rowLayout.implicitWidth + Appearance.padding.normal * 2
radius: height / 2 radius: height / 2
anchors {
left: parent.left
right: parent.right
verticalCenter: parent.verticalCenter
}
StateLayer { StateLayer {
onClicked: root.visibilities.resources = !root.visibilities.resources onClicked: root.visibilities.resources = !root.visibilities.resources
} }
}
RowLayout { RowLayout {
id: rowLayout id: rowLayout
anchors.centerIn: parent anchors.centerIn: parent
spacing: 6 implicitHeight: root.implicitHeight
spacing: Appearance.spacing.smaller
MaterialIcon { Ref {
Layout.alignment: Qt.AlignVCenter service: SystemUsage
color: DynamicColors.palette.m3onSurface
font.pointSize: 14
text: "memory_alt"
} }
Resource { Resource {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.fillHeight: true
icon: "memory"
mainColor: DynamicColors.palette.m3primary mainColor: DynamicColors.palette.m3primary
percentage: ResourceUsage.memoryUsedPercentage percentage: SystemUsage.cpuPerc
warningThreshold: 95 warningThreshold: 95
} }
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
color: DynamicColors.palette.m3onSurface
font.pointSize: 14
text: "memory"
}
Resource { Resource {
Layout.fillHeight: true
icon: "memory_alt"
mainColor: DynamicColors.palette.m3secondary mainColor: DynamicColors.palette.m3secondary
percentage: ResourceUsage.cpuUsage percentage: SystemUsage.memPerc
warningThreshold: 80 warningThreshold: 80
} }
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
color: DynamicColors.palette.m3onSurface
font.pointSize: 14
text: "gamepad"
}
Resource { Resource {
Layout.fillHeight: true
icon: "gamepad"
mainColor: DynamicColors.palette.m3tertiary mainColor: DynamicColors.palette.m3tertiary
percentage: ResourceUsage.gpuUsage percentage: SystemUsage.gpuPerc
}
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
color: DynamicColors.palette.m3onSurface
font.pointSize: 14
text: "developer_board"
} }
Resource { Resource {
Layout.fillHeight: true
icon: "developer_board"
mainColor: DynamicColors.palette.m3primary mainColor: DynamicColors.palette.m3primary
percentage: ResourceUsage.gpuMemUsage percentage: SystemUsage.gpuMemUsed
} }
} }
} }
+2 -3
View File
@@ -28,15 +28,14 @@ ShapePath {
PathLine { PathLine {
relativeX: 0 relativeX: 0
relativeY: root.wrapper.height - root.roundingY * 2 relativeY: root.wrapper.height
} }
PathArc { PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height) radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding relativeX: root.rounding
relativeY: root.roundingY relativeY: -root.roundingY
} }
PathLine { PathLine {
+66
View File
@@ -0,0 +1,66 @@
import QtQuick
import QtQuick.Shapes
import qs.Components
import qs.Config
ShapePath {
id: root
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real rounding: Appearance.rounding.large
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.roundingY, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.roundingY * 2
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: root.wrapper.width - root.rounding * 2
relativeY: 0
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathLine {
relativeX: 0
relativeY: -(root.wrapper.height - root.roundingY * 2)
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
}
+188
View File
@@ -0,0 +1,188 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Widgets
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Modules as Modules
import qs.Config
import qs.Helpers
Item {
id: root
required property Item content
implicitHeight: clayout.contentHeight + Appearance.padding.smaller * 2
implicitWidth: clayout.contentWidth + Appearance.padding.smaller * 2
ListModel {
id: listModel
ListElement {
icon: "settings"
key: "general"
name: "General"
}
ListElement {
icon: "wallpaper"
key: "wallpaper"
name: "Wallpaper"
}
ListElement {
icon: "settop_component"
key: "bar"
name: "Bar"
}
ListElement {
icon: "lock"
key: "lockscreen"
name: "Lockscreen"
}
ListElement {
icon: "build_circle"
key: "services"
name: "Services"
}
ListElement {
icon: "notifications"
key: "notifications"
name: "Notifications"
}
ListElement {
icon: "view_sidebar"
key: "sidebar"
name: "Sidebar"
}
ListElement {
icon: "handyman"
key: "utilities"
name: "Utilities"
}
ListElement {
icon: "dashboard"
key: "dashboard"
name: "Dashboard"
}
ListElement {
icon: "colors"
key: "appearance"
name: "Appearance"
}
ListElement {
icon: "display_settings"
key: "osd"
name: "On screen display"
}
ListElement {
icon: "rocket_launch"
key: "launcher"
name: "Launcher"
}
}
CustomClippingRect {
anchors.fill: parent
color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.normal
CustomListView {
id: clayout
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.margins: Appearance.padding.smaller
anchors.top: parent.top
boundsBehavior: Flickable.StopAtBounds
contentWidth: contentItem.childrenRect.width
highlightFollowsCurrentItem: false
implicitWidth: contentItem.childrenRect.width
model: listModel
spacing: 5
delegate: Category {
}
highlight: CustomRect {
color: DynamicColors.palette.m3primary
implicitHeight: clayout.currentItem?.implicitHeight ?? 0
implicitWidth: clayout.width
radius: Appearance.rounding.normal - Appearance.padding.smaller
y: clayout.currentItem?.y ?? 0
Behavior on y {
Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
}
}
}
}
component Category: CustomRect {
id: categoryItem
required property string icon
required property int index
required property string key
required property string name
implicitHeight: 42
implicitWidth: 200
radius: Appearance.rounding.normal - Appearance.padding.smaller
RowLayout {
id: layout
anchors.left: parent.left
anchors.margins: Appearance.padding.smaller
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
MaterialIcon {
id: icon
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillHeight: true
Layout.preferredWidth: icon.contentWidth
color: categoryItem.index === clayout.currentIndex ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
font.pointSize: Appearance.font.size.small * 2
text: categoryItem.icon
verticalAlignment: Text.AlignVCenter
}
CustomText {
id: text
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillHeight: true
Layout.fillWidth: true
Layout.leftMargin: Appearance.spacing.normal
color: categoryItem.index === clayout.currentIndex ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
text: categoryItem.name
verticalAlignment: Text.AlignVCenter
}
}
StateLayer {
id: layer
onClicked: {
root.content.currentCategory = categoryItem.key;
clayout.currentIndex = categoryItem.index;
}
}
}
}
+159
View File
@@ -0,0 +1,159 @@
import qs.Modules.Settings.Controls
import qs.Config
SettingsPage {
id: root
SettingsSection {
SettingsHeader {
name: "Scale"
}
SettingSpinBox {
name: "Rounding scale"
object: Config.appearance.rounding
setting: "scale"
step: 0.1
}
Separator {
}
SettingSpinBox {
name: "Spacing scale"
object: Config.appearance.spacing
setting: "scale"
step: 0.1
}
Separator {
}
SettingSpinBox {
name: "Padding scale"
object: Config.appearance.padding
setting: "scale"
step: 0.1
}
Separator {
}
SettingSpinBox {
name: "Font size scale"
object: Config.appearance.font.size
setting: "scale"
step: 0.1
}
Separator {
}
SettingSpinBox {
name: "Animation duration scale"
object: Config.appearance.anim.durations
setting: "scale"
step: 0.1
}
}
SettingsSection {
SettingsHeader {
name: "Fonts"
}
SettingInput {
name: "Sans family"
object: Config.appearance.font.family
setting: "sans"
}
Separator {
}
SettingInput {
name: "Monospace family"
object: Config.appearance.font.family
setting: "mono"
}
Separator {
}
SettingInput {
name: "Material family"
object: Config.appearance.font.family
setting: "material"
}
Separator {
}
SettingInput {
name: "Clock family"
object: Config.appearance.font.family
setting: "clock"
}
}
SettingsSection {
SettingsHeader {
name: "Animation"
}
SettingSpinBox {
name: "Media GIF speed adjustment"
object: Config.appearance.anim
setting: "mediaGifSpeedAdjustment"
step: 10
}
Separator {
}
SettingSpinBox {
name: "Session GIF speed"
max: 5
min: 0
object: Config.appearance.anim
setting: "sessionGifSpeed"
step: 0.1
}
}
SettingsSection {
SettingsHeader {
name: "Transparency"
}
SettingSwitch {
name: "Enable transparency"
object: Config.appearance.transparency
setting: "enabled"
}
Separator {
}
SettingSpinBox {
name: "Base opacity"
max: 1
min: 0
object: Config.appearance.transparency
setting: "base"
step: 0.05
}
Separator {
}
SettingSpinBox {
name: "Layer opacity"
max: 1
min: 0
object: Config.appearance.transparency
setting: "layers"
step: 0.05
}
}
}
@@ -0,0 +1,29 @@
import qs.Modules.Settings.Controls
import qs.Config
SettingsPage {
id: root
SettingsSection {
SettingsHeader {
name: "Wallpaper"
}
SettingSwitch {
name: "Enable wallpaper rendering"
object: Config.background
setting: "enabled"
}
Separator {
}
SettingSpinBox {
name: "Fade duration"
min: 0
object: Config.background
setting: "wallFadeDuration"
step: 50
}
}
}
+184
View File
@@ -0,0 +1,184 @@
import qs.Modules.Settings.Controls
import qs.Config
SettingsPage {
SettingsSection {
SettingsHeader {
name: "Bar"
}
SettingSwitch {
name: "Auto hide"
object: Config.barConfig
setting: "autoHide"
}
Separator {
}
SettingSpinBox {
name: "Height"
min: 1
object: Config.barConfig
setting: "height"
}
Separator {
}
SettingSpinBox {
name: "Rounding"
min: 0
object: Config.barConfig
setting: "rounding"
}
Separator {
}
SettingSpinBox {
name: "Border"
min: 0
object: Config.barConfig
setting: "border"
}
}
SettingsSection {
SettingsHeader {
name: "Popouts"
}
SettingSwitch {
name: "Tray"
object: Config.barConfig.popouts
setting: "tray"
}
Separator {
}
SettingSwitch {
name: "Audio"
object: Config.barConfig.popouts
setting: "audio"
}
Separator {
}
SettingSwitch {
name: "Active window"
object: Config.barConfig.popouts
setting: "activeWindow"
}
Separator {
}
SettingSwitch {
name: "Resources"
object: Config.barConfig.popouts
setting: "resources"
}
Separator {
}
SettingSwitch {
name: "Clock"
object: Config.barConfig.popouts
setting: "clock"
}
Separator {
}
SettingSwitch {
name: "Network"
object: Config.barConfig.popouts
setting: "network"
}
Separator {
}
SettingSwitch {
name: "Power"
object: Config.barConfig.popouts
setting: "upower"
}
}
SettingsSection {
SettingsHeader {
name: "Entries"
}
SettingBarEntryList {
name: "Bar entries"
object: Config.barConfig
setting: "entries"
}
}
SettingsSection {
SettingsHeader {
name: "Dock"
}
SettingSwitch {
name: "Enable dock"
object: Config.dock
setting: "enable"
}
Separator {
}
SettingSpinBox {
name: "Dock height"
min: 1
object: Config.dock
setting: "height"
}
Separator {
}
SettingSwitch {
name: "Hover to reveal"
object: Config.dock
setting: "hoverToReveal"
}
Separator {
}
SettingSwitch {
name: "Pin on startup"
object: Config.dock
setting: "pinnedOnStartup"
}
Separator {
}
SettingStringList {
name: "Pinned apps"
addLabel: qsTr("Add pinned app")
object: Config.dock
setting: "pinnedApps"
}
Separator {
}
SettingStringList {
name: "Ignored app regexes"
addLabel: qsTr("Add ignored regex")
object: Config.dock
setting: "ignoredAppRegexes"
}
}
}
+212
View File
@@ -0,0 +1,212 @@
import qs.Modules.Settings.Controls
import qs.Config
SettingsPage {
SettingsSection {
SettingsHeader {
name: "Dashboard"
}
SettingSwitch {
name: "Enable dashboard"
object: Config.dashboard
setting: "enabled"
}
Separator {
}
SettingSpinBox {
name: "Media update interval"
min: 0
object: Config.dashboard
setting: "mediaUpdateInterval"
step: 50
}
Separator {
}
SettingSpinBox {
name: "Resource update interval"
min: 0
object: Config.dashboard
setting: "resourceUpdateInterval"
step: 50
}
Separator {
}
SettingSpinBox {
name: "Drag threshold"
min: 0
object: Config.dashboard
setting: "dragThreshold"
}
}
SettingsSection {
SettingsHeader {
name: "Performance"
}
SettingSwitch {
name: "Show battery"
object: Config.dashboard.performance
setting: "showBattery"
}
Separator {
}
SettingSwitch {
name: "Show GPU"
object: Config.dashboard.performance
setting: "showGpu"
}
Separator {
}
SettingSwitch {
name: "Show CPU"
object: Config.dashboard.performance
setting: "showCpu"
}
Separator {
}
SettingSwitch {
name: "Show memory"
object: Config.dashboard.performance
setting: "showMemory"
}
Separator {
}
SettingSwitch {
name: "Show storage"
object: Config.dashboard.performance
setting: "showStorage"
}
Separator {
}
SettingSwitch {
name: "Show network"
object: Config.dashboard.performance
setting: "showNetwork"
}
}
SettingsSection {
SettingsHeader {
name: "Layout Sizes"
}
SettingReadOnly {
name: "Tab indicator height"
value: String(Config.dashboard.sizes.tabIndicatorHeight)
}
Separator {
}
SettingReadOnly {
name: "Tab indicator spacing"
value: String(Config.dashboard.sizes.tabIndicatorSpacing)
}
Separator {
}
SettingReadOnly {
name: "Info width"
value: String(Config.dashboard.sizes.infoWidth)
}
Separator {
}
SettingReadOnly {
name: "Info icon size"
value: String(Config.dashboard.sizes.infoIconSize)
}
Separator {
}
SettingReadOnly {
name: "Date time width"
value: String(Config.dashboard.sizes.dateTimeWidth)
}
Separator {
}
SettingReadOnly {
name: "Media width"
value: String(Config.dashboard.sizes.mediaWidth)
}
Separator {
}
SettingReadOnly {
name: "Media progress sweep"
value: String(Config.dashboard.sizes.mediaProgressSweep)
}
Separator {
}
SettingReadOnly {
name: "Media progress thickness"
value: String(Config.dashboard.sizes.mediaProgressThickness)
}
Separator {
}
SettingReadOnly {
name: "Resource progress thickness"
value: String(Config.dashboard.sizes.resourceProgessThickness)
}
Separator {
}
SettingReadOnly {
name: "Weather width"
value: String(Config.dashboard.sizes.weatherWidth)
}
Separator {
}
SettingReadOnly {
name: "Media cover art size"
value: String(Config.dashboard.sizes.mediaCoverArtSize)
}
Separator {
}
SettingReadOnly {
name: "Media visualiser size"
value: String(Config.dashboard.sizes.mediaVisualiserSize)
}
Separator {
}
SettingReadOnly {
name: "Resource size"
value: String(Config.dashboard.sizes.resourceSize)
}
}
}
+212
View File
@@ -0,0 +1,212 @@
import qs.Modules.Settings.Controls
import qs.Config
import qs.Components
SettingsPage {
id: root
function schemeTypeItem(items, value) {
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (item.value === value)
return item;
}
return items[0] ?? null;
}
SettingsSection {
SettingsHeader {
name: "General"
}
SettingInput {
name: "Logo"
object: Config.general
setting: "logo"
}
Separator {
}
SettingInput {
name: "Wallpaper path"
object: Config.general
setting: "wallpaperPath"
}
Separator {
}
SettingSwitch {
name: "Desktop icons"
object: Config.general
setting: "desktopIcons"
}
}
SettingsSection {
SettingsHeader {
name: "Color"
}
CustomSplitButtonRow {
active: Config.general.color.mode === "light" ? menuItems[0] : menuItems[1]
label: qsTr("Scheme mode")
menuItems: [
MenuItem {
icon: "light_mode"
text: qsTr("Light")
value: "light"
},
MenuItem {
icon: "dark_mode"
text: qsTr("Dark")
value: "dark"
}
]
onSelected: item => {
Config.general.color.mode = item.value;
Config.save();
}
}
Separator {
}
CustomSplitButtonRow {
id: schemeType
active: root.schemeTypeItem(menuItems, Config.colors.schemeType)
label: qsTr("Scheme type")
z: 2
menuItems: [
MenuItem {
icon: "palette"
text: qsTr("Vibrant")
value: "vibrant"
},
MenuItem {
icon: "gesture"
text: qsTr("Expressive")
value: "expressive"
},
MenuItem {
icon: "contrast"
text: qsTr("Monochrome")
value: "monochrome"
},
MenuItem {
icon: "tonality"
text: qsTr("Neutral")
value: "neutral"
},
MenuItem {
icon: "gradient"
text: qsTr("Tonal spot")
value: "tonalSpot"
},
MenuItem {
icon: "target"
text: qsTr("Fidelity")
value: "fidelity"
},
MenuItem {
icon: "article"
text: qsTr("Content")
value: "content"
},
MenuItem {
icon: "colors"
text: qsTr("Rainbow")
value: "rainbow"
},
MenuItem {
icon: "nutrition"
text: qsTr("Fruit salad")
value: "fruitSalad"
}
]
onSelected: item => {
Config.colors.schemeType = item.value;
Config.save();
}
}
Separator {
}
SettingSwitch {
name: "Automatic color scheme"
object: Config.general.color
setting: "schemeGeneration"
}
Separator {
}
SettingSwitch {
name: "Smart color scheme"
object: Config.general.color
setting: "smart"
}
Separator {
}
SettingSpinner {
name: "Schedule dark mode"
object: Config.general.color
settings: ["scheduleDarkStart", "scheduleDarkEnd"]
}
}
SettingsSection {
z: -1
SettingsHeader {
name: "Default Apps"
}
SettingStringList {
addLabel: qsTr("Add terminal command")
name: "Terminal"
object: Config.general.apps
setting: "terminal"
}
Separator {
}
SettingStringList {
addLabel: qsTr("Add audio command")
name: "Audio"
object: Config.general.apps
setting: "audio"
}
Separator {
}
SettingStringList {
addLabel: qsTr("Add playback command")
name: "Playback"
object: Config.general.apps
setting: "playback"
}
Separator {
}
SettingStringList {
addLabel: qsTr("Add explorer command")
name: "Explorer"
object: Config.general.apps
setting: "explorer"
}
}
}
+148
View File
@@ -0,0 +1,148 @@
import qs.Modules.Settings.Controls
import qs.Config
SettingsPage {
SettingsSection {
SettingsHeader {
name: "Launcher"
}
SettingSpinBox {
name: "Max apps shown"
min: 1
object: Config.launcher
setting: "maxAppsShown"
}
Separator {
}
SettingSpinBox {
name: "Max wallpapers shown"
min: 1
object: Config.launcher
setting: "maxWallpapers"
}
Separator {
}
SettingInput {
name: "Action prefix"
object: Config.launcher
setting: "actionPrefix"
}
Separator {
}
SettingInput {
name: "Special prefix"
object: Config.launcher
setting: "specialPrefix"
}
}
SettingsSection {
SettingsHeader {
name: "Fuzzy Search"
}
SettingSwitch {
name: "Apps"
object: Config.launcher.useFuzzy
setting: "apps"
}
Separator {
}
SettingSwitch {
name: "Actions"
object: Config.launcher.useFuzzy
setting: "actions"
}
Separator {
}
SettingSwitch {
name: "Schemes"
object: Config.launcher.useFuzzy
setting: "schemes"
}
Separator {
}
SettingSwitch {
name: "Variants"
object: Config.launcher.useFuzzy
setting: "variants"
}
Separator {
}
SettingSwitch {
name: "Wallpapers"
object: Config.launcher.useFuzzy
setting: "wallpapers"
}
}
SettingsSection {
SettingsHeader {
name: "Sizes"
}
SettingSpinBox {
name: "Item width"
min: 1
object: Config.launcher.sizes
setting: "itemWidth"
}
Separator {
}
SettingSpinBox {
name: "Item height"
min: 1
object: Config.launcher.sizes
setting: "itemHeight"
}
Separator {
}
SettingSpinBox {
name: "Wallpaper width"
min: 1
object: Config.launcher.sizes
setting: "wallpaperWidth"
}
Separator {
}
SettingSpinBox {
name: "Wallpaper height"
min: 1
object: Config.launcher.sizes
setting: "wallpaperHeight"
}
}
SettingsSection {
SettingsHeader {
name: "Actions"
}
SettingActionList {
name: "Launcher actions"
object: Config.launcher
setting: "actions"
}
}
}
@@ -0,0 +1,90 @@
import qs.Modules.Settings.Categories.Lockscreen
import qs.Modules.Settings.Controls
import qs.Config
SettingsPage {
id: root
SettingsSection {
SettingsHeader {
name: "Lockscreen"
}
SettingSwitch {
name: "Recolor logo"
object: Config.lock
setting: "recolorLogo"
}
Separator {
}
SettingSwitch {
name: "Enable fingerprint"
object: Config.lock
setting: "enableFprint"
}
Separator {
}
SettingSpinBox {
name: "Max fingerprint tries"
min: 1
object: Config.lock
setting: "maxFprintTries"
step: 1
}
Separator {
}
SettingSpinBox {
name: "Blur amount"
min: 0
object: Config.lock
setting: "blurAmount"
step: 1
}
Separator {
}
SettingSpinBox {
name: "Height multiplier"
max: 2
min: 0.1
object: Config.lock.sizes
setting: "heightMult"
step: 0.05
}
Separator {
}
SettingSpinBox {
name: "Aspect ratio"
max: 4
min: 0.5
object: Config.lock.sizes
setting: "ratio"
step: 0.05
}
Separator {
}
SettingSpinBox {
name: "Center width"
min: 100
object: Config.lock.sizes
setting: "centerWidth"
step: 10
}
}
SettingsSection {
Idle {
}
}
}
@@ -0,0 +1,83 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Modules.Settings.Controls
ColumnLayout {
id: root
function addTimeoutEntry() {
let list = [...Config.general.idle.timeouts];
list.push({
name: "New Entry",
timeout: 600,
idleAction: "lock"
});
Config.general.idle.timeouts = list;
Config.save();
}
function updateTimeoutEntry(i, key, value) {
const list = [...Config.general.idle.timeouts];
let entry = list[i];
entry[key] = value;
list[i] = entry;
Config.general.idle.timeouts = list;
Config.save();
}
Layout.fillWidth: true
spacing: Appearance.spacing.smaller
Settings {
name: "Idle Monitors"
}
Repeater {
model: [...Config.general.idle.timeouts]
SettingList {
Layout.fillWidth: true
onAddActiveActionRequested: {
root.updateTimeoutEntry(index, "activeAction", "");
}
onFieldEdited: function (key, value) {
root.updateTimeoutEntry(index, key, value);
}
}
}
IconButton {
font.pointSize: Appearance.font.size.large
icon: "add"
onClicked: root.addTimeoutEntry()
}
component Settings: CustomRect {
id: settingsItem
required property string name
Layout.preferredHeight: 60
Layout.preferredWidth: 200
CustomText {
id: text
anchors.fill: parent
font.bold: true
font.pointSize: Appearance.font.size.large * 2
text: settingsItem.name
verticalAlignment: Text.AlignVCenter
}
}
}
@@ -0,0 +1,112 @@
import qs.Modules.Settings.Controls
import qs.Config
SettingsPage {
SettingsSection {
SettingsHeader {
name: "Notifications"
}
SettingSwitch {
name: "Expire notifications"
object: Config.notifs
setting: "expire"
}
Separator {
}
SettingSpinBox {
name: "Default expire timeout"
min: 0
object: Config.notifs
setting: "defaultExpireTimeout"
step: 100
}
Separator {
}
SettingSpinBox {
name: "App notification cooldown"
min: 0
object: Config.notifs
setting: "appNotifCooldown"
step: 100
}
Separator {
}
SettingSpinBox {
name: "Clear threshold"
max: 1
min: 0
object: Config.notifs
setting: "clearThreshold"
step: 0.05
}
Separator {
}
SettingSpinBox {
name: "Expand threshold"
min: 0
object: Config.notifs
setting: "expandThreshold"
}
Separator {
}
SettingSwitch {
name: "Action on click"
object: Config.notifs
setting: "actionOnClick"
}
Separator {
}
SettingSpinBox {
name: "Group preview count"
min: 1
object: Config.notifs
setting: "groupPreviewNum"
}
}
SettingsSection {
SettingsHeader {
name: "Sizes"
}
SettingSpinBox {
name: "Width"
min: 1
object: Config.notifs.sizes
setting: "width"
}
Separator {
}
SettingSpinBox {
name: "Image size"
min: 1
object: Config.notifs.sizes
setting: "image"
}
Separator {
}
SettingSpinBox {
name: "Badge size"
min: 1
object: Config.notifs.sizes
setting: "badge"
}
}
}
+77
View File
@@ -0,0 +1,77 @@
import qs.Modules.Settings.Controls
import qs.Config
SettingsPage {
SettingsSection {
SettingsHeader {
name: "On Screen Display"
}
SettingSwitch {
name: "Enable OSD"
object: Config.osd
setting: "enabled"
}
Separator {
}
SettingSpinBox {
name: "Hide delay"
min: 0
object: Config.osd
setting: "hideDelay"
step: 100
}
Separator {
}
SettingSwitch {
name: "Enable brightness OSD"
object: Config.osd
setting: "enableBrightness"
}
Separator {
}
SettingSwitch {
name: "Enable microphone OSD"
object: Config.osd
setting: "enableMicrophone"
}
Separator {
}
SettingSwitch {
name: "Brightness on all monitors"
object: Config.osd
setting: "allMonBrightness"
}
}
SettingsSection {
SettingsHeader {
name: "Sizes"
}
SettingSpinBox {
name: "Slider width"
min: 1
object: Config.osd.sizes
setting: "sliderWidth"
}
Separator {
}
SettingSpinBox {
name: "Slider height"
min: 1
object: Config.osd.sizes
setting: "sliderHeight"
}
}
}

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