155 Commits

Author SHA1 Message Date
zach c022933d16 Merge pull request 'Updating README.md for NixOS flake installation' (#28) from nixos-readme-gitea into main
Reviewed-on: #28
2026-03-22 17:25:32 +01:00
AramJonghu c266665cff Updating README.md for NixOS flake installation
Flake was still pointing to github instead of the new git instance. Adjusted now.

Delete branch once merged.
2026-03-22 17:24:10 +01:00
zach 262d6404a4 cleanup 2026-03-22 17:19:15 +01:00
zach c99d8abeac Merge pull request 'Greeter + Hyprsunset toggle and scheduler' (#27) from settingsWindow into main
Reviewed-on: #27
2026-03-22 17:16:39 +01:00
zach 864604401b Merge branch 'main' into settingsWindow 2026-03-22 17:16:28 +01:00
zach 205a76b2f3 hyprsunset schedule and toggle 2026-03-22 17:08:48 +01:00
zach 42ea3318c1 test 2026-03-22 16:00:59 +01:00
Zacharias-Brohn 1eae2044c1 test 2026-03-22 15:58:23 +01:00
Zacharias-Brohn f531d5cbbb test 2026-03-21 23:29:00 +01:00
Zacharias-Brohn ec49904bac select rectangle 2026-03-21 15:50:34 +01:00
Zacharias-Brohn 8cc2b14ad1 remove wallust, fix enabled switch for schedule dark mode 2026-03-21 12:20:55 +01:00
Zacharias-Brohn d839f32196 config fixes for greeter 2026-03-20 23:54:21 +01:00
Zacharias-Brohn 3c46256a9f test script 2026-03-20 21:10:08 +01:00
Zacharias-Brohn 10b56e1e1b fix no blur 2026-03-20 13:54:12 +01:00
Zacharias-Brohn 0644c5bf86 vulkan 2026-03-20 13:45:03 +01:00
Zacharias-Brohn c1efd7dacc test 2026-03-19 00:14:09 +01:00
Zacharias-Brohn a982ca500b cmakelists sucks 2026-03-18 23:42:21 +01:00
Zacharias-Brohn 6b482979fe greeter test 2026-03-18 23:39:37 +01:00
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
Zacharias-Brohn eabff9b18f esc handling for resources 2026-02-28 18:18:52 +01:00
Zacharias-Brohn e71a15f2e2 WlrLayershell.namespace fix 2026-02-28 14:03:51 +01:00
Zacharias-Brohn b28eec5930 resource popout 2026-02-28 13:57:29 +01:00
Zacharias-Brohn 65adecfa21 ddcutil-service usage 2026-02-28 00:23:29 +01:00
Zacharias-Brohn 178375d861 ddcutil-service usage 2026-02-27 14:46:09 +01:00
Zacharias-Brohn afb736102d cava + nix 2026-02-26 16:27:05 +01:00
Zacharias-Brohn 09a93d9420 cava test 2026-02-26 14:55:13 +01:00
Zacharias-Brohn 74ee1b48f9 cava test 2026-02-26 14:52:24 +01:00
Zacharias-Brohn dd02b2636e cava test 2026-02-26 14:47:46 +01:00
Zacharias-Brohn f138bf7bdc cava test 2026-02-26 14:44:31 +01:00
Zacharias-Brohn e4e3cab22d cava test 2026-02-26 14:33:49 +01:00
Zacharias-Brohn 0e67c4d6cb cava test 2026-02-26 14:26:20 +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
Zacharias-Brohn 116dca6440 oops lol 2026-02-25 22:19:56 +01:00
Zacharias-Brohn 6d953661b3 oops lol 2026-02-25 22:18:26 +01:00
Zach a8dd730808 Merge pull request #17 from Zacharias-Brohn/horizontal-media-widget
update
2026-02-25 22:14:19 +01:00
Zacharias-Brohn 5f875915f4 update 2026-02-25 22:12:29 +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
Zacharias-Brohn ed9e0d1c85 updates module + remove pixelSize usage 2026-02-25 18:13:09 +01:00
Zacharias-Brohn 11da456048 formatter 2026-02-25 17:29:14 +01:00
Zacharias-Brohn 15a112eaf7 ideas 2026-02-25 17:28:09 +01:00
Zacharias-Brohn eafb176d6e notification cooldown 2026-02-25 17:08:04 +01:00
Zacharias-Brohn 419214f4bd oops lol 2026-02-25 14:35:23 +01:00
Zacharias-Brohn bd246247ac .qmlformat.ini 2026-02-25 14:29:54 +01:00
Zacharias-Brohn c3af6575e7 lmao 2026-02-25 14:20:11 +01:00
Zacharias-Brohn ca04c7d2f1 horizontal media widget 2026-02-25 14:19:27 +01:00
Zacharias-Brohn d56a0260fb formatter 2026-02-24 23:20:11 +01:00
Zacharias-Brohn 40cd984b6d fix the switch 2026-02-23 23:24:11 +01:00
inorishio 412119aa89 plans 2026-02-23 22:48:01 +01:00
Zacharias-Brohn f645c90dbd category highlight 2026-02-23 22:37:40 +01:00
Zacharias-Brohn 22015bf61d custom switch 2026-02-23 21:38:45 +01:00
Zach fa63746dfd Merge pull request #13 from Zacharias-Brohn/settingsWindow
Settings window
2026-02-23 19:35:57 +01:00
inorishio 14b6af90f9 stupid idea's from daivin 2026-02-23 18:56:07 +01:00
Zacharias-Brohn 0ff4ffc869 bluetooth toggle 2026-02-23 18:50:18 +01:00
Zach 83db3a39cb Merge pull request #12 from Zacharias-Brohn/organize
Organize
2026-02-23 18:23:28 +01:00
inorishio 3893213637 Merge branch 'settingsWindow' into organize
Merge.
2026-02-23 18:22:04 +01:00
Zach f8eb115c34 Merge pull request #11 from Zacharias-Brohn/main
merge main
2026-02-23 18:16:25 +01:00
inorishio 728b78a69e settings? 2026-02-23 18:15:18 +01:00
inorishio 944ea84b5a Start of settings window 2026-02-23 16:52:05 +01:00
Zacharias-Brohn dee783df77 cmake oopsie 2026-02-23 16:38:38 +01:00
Zach 427ee50213 Merge pull request #10 from Zacharias-Brohn/main
oops merge
2026-02-23 16:24:00 +01:00
Zacharias-Brohn cdefe3706f organize files 2026-02-23 16:22:28 +01:00
410 changed files with 34920 additions and 14466 deletions
+1
View File
@@ -1,3 +1,4 @@
./result/
.pyre/ .pyre/
.cache/ .cache/
.venv/ .venv/
+10
View File
@@ -0,0 +1,10 @@
[General]
FunctionsSpacing=true
IndentWidth=4
MaxColumnWidth=-1
NewlineType=native
NormalizeOrder=true
ObjectsSpacing=true
SemicolonRule=always
SortImports=false
UseTabs=true
-197
View File
@@ -1,197 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import qs.Daemons
import qs.Components
import qs.Modules
import qs.Modules.Bar
import qs.Config
import qs.Helpers
import qs.Drawers
Variants {
model: Quickshell.screens
Scope {
id: scope
required property var modelData
PanelWindow {
id: bar
property bool trayMenuVisible: false
screen: scope.modelData
color: "transparent"
property var root: Quickshell.shellDir
WlrLayershell.namespace: "ZShell-Bar"
WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.keyboardFocus: visibilities.launcher || visibilities.sidebar || visibilities.dashboard ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
contentItem.focus: true
contentItem.Keys.onEscapePressed: {
if ( Config.barConfig.autoHide )
visibilities.bar = false;
visibilities.sidebar = false;
visibilities.dashboard = false;
visibilities.osd = false;
}
PanelWindow {
id: exclusionZone
WlrLayershell.namespace: "ZShell-Bar-Exclusion"
screen: bar.screen
WlrLayershell.layer: WlrLayer.Bottom
WlrLayershell.exclusionMode: Config.barConfig.autoHide ? ExclusionMode.Ignore : ExclusionMode.Auto
anchors {
left: true
right: true
top: true
}
color: "transparent"
implicitHeight: 34
}
anchors {
top: true
left: true
right: true
bottom: true
}
mask: Region {
id: region
x: 0
y: Config.barConfig.autoHide && !visibilities.bar ? 4 : 34
property list<Region> nullRegions: []
width: bar.width
height: bar.screen.height - backgroundRect.implicitHeight
intersection: Intersection.Xor
regions: popoutRegions.instances
}
Variants {
id: popoutRegions
model: panels.children
Region {
required property Item modelData
x: modelData.x
y: modelData.y + backgroundRect.implicitHeight
width: modelData.width
height: modelData.height
intersection: Intersection.Subtract
}
}
HyprlandFocusGrab {
id: focusGrab
active: visibilities.launcher || visibilities.sidebar || visibilities.dashboard || ( panels.popouts.hasCurrent && panels.popouts.currentName.startsWith( "traymenu" ))
windows: [bar]
onCleared: {
visibilities.launcher = false;
visibilities.sidebar = false;
visibilities.dashboard = false;
visibilities.osd = false;
panels.popouts.hasCurrent = false;
}
}
PersistentProperties {
id: visibilities
property bool sidebar
property bool dashboard
property bool bar
property bool osd
property bool launcher
property bool notif: NotifServer.popups.length > 0
Component.onCompleted: Visibilities.load(scope.modelData, this)
}
Binding {
target: visibilities
property: "bar"
value: visibilities.sidebar || visibilities.dashboard || visibilities.osd || visibilities.notif
when: Config.barConfig.autoHide
}
Item {
anchors.fill: parent
opacity: Appearance.transparency.enabled ? DynamicColors.transparency.base : 1
layer.enabled: true
layer.effect: MultiEffect {
shadowEnabled: true
blurMax: 32
shadowColor: Qt.alpha(DynamicColors.palette.m3shadow, 1)
}
Border {
bar: backgroundRect
visibilities: visibilities
}
Backgrounds {
visibilities: visibilities
panels: panels
bar: backgroundRect
}
}
Interactions {
id: mouseArea
screen: scope.modelData
popouts: panels.popouts
visibilities: visibilities
panels: panels
bar: barLoader
anchors.fill: parent
Panels {
id: panels
screen: scope.modelData
bar: backgroundRect
visibilities: visibilities
}
CustomRect {
id: backgroundRect
property Wrapper popouts: panels.popouts
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: 34
anchors.topMargin: Config.barConfig.autoHide && !visibilities.bar ? -30 : 0
color: "transparent"
radius: 0
Behavior on color {
CAnim {}
}
Behavior on anchors.topMargin {
Anim {}
}
BarLoader {
id: barLoader
anchors.fill: parent
popouts: panels.popouts
bar: bar
visibilities: visibilities
screen: scope.modelData
}
}
}
}
}
}
+3 -1
View File
@@ -12,6 +12,7 @@ set(ENABLE_MODULES "plugin;shell" CACHE STRING "Modules to build/install")
set(INSTALL_LIBDIR "usr/lib/ZShell" CACHE STRING "Library install dir") set(INSTALL_LIBDIR "usr/lib/ZShell" CACHE STRING "Library install dir")
set(INSTALL_QMLDIR "usr/lib/qt6/qml" CACHE STRING "QML install dir") set(INSTALL_QMLDIR "usr/lib/qt6/qml" CACHE STRING "QML install dir")
set(INSTALL_QSCONFDIR "etc/xdg/quickshell/zshell" CACHE STRING "Quickshell config install dir") set(INSTALL_QSCONFDIR "etc/xdg/quickshell/zshell" CACHE STRING "Quickshell config install dir")
set(INSTALL_GREETERCONFDIR "etc/xdg/quickshell/zshell-greeter" CACHE STRING "Quickshell greeter install dir")
add_compile_options( add_compile_options(
-Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wall -Wextra -Wpedantic -Wshadow -Wconversion
@@ -30,5 +31,6 @@ if("shell" IN_LIST ENABLE_MODULES)
foreach(dir assets scripts Components Config Modules Daemons Drawers Effects Helpers Paths) foreach(dir assets scripts Components Config Modules Daemons Drawers Effects Helpers Paths)
install(DIRECTORY ${dir} DESTINATION "${INSTALL_QSCONFDIR}") install(DIRECTORY ${dir} DESTINATION "${INSTALL_QSCONFDIR}")
endforeach() endforeach()
install(FILES shell.qml Bar.qml Wallpaper.qml DESTINATION "${INSTALL_QSCONFDIR}") install(FILES shell.qml DESTINATION "${INSTALL_QSCONFDIR}")
install(DIRECTORY Greeter/ DESTINATION "${INSTALL_GREETERCONFDIR}")
endif() endif()
+8
View File
@@ -0,0 +1,8 @@
import QtQuick
import qs.Config
NumberAnimation {
duration: MaterialEasing.standardTime
easing.bezierCurve: MaterialEasing.standard
easing.type: Easing.BezierSpline
}
+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;
}
}
}
+8
View File
@@ -0,0 +1,8 @@
import QtQuick
import qs.Config
ColorAnimation {
duration: MaterialEasing.standardTime
easing.bezierCurve: MaterialEasing.standard
easing.type: Easing.BezierSpline
}
+84 -90
View File
@@ -1,108 +1,102 @@
import qs.Helpers
import qs.Config import qs.Config
import qs.Modules
import ZShell.Internal import ZShell.Internal
import QtQuick import QtQuick
import QtQuick.Templates import QtQuick.Templates
BusyIndicator { BusyIndicator {
id: root id: root
enum AnimType { enum AnimState {
Advance = 0, Stopped,
Retreat Running,
} Completing
}
enum AnimType {
Advance = 0,
Retreat
}
enum AnimState { property int animState
Stopped, property color bgColour: DynamicColors.palette.m3secondaryContainer
Running, property color fgColour: DynamicColors.palette.m3primary
Completing property real implicitSize: Appearance.font.size.normal * 3
} property real internalStrokeWidth: strokeWidth
readonly property alias progress: manager.progress
property real strokeWidth: Appearance.padding.small * 0.8
property alias type: manager.indeterminateAnimationType
property real implicitSize: Appearance.font.size.normal * 3 implicitHeight: implicitSize
property real strokeWidth: Appearance.padding.small * 0.8 implicitWidth: implicitSize
property color fgColour: DynamicColors.palette.m3primary padding: 0
property color bgColour: DynamicColors.palette.m3secondaryContainer
property alias type: manager.indeterminateAnimationType contentItem: CircularProgress {
readonly property alias progress: manager.progress anchors.fill: parent
bgColour: root.bgColour
fgColour: root.fgColour
padding: root.padding
rotation: manager.rotation
startAngle: manager.startFraction * 360
strokeWidth: root.internalStrokeWidth
value: manager.endFraction - manager.startFraction
}
states: State {
name: "stopped"
when: !root.running
property real internalStrokeWidth: strokeWidth PropertyChanges {
property int animState root.internalStrokeWidth: root.strokeWidth / 3
root.opacity: 0
}
}
transitions: Transition {
Anim {
duration: manager.completeEndDuration * Appearance.anim.durations.scale
properties: "opacity,internalStrokeWidth"
}
}
padding: 0 Component.onCompleted: {
implicitWidth: implicitSize if (running) {
implicitHeight: implicitSize running = false;
running = true;
}
}
onRunningChanged: {
if (running) {
manager.completeEndProgress = 0;
animState = CircularIndicator.Running;
} else {
if (animState == CircularIndicator.Running)
animState = CircularIndicator.Completing;
}
}
Component.onCompleted: { CircularIndicatorManager {
if (running) { id: manager
running = false;
running = true;
}
}
onRunningChanged: { }
if (running) {
manager.completeEndProgress = 0;
animState = CircularIndicator.Running;
} else {
if (animState == CircularIndicator.Running)
animState = CircularIndicator.Completing;
}
}
states: State { NumberAnimation {
name: "stopped" duration: manager.duration * Appearance.anim.durations.scale
when: !root.running from: 0
loops: Animation.Infinite
property: "progress"
running: root.animState !== CircularIndicator.Stopped
target: manager
to: 1
}
PropertyChanges { NumberAnimation {
root.opacity: 0 duration: manager.completeEndDuration * Appearance.anim.durations.scale
root.internalStrokeWidth: root.strokeWidth / 3 from: 0
} property: "completeEndProgress"
} running: root.animState === CircularIndicator.Completing
target: manager
to: 1
transitions: Transition { onFinished: {
Anim { if (root.animState === CircularIndicator.Completing)
properties: "opacity,internalStrokeWidth" root.animState = CircularIndicator.Stopped;
duration: manager.completeEndDuration * Appearance.anim.durations.scale }
} }
}
contentItem: CircularProgress {
anchors.fill: parent
strokeWidth: root.internalStrokeWidth
fgColour: root.fgColour
bgColour: root.bgColour
padding: root.padding
rotation: manager.rotation
startAngle: manager.startFraction * 360
value: manager.endFraction - manager.startFraction
}
CircularIndicatorManager {
id: manager
}
NumberAnimation {
running: root.animState !== CircularIndicator.Stopped
loops: Animation.Infinite
target: manager
property: "progress"
from: 0
to: 1
duration: manager.duration * Appearance.anim.durations.scale
}
NumberAnimation {
running: root.animState === CircularIndicator.Completing
target: manager
property: "completeEndProgress"
from: 0
to: 1
duration: manager.completeEndDuration * Appearance.anim.durations.scale
onFinished: {
if (root.animState === CircularIndicator.Completing)
root.animState = CircularIndicator.Stopped;
}
}
} }
+52 -55
View File
@@ -1,69 +1,66 @@
import QtQuick import QtQuick
import QtQuick.Shapes import QtQuick.Shapes
import qs.Helpers
import qs.Config import qs.Config
import qs.Modules
Shape { Shape {
id: root id: root
property real value readonly property real arcRadius: (size - padding - strokeWidth) / 2
property int startAngle: -90 property color bgColour: DynamicColors.palette.m3secondaryContainer
property int strokeWidth: Appearance.padding.smaller property color fgColour: DynamicColors.palette.m3primary
property int padding: 0 readonly property real gapAngle: ((spacing + strokeWidth) / (arcRadius || 1)) * (180 / Math.PI)
property int spacing: Appearance.spacing.small property int padding: 0
property color fgColour: DynamicColors.palette.m3primary readonly property real size: Math.min(width, height)
property color bgColour: DynamicColors.palette.m3secondaryContainer property int spacing: Appearance.spacing.small
property int startAngle: -90
property int strokeWidth: Appearance.padding.smaller
readonly property real vValue: value || 1 / 360
property real value
readonly property real size: Math.min(width, height) asynchronous: true
readonly property real arcRadius: (size - padding - strokeWidth) / 2 preferredRendererType: Shape.CurveRenderer
readonly property real vValue: value || 1 / 360
readonly property real gapAngle: ((spacing + strokeWidth) / (arcRadius || 1)) * (180 / Math.PI)
preferredRendererType: Shape.CurveRenderer ShapePath {
asynchronous: true capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
fillColor: "transparent"
strokeColor: root.bgColour
strokeWidth: root.strokeWidth
ShapePath { Behavior on strokeColor {
fillColor: "transparent" CAnim {
strokeColor: root.bgColour duration: Appearance.anim.durations.large
strokeWidth: root.strokeWidth }
capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap }
PathAngleArc { PathAngleArc {
startAngle: root.startAngle + 360 * root.vValue + root.gapAngle centerX: root.size / 2
sweepAngle: Math.max(-root.gapAngle, 360 * (1 - root.vValue) - root.gapAngle * 2) centerY: root.size / 2
radiusX: root.arcRadius radiusX: root.arcRadius
radiusY: root.arcRadius radiusY: root.arcRadius
centerX: root.size / 2 startAngle: root.startAngle + 360 * root.vValue + root.gapAngle
centerY: root.size / 2 sweepAngle: Math.max(-root.gapAngle, 360 * (1 - root.vValue) - root.gapAngle * 2)
} }
}
Behavior on strokeColor { ShapePath {
CAnim { capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
duration: Appearance.anim.durations.large fillColor: "transparent"
} strokeColor: root.fgColour
} strokeWidth: root.strokeWidth
}
ShapePath { Behavior on strokeColor {
fillColor: "transparent" CAnim {
strokeColor: root.fgColour duration: Appearance.anim.durations.large
strokeWidth: root.strokeWidth }
capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap }
PathAngleArc { PathAngleArc {
startAngle: root.startAngle centerX: root.size / 2
sweepAngle: 360 * root.vValue centerY: root.size / 2
radiusX: root.arcRadius radiusX: root.arcRadius
radiusY: root.arcRadius radiusY: root.arcRadius
centerX: root.size / 2 startAngle: root.startAngle
centerY: root.size / 2 sweepAngle: 360 * root.vValue
} }
}
Behavior on strokeColor {
CAnim {
duration: Appearance.anim.durations.large
}
}
}
} }
+135
View File
@@ -0,0 +1,135 @@
import QtQuick
import QtQuick.Layouts
import qs.Config
ColumnLayout {
id: root
default property alias content: contentColumn.data
property string description: ""
property bool expanded: false
property bool nested: false
property bool showBackground: false
required property string title
signal toggleRequested
Layout.fillWidth: true
spacing: Appearance.spacing.small
Item {
id: sectionHeaderItem
Layout.fillWidth: true
Layout.preferredHeight: Math.max(titleRow.implicitHeight + Appearance.padding.normal * 2, 48)
RowLayout {
id: titleRow
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.normal
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.normal
anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.normal
CustomText {
font.pointSize: Appearance.font.size.larger
font.weight: 500
text: root.title
}
Item {
Layout.fillWidth: true
}
MaterialIcon {
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.normal
rotation: root.expanded ? 180 : 0
text: "expand_more"
Behavior on rotation {
Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.standard
}
}
}
}
StateLayer {
function onClicked(): void {
root.toggleRequested();
root.expanded = !root.expanded;
}
anchors.fill: parent
color: DynamicColors.palette.m3onSurface
radius: Appearance.rounding.normal
showHoverBackground: false
}
}
Item {
id: contentWrapper
Layout.fillWidth: true
Layout.preferredHeight: root.expanded ? (contentColumn.implicitHeight + Appearance.spacing.small * 2) : 0
clip: true
Behavior on Layout.preferredHeight {
Anim {
easing.bezierCurve: Appearance.anim.curves.standard
}
}
CustomRect {
id: backgroundRect
anchors.fill: parent
color: DynamicColors.transparency.enabled ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, root.nested ? 3 : 2) : (root.nested ? DynamicColors.palette.m3surfaceContainerHigh : DynamicColors.palette.m3surfaceContainer)
opacity: root.showBackground && root.expanded ? 1.0 : 0.0
radius: Appearance.rounding.normal
visible: root.showBackground
Behavior on opacity {
Anim {
easing.bezierCurve: Appearance.anim.curves.standard
}
}
}
ColumnLayout {
id: contentColumn
anchors.bottomMargin: Appearance.spacing.small
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.normal
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.normal
opacity: root.expanded ? 1.0 : 0.0
spacing: Appearance.spacing.small
y: Appearance.spacing.small
Behavior on opacity {
Anim {
easing.bezierCurve: Appearance.anim.curves.standard
}
}
CustomText {
id: descriptionText
Layout.bottomMargin: root.description !== "" ? Appearance.spacing.small : 0
Layout.fillWidth: true
Layout.topMargin: root.description !== "" ? Appearance.spacing.smaller : 0
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.small
text: root.description
visible: root.description !== ""
wrapMode: Text.Wrap
}
}
}
}
+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;
}
}
}
+20 -21
View File
@@ -5,31 +5,30 @@ import Quickshell.Widgets
import QtQuick import QtQuick
IconImage { IconImage {
id: root id: root
required property color color required property color color
asynchronous: true asynchronous: true
layer.enabled: true
layer.enabled: true layer.effect: Coloriser {
layer.effect: Coloriser { colorizationColor: root.color
sourceColor: analyser.dominantColour sourceColor: analyser.dominantColour
colorizationColor: root.color }
}
layer.onEnabledChanged: { layer.onEnabledChanged: {
if (layer.enabled && status === Image.Ready) if (layer.enabled && status === Image.Ready)
analyser.requestUpdate(); analyser.requestUpdate();
} }
onStatusChanged: {
if (layer.enabled && status === Image.Ready)
analyser.requestUpdate();
}
onStatusChanged: { ImageAnalyser {
if (layer.enabled && status === Image.Ready) id: analyser
analyser.requestUpdate();
}
ImageAnalyser { sourceItem: root
id: analyser }
sourceItem: root
}
} }
+7 -7
View File
@@ -1,14 +1,14 @@
import QtQuick import QtQuick
import QtQuick.Effects import QtQuick.Effects
import qs.Modules
MultiEffect { MultiEffect {
property color sourceColor: "black" property color sourceColor: "black"
colorization: 1 brightness: 1 - sourceColor.hslLightness
brightness: 1 - sourceColor.hslLightness colorization: 1
Behavior on colorizationColor { Behavior on colorizationColor {
CAnim {} CAnim {
} }
}
} }
+47 -60
View File
@@ -1,83 +1,70 @@
import QtQuick import QtQuick
import QtQuick.Templates import QtQuick.Templates
import qs.Config import qs.Config
import qs.Modules
Slider { Slider {
id: root id: root
required property real peak
property color nonPeakColor: DynamicColors.tPalette.m3primary property color nonPeakColor: DynamicColors.tPalette.m3primary
required property real peak
property color peakColor: DynamicColors.palette.m3primary property color peakColor: DynamicColors.palette.m3primary
background: Item { background: Item {
CustomRect { CustomRect {
anchors.top: parent.top anchors.bottom: parent.bottom
anchors.bottom: parent.bottom anchors.bottomMargin: root.implicitHeight / 3
anchors.left: parent.left anchors.left: parent.left
anchors.topMargin: root.implicitHeight / 3 anchors.top: parent.top
anchors.bottomMargin: root.implicitHeight / 3 anchors.topMargin: root.implicitHeight / 3
bottomRightRadius: root.implicitHeight / 15
implicitWidth: root.handle.x - root.implicitHeight color: root.nonPeakColor
implicitWidth: root.handle.x - root.implicitHeight
color: root.nonPeakColor radius: 1000
radius: 1000 topRightRadius: root.implicitHeight / 15
topRightRadius: root.implicitHeight / 15
bottomRightRadius: root.implicitHeight / 15
CustomRect { CustomRect {
anchors.top: parent.top
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top
bottomRightRadius: root.implicitHeight / 15
color: root.peakColor
implicitWidth: parent.width * root.peak implicitWidth: parent.width * root.peak
radius: 1000 radius: 1000
topRightRadius: root.implicitHeight / 15 topRightRadius: root.implicitHeight / 15
bottomRightRadius: root.implicitHeight / 15
color: root.peakColor
Behavior on implicitWidth { Behavior on implicitWidth {
Anim { duration: 50 } Anim {
duration: 50
}
} }
} }
} }
CustomRect { CustomRect {
anchors.top: parent.top anchors.bottom: parent.bottom
anchors.bottom: parent.bottom anchors.bottomMargin: root.implicitHeight / 3
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: root.implicitHeight / 3 anchors.top: parent.top
anchors.bottomMargin: root.implicitHeight / 3 anchors.topMargin: root.implicitHeight / 3
bottomLeftRadius: root.implicitHeight / 15
color: DynamicColors.tPalette.m3surfaceContainer
implicitWidth: root.implicitWidth - root.handle.x - root.handle.implicitWidth - root.implicitHeight
radius: 1000
topLeftRadius: root.implicitHeight / 15
}
}
handle: CustomRect {
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3primary
implicitHeight: 15
implicitWidth: 5
radius: 1000
x: root.visualPosition * root.availableWidth - implicitWidth / 2
implicitWidth: root.implicitWidth - root.handle.x - root.handle.implicitWidth - root.implicitHeight MouseArea {
acceptedButtons: Qt.NoButton
Component.onCompleted: { anchors.fill: parent
console.log(root.handle.x, implicitWidth) cursorShape: Qt.PointingHandCursor
} }
}
color: DynamicColors.tPalette.m3surfaceContainer
radius: 1000
topLeftRadius: root.implicitHeight / 15
bottomLeftRadius: root.implicitHeight / 15
}
}
handle: CustomRect {
x: root.visualPosition * root.availableWidth - implicitWidth / 2
implicitWidth: 5
implicitHeight: 15
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3primary
radius: 1000
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: Qt.PointingHandCursor
}
}
} }
+55 -51
View File
@@ -2,64 +2,68 @@ import QtQuick
import QtQuick.Controls.Basic import QtQuick.Controls.Basic
BusyIndicator { BusyIndicator {
id: control id: control
property color color: delegate.color
property int busySize: 64 property int busySize: 64
property color color: delegate.color
contentItem: Item { contentItem: Item {
implicitWidth: control.busySize implicitHeight: control.busySize
implicitHeight: control.busySize implicitWidth: control.busySize
Item { Item {
id: item id: item
x: parent.width / 2 - (control.busySize / 2)
y: parent.height / 2 - (control.busySize / 2)
width: control.busySize
height: control.busySize
opacity: control.running ? 1 : 0
Behavior on opacity { height: control.busySize
OpacityAnimator { opacity: control.running ? 1 : 0
duration: 250 width: control.busySize
} x: parent.width / 2 - (control.busySize / 2)
} y: parent.height / 2 - (control.busySize / 2)
RotationAnimator { Behavior on opacity {
target: item OpacityAnimator {
running: control.visible && control.running duration: 250
from: 0 }
to: 360 }
loops: Animation.Infinite
duration: 1250
}
Repeater { RotationAnimator {
id: repeater duration: 1250
model: 6 from: 0
loops: Animation.Infinite
running: control.visible && control.running
target: item
to: 360
}
CustomRect { Repeater {
id: delegate id: repeater
x: item.width / 2 - width / 2
y: item.height / 2 - height / 2
implicitWidth: 10
implicitHeight: 10
radius: 5
color: control.color
required property int index model: 6
transform: [ CustomRect {
Translate { id: delegate
y: -Math.min(item.width, item.height) * 0.5 + 5
}, required property int index
Rotation {
angle: delegate.index / repeater.count * 360 color: control.color
origin.x: 5 implicitHeight: 10
origin.y: 5 implicitWidth: 10
} radius: 5
] x: item.width / 2 - width / 2
} y: item.height / 2 - height / 2
}
} transform: [
} Translate {
y: -Math.min(item.width, item.height) * 0.5 + 5
},
Rotation {
angle: delegate.index / repeater.count * 360
origin.x: 5
origin.y: 5
}
]
}
}
}
}
} }
+12 -13
View File
@@ -1,33 +1,32 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import qs.Config
Button { Button {
id: control id: control
required property color textColor property color bgColor: DynamicColors.palette.m3primary
required property color bgColor
property int radius: 4 property int radius: 4
property color textColor: DynamicColors.palette.m3onPrimary
contentItem: CustomText { background: CustomRect {
text: control.text color: control.bgColor
opacity: control.enabled ? 1.0 : 0.5 opacity: control.enabled ? 1.0 : 0.5
radius: control.radius
}
contentItem: CustomText {
color: control.textColor color: control.textColor
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
opacity: control.enabled ? 1.0 : 0.5
text: control.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
background: CustomRect {
opacity: control.enabled ? 1.0 : 0.5
radius: control.radius
color: control.bgColor
}
StateLayer { StateLayer {
radius: control.radius
function onClicked(): void { function onClicked(): void {
control.clicked(); control.clicked();
} }
radius: control.radius
} }
} }
+14 -16
View File
@@ -5,35 +5,33 @@ import qs.Config
CheckBox { CheckBox {
id: control id: control
property int checkWidth: 20
property int checkHeight: 20 property int checkHeight: 20
property int checkWidth: 20
contentItem: CustomText {
anchors.left: parent.left
anchors.leftMargin: control.checkWidth + control.leftPadding + 8
anchors.verticalCenter: parent.verticalCenter
font.pointSize: control.font.pointSize
text: control.text
}
indicator: CustomRect { indicator: CustomRect {
implicitWidth: control.checkWidth
implicitHeight: control.checkHeight
// x: control.leftPadding // x: control.leftPadding
// y: parent.implicitHeight / 2 - implicitHeight / 2 // y: parent.implicitHeight / 2 - implicitHeight / 2
border.color: control.checked ? DynamicColors.palette.m3primary : "transparent" border.color: control.checked ? DynamicColors.palette.m3primary : "transparent"
color: DynamicColors.palette.m3surfaceVariant color: DynamicColors.palette.m3surfaceVariant
implicitHeight: control.checkHeight
implicitWidth: control.checkWidth
radius: 4 radius: 4
CustomRect { CustomRect {
implicitWidth: control.checkWidth - (x * 2) color: DynamicColors.palette.m3primary
implicitHeight: control.checkHeight - (y * 2) implicitHeight: control.checkHeight - (y * 2)
implicitWidth: control.checkWidth - (x * 2)
radius: 3
visible: control.checked
x: 4 x: 4
y: 4 y: 4
radius: 3
color: DynamicColors.palette.m3primary
visible: control.checked
} }
} }
contentItem: CustomText {
text: control.text
font.pointSize: control.font.pointSize
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: control.checkWidth + control.leftPadding + 8
}
} }
+6 -6
View File
@@ -1,13 +1,13 @@
import Quickshell.Widgets import Quickshell.Widgets
import QtQuick import QtQuick
import qs.Modules
ClippingRectangle { ClippingRectangle {
id: root id: root
color: "transparent" color: "transparent"
Behavior on color { Behavior on color {
CAnim {} CAnim {
} }
}
} }
+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
}
}
}
+7 -8
View File
@@ -1,14 +1,13 @@
import QtQuick import QtQuick
import qs.Modules
Flickable { Flickable {
id: root id: root
maximumFlickVelocity: 3000 maximumFlickVelocity: 3000
rebound: Transition { rebound: Transition {
Anim { Anim {
properties: "x,y" properties: "x,y"
} }
} }
} }
+2 -2
View File
@@ -4,7 +4,7 @@ import Quickshell.Widgets
import QtQuick import QtQuick
IconImage { IconImage {
id: root id: root
asynchronous: true asynchronous: true
} }
+7 -9
View File
@@ -1,15 +1,13 @@
import QtQuick import QtQuick
import qs.Config
import qs.Modules
ListView { ListView {
id: root id: root
maximumFlickVelocity: 3000 maximumFlickVelocity: 3000
rebound: Transition { rebound: Transition {
Anim { Anim {
properties: "x,y" properties: "x,y"
} }
} }
} }
+12 -12
View File
@@ -1,19 +1,19 @@
import QtQuick import QtQuick
MouseArea { MouseArea {
property int scrollAccumulatedY: 0 property int scrollAccumulatedY: 0
function onWheel(event: WheelEvent): void { function onWheel(event: WheelEvent): void {
} }
onWheel: event => { onWheel: event => {
if (Math.sign(event.angleDelta.y) !== Math.sign(scrollAccumulatedY)) if (Math.sign(event.angleDelta.y) !== Math.sign(scrollAccumulatedY))
scrollAccumulatedY = 0; scrollAccumulatedY = 0;
scrollAccumulatedY += event.angleDelta.y; scrollAccumulatedY += event.angleDelta.y;
if (Math.abs(scrollAccumulatedY) >= 120) { if (Math.abs(scrollAccumulatedY) >= 120) {
onWheel(event); onWheel(event);
scrollAccumulatedY = 0; scrollAccumulatedY = 0;
} }
} }
} }
+40 -43
View File
@@ -1,56 +1,53 @@
import QtQuick import QtQuick
import QtQuick.Templates import QtQuick.Templates
import qs.Config import qs.Config
import qs.Modules
RadioButton { RadioButton {
id: root id: root
font.pointSize: 12 font.pointSize: Appearance.font.size.normal
implicitHeight: Math.max(implicitIndicatorHeight, implicitContentHeight)
implicitWidth: implicitIndicatorWidth + implicitContentWidth + contentItem.anchors.leftMargin
implicitWidth: implicitIndicatorWidth + implicitContentWidth + contentItem.anchors.leftMargin contentItem: CustomText {
implicitHeight: Math.max(implicitIndicatorHeight, implicitContentHeight) anchors.left: outerCircle.right
anchors.leftMargin: 10
anchors.verticalCenter: parent.verticalCenter
font.pointSize: root.font.pointSize
text: root.text
}
indicator: Rectangle {
id: outerCircle
indicator: Rectangle { anchors.verticalCenter: parent.verticalCenter
id: outerCircle border.color: root.checked ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant
border.width: 2
color: "transparent"
implicitHeight: 16
implicitWidth: 16
radius: 1000
implicitWidth: 16 Behavior on border.color {
implicitHeight: 16 CAnim {
radius: 1000 }
color: "transparent" }
border.color: root.checked ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant
border.width: 2
anchors.verticalCenter: parent.verticalCenter
StateLayer { StateLayer {
anchors.margins: -7 function onClicked(): void {
color: root.checked ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3primary root.click();
z: -1 }
function onClicked(): void { anchors.margins: -7
root.click(); color: root.checked ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3primary
} z: -1
} }
CustomRect { CustomRect {
anchors.centerIn: parent anchors.centerIn: parent
implicitWidth: 8 color: Qt.alpha(DynamicColors.palette.m3primary, root.checked ? 1 : 0)
implicitHeight: 8 implicitHeight: 8
implicitWidth: 8
radius: 1000 radius: 1000
color: Qt.alpha(DynamicColors.palette.m3primary, root.checked ? 1 : 0) }
} }
Behavior on border.color {
CAnim {}
}
}
contentItem: CustomText {
text: root.text
font.pointSize: root.font.pointSize
anchors.verticalCenter: parent.verticalCenter
anchors.left: outerCircle.right
anchors.leftMargin: 10
}
} }
+6 -6
View File
@@ -1,12 +1,12 @@
import QtQuick import QtQuick
import qs.Modules
Rectangle { Rectangle {
id: root id: root
color: "transparent" color: "transparent"
Behavior on color { Behavior on color {
CAnim {} CAnim {
} }
}
} }
+163 -163
View File
@@ -1,189 +1,189 @@
import qs.Config import qs.Config
import qs.Modules
import QtQuick import QtQuick
import QtQuick.Templates import QtQuick.Templates
ScrollBar { ScrollBar {
id: root id: root
required property Flickable flickable property bool _updatingFromFlickable: false
property bool shouldBeActive property bool _updatingFromUser: false
property real nonAnimPosition property bool animating
property bool animating required property Flickable flickable
property real nonAnimPosition
property bool shouldBeActive
onHoveredChanged: { implicitWidth: 8
if (hovered)
shouldBeActive = true;
else
shouldBeActive = flickable.moving;
}
property bool _updatingFromFlickable: false contentItem: CustomRect {
property bool _updatingFromUser: false anchors.left: parent.left
anchors.right: parent.right
color: DynamicColors.palette.m3secondary
opacity: {
if (root.size === 1)
return 0;
if (fullMouse.pressed)
return 1;
if (mouse.containsMouse)
return 0.8;
if (root.policy === ScrollBar.AlwaysOn || root.shouldBeActive)
return 0.6;
return 0;
}
radius: 1000
// Sync nonAnimPosition with Qt's automatic position binding Behavior on opacity {
onPositionChanged: { Anim {
if (_updatingFromUser) { }
_updatingFromUser = false; }
return;
}
if (position === nonAnimPosition) {
animating = false;
return;
}
if (!animating && !_updatingFromFlickable && !fullMouse.pressed) {
nonAnimPosition = position;
}
}
// Sync nonAnimPosition with flickable when not animating MouseArea {
Connections { id: mouse
target: flickable
function onContentYChanged() {
if (!animating && !fullMouse.pressed) {
_updatingFromFlickable = true;
const contentHeight = flickable.contentHeight;
const height = flickable.height;
if (contentHeight > height) {
nonAnimPosition = Math.max(0, Math.min(1, flickable.contentY / (contentHeight - height)));
} else {
nonAnimPosition = 0;
}
_updatingFromFlickable = false;
}
}
}
Component.onCompleted: { acceptedButtons: Qt.NoButton
if (flickable) { anchors.fill: parent
const contentHeight = flickable.contentHeight; cursorShape: Qt.PointingHandCursor
const height = flickable.height; hoverEnabled: true
if (contentHeight > height) { }
nonAnimPosition = Math.max(0, Math.min(1, flickable.contentY / (contentHeight - height))); }
} Behavior on position {
} enabled: !fullMouse.pressed
}
implicitWidth: 8
contentItem: CustomRect { Anim {
anchors.left: parent.left }
anchors.right: parent.right }
opacity: {
if (root.size === 1)
return 0;
if (fullMouse.pressed)
return 1;
if (mouse.containsMouse)
return 0.8;
if (root.policy === ScrollBar.AlwaysOn || root.shouldBeActive)
return 0.6;
return 0;
}
radius: 1000
color: DynamicColors.palette.m3secondary
MouseArea { Component.onCompleted: {
id: mouse if (flickable) {
const contentHeight = flickable.contentHeight;
const height = flickable.height;
if (contentHeight > height) {
nonAnimPosition = Math.max(0, Math.min(1, flickable.contentY / (contentHeight - height)));
}
}
}
onHoveredChanged: {
if (hovered)
shouldBeActive = true;
else
shouldBeActive = flickable.moving;
}
anchors.fill: parent // Sync nonAnimPosition with Qt's automatic position binding
cursorShape: Qt.PointingHandCursor onPositionChanged: {
hoverEnabled: true if (_updatingFromUser) {
acceptedButtons: Qt.NoButton _updatingFromUser = false;
} return;
}
if (position === nonAnimPosition) {
animating = false;
return;
}
if (!animating && !_updatingFromFlickable && !fullMouse.pressed) {
nonAnimPosition = position;
}
}
Behavior on opacity { // Sync nonAnimPosition with flickable when not animating
Anim {} Connections {
} function onContentYChanged() {
} if (!animating && !fullMouse.pressed) {
_updatingFromFlickable = true;
const contentHeight = flickable.contentHeight;
const height = flickable.height;
if (contentHeight > height) {
nonAnimPosition = Math.max(0, Math.min(1, flickable.contentY / (contentHeight - height)));
} else {
nonAnimPosition = 0;
}
_updatingFromFlickable = false;
}
}
Connections { target: flickable
target: root.flickable }
function onMovingChanged(): void { Connections {
if (root.flickable.moving) function onMovingChanged(): void {
root.shouldBeActive = true; if (root.flickable.moving)
else root.shouldBeActive = true;
hideDelay.restart(); else
} hideDelay.restart();
} }
Timer { target: root.flickable
id: hideDelay }
interval: 600 Timer {
onTriggered: root.shouldBeActive = root.flickable.moving || root.hovered id: hideDelay
}
CustomMouseArea { interval: 600
id: fullMouse
anchors.fill: parent onTriggered: root.shouldBeActive = root.flickable.moving || root.hovered
preventStealing: true }
onPressed: event => { CustomMouseArea {
root.animating = true; id: fullMouse
root._updatingFromUser = true;
const newPos = Math.max(0, Math.min(1 - root.size, event.y / root.height - root.size / 2));
root.nonAnimPosition = newPos;
// Update flickable position
// Map scrollbar position [0, 1-size] to contentY [0, maxContentY]
if (root.flickable) {
const contentHeight = root.flickable.contentHeight;
const height = root.flickable.height;
if (contentHeight > height) {
const maxContentY = contentHeight - height;
const maxPos = 1 - root.size;
const contentY = maxPos > 0 ? (newPos / maxPos) * maxContentY : 0;
root.flickable.contentY = Math.max(0, Math.min(maxContentY, contentY));
}
}
}
onPositionChanged: event => { function onWheel(event: WheelEvent): void {
root._updatingFromUser = true; root.animating = true;
const newPos = Math.max(0, Math.min(1 - root.size, event.y / root.height - root.size / 2)); root._updatingFromUser = true;
root.nonAnimPosition = newPos; let newPos = root.nonAnimPosition;
// Update flickable position if (event.angleDelta.y > 0)
// Map scrollbar position [0, 1-size] to contentY [0, maxContentY] newPos = Math.max(0, root.nonAnimPosition - 0.1);
if (root.flickable) { else if (event.angleDelta.y < 0)
const contentHeight = root.flickable.contentHeight; newPos = Math.min(1 - root.size, root.nonAnimPosition + 0.1);
const height = root.flickable.height; root.nonAnimPosition = newPos;
if (contentHeight > height) { // Update flickable position
const maxContentY = contentHeight - height; // Map scrollbar position [0, 1-size] to contentY [0, maxContentY]
const maxPos = 1 - root.size; if (root.flickable) {
const contentY = maxPos > 0 ? (newPos / maxPos) * maxContentY : 0; const contentHeight = root.flickable.contentHeight;
root.flickable.contentY = Math.max(0, Math.min(maxContentY, contentY)); const height = root.flickable.height;
} if (contentHeight > height) {
} const maxContentY = contentHeight - height;
} const maxPos = 1 - root.size;
const contentY = maxPos > 0 ? (newPos / maxPos) * maxContentY : 0;
root.flickable.contentY = Math.max(0, Math.min(maxContentY, contentY));
}
}
}
function onWheel(event: WheelEvent): void { anchors.fill: parent
root.animating = true; preventStealing: true
root._updatingFromUser = true;
let newPos = root.nonAnimPosition;
if (event.angleDelta.y > 0)
newPos = Math.max(0, root.nonAnimPosition - 0.1);
else if (event.angleDelta.y < 0)
newPos = Math.min(1 - root.size, root.nonAnimPosition + 0.1);
root.nonAnimPosition = newPos;
// Update flickable position
// Map scrollbar position [0, 1-size] to contentY [0, maxContentY]
if (root.flickable) {
const contentHeight = root.flickable.contentHeight;
const height = root.flickable.height;
if (contentHeight > height) {
const maxContentY = contentHeight - height;
const maxPos = 1 - root.size;
const contentY = maxPos > 0 ? (newPos / maxPos) * maxContentY : 0;
root.flickable.contentY = Math.max(0, Math.min(maxContentY, contentY));
}
}
}
}
Behavior on position { onPositionChanged: event => {
enabled: !fullMouse.pressed root._updatingFromUser = true;
const newPos = Math.max(0, Math.min(1 - root.size, event.y / root.height - root.size / 2));
Anim {} root.nonAnimPosition = newPos;
} // Update flickable position
// Map scrollbar position [0, 1-size] to contentY [0, maxContentY]
if (root.flickable) {
const contentHeight = root.flickable.contentHeight;
const height = root.flickable.height;
if (contentHeight > height) {
const maxContentY = contentHeight - height;
const maxPos = 1 - root.size;
const contentY = maxPos > 0 ? (newPos / maxPos) * maxContentY : 0;
root.flickable.contentY = Math.max(0, Math.min(maxContentY, contentY));
}
}
}
onPressed: event => {
root.animating = true;
root._updatingFromUser = true;
const newPos = Math.max(0, Math.min(1 - root.size, event.y / root.height - root.size / 2));
root.nonAnimPosition = newPos;
// Update flickable position
// Map scrollbar position [0, 1-size] to contentY [0, maxContentY]
if (root.flickable) {
const contentHeight = root.flickable.contentHeight;
const height = root.flickable.height;
if (contentHeight > height) {
const maxContentY = contentHeight - height;
const maxPos = 1 - root.size;
const contentY = maxPos > 0 ? (newPos / maxPos) * maxContentY : 0;
root.flickable.contentY = Math.max(0, Math.min(maxContentY, contentY));
}
}
}
}
} }
+36 -44
View File
@@ -1,53 +1,45 @@
import QtQuick import QtQuick
import QtQuick.Templates import QtQuick.Templates
import qs.Config import qs.Config
import qs.Modules
Slider { Slider {
id: root id: root
background: Item { background: Item {
CustomRect { CustomRect {
anchors.top: parent.top anchors.bottom: parent.bottom
anchors.bottom: parent.bottom anchors.left: parent.left
anchors.left: parent.left anchors.top: parent.top
bottomRightRadius: root.implicitHeight / 6
color: DynamicColors.palette.m3primary
implicitWidth: root.handle.x - root.implicitHeight / 2
radius: 1000
topRightRadius: root.implicitHeight / 6
}
implicitWidth: root.handle.x - root.implicitHeight / 2 CustomRect {
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.top: parent.top
bottomLeftRadius: root.implicitHeight / 6
color: DynamicColors.tPalette.m3surfaceContainer
implicitWidth: parent.width - root.handle.x - root.handle.implicitWidth - root.implicitHeight / 2
radius: 1000
topLeftRadius: root.implicitHeight / 6
}
}
handle: CustomRect {
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3primary
implicitHeight: 15
implicitWidth: 5
radius: 1000
x: root.visualPosition * root.availableWidth - implicitWidth / 2
color: DynamicColors.palette.m3primary MouseArea {
radius: 1000 acceptedButtons: Qt.NoButton
topRightRadius: root.implicitHeight / 6 anchors.fill: parent
bottomRightRadius: root.implicitHeight / 6 cursorShape: Qt.PointingHandCursor
} }
}
CustomRect {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
implicitWidth: parent.width - root.handle.x - root.handle.implicitWidth - root.implicitHeight / 2
color: DynamicColors.tPalette.m3surfaceContainer
radius: 1000
topLeftRadius: root.implicitHeight / 6
bottomLeftRadius: root.implicitHeight / 6
}
}
handle: CustomRect {
x: root.visualPosition * root.availableWidth - implicitWidth / 2
implicitWidth: 5
implicitHeight: 15
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3primary
radius: 1000
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: Qt.PointingHandCursor
}
}
} }
+169
View File
@@ -0,0 +1,169 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Config
RowLayout {
id: root
property string displayText: root.value.toString()
property bool isEditing: false
property real max: Infinity
property real min: -Infinity
property alias repeatRate: timer.interval
property real step: 1
property real value
signal valueModified(value: real)
spacing: Appearance.spacing.small
onValueChanged: {
if (!root.isEditing) {
root.displayText = root.value.toString();
}
}
CustomTextField {
id: textField
implicitHeight: upButton.implicitHeight
inputMethodHints: Qt.ImhFormattedNumbersOnly
leftPadding: Appearance.padding.normal
padding: Appearance.padding.small
rightPadding: Appearance.padding.normal
text: root.isEditing ? text : root.displayText
background: CustomRect {
color: DynamicColors.tPalette.m3surfaceContainerHigh
implicitWidth: 100
radius: Appearance.rounding.full
}
validator: DoubleValidator {
bottom: root.min
decimals: root.step < 1 ? Math.max(1, Math.ceil(-Math.log10(root.step))) : 0
top: root.max
}
onAccepted: {
const numValue = parseFloat(text);
if (!isNaN(numValue)) {
const clampedValue = Math.max(root.min, Math.min(root.max, numValue));
root.value = clampedValue;
root.displayText = clampedValue.toString();
root.valueModified(clampedValue);
} else {
text = root.displayText;
}
root.isEditing = false;
}
onActiveFocusChanged: {
if (activeFocus) {
root.isEditing = true;
} else {
root.isEditing = false;
root.displayText = root.value.toString();
}
}
onEditingFinished: {
if (text !== root.displayText) {
const numValue = parseFloat(text);
if (!isNaN(numValue)) {
const clampedValue = Math.max(root.min, Math.min(root.max, numValue));
root.value = clampedValue;
root.displayText = clampedValue.toString();
root.valueModified(clampedValue);
} else {
text = root.displayText;
}
}
root.isEditing = false;
}
}
CustomRect {
id: upButton
color: DynamicColors.palette.m3primary
implicitHeight: upIcon.implicitHeight + Appearance.padding.small * 2
implicitWidth: implicitHeight
radius: Appearance.rounding.full
StateLayer {
id: upState
function onClicked(): void {
let newValue = Math.min(root.max, root.value + root.step);
// Round to avoid floating point precision errors
const decimals = root.step < 1 ? Math.max(1, Math.ceil(-Math.log10(root.step))) : 0;
newValue = Math.round(newValue * Math.pow(10, decimals)) / Math.pow(10, decimals);
root.value = newValue;
root.displayText = newValue.toString();
root.valueModified(newValue);
}
color: DynamicColors.palette.m3onPrimary
onPressAndHold: timer.start()
onReleased: timer.stop()
}
MaterialIcon {
id: upIcon
anchors.centerIn: parent
color: DynamicColors.palette.m3onPrimary
text: "keyboard_arrow_up"
}
}
CustomRect {
color: DynamicColors.palette.m3primary
implicitHeight: downIcon.implicitHeight + Appearance.padding.small * 2
implicitWidth: implicitHeight
radius: Appearance.rounding.full
StateLayer {
id: downState
function onClicked(): void {
let newValue = Math.max(root.min, root.value - root.step);
// Round to avoid floating point precision errors
const decimals = root.step < 1 ? Math.max(1, Math.ceil(-Math.log10(root.step))) : 0;
newValue = Math.round(newValue * Math.pow(10, decimals)) / Math.pow(10, decimals);
root.value = newValue;
root.displayText = newValue.toString();
root.valueModified(newValue);
}
color: DynamicColors.palette.m3onPrimary
onPressAndHold: timer.start()
onReleased: timer.stop()
}
MaterialIcon {
id: downIcon
anchors.centerIn: parent
color: DynamicColors.palette.m3onPrimary
text: "keyboard_arrow_down"
}
}
Timer {
id: timer
interval: 100
repeat: true
triggeredOnStart: true
onTriggered: {
if (upState.pressed)
upState.onClicked();
else if (downState.pressed)
downState.onClicked();
}
}
}
+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();
}
}
}
}
+133 -44
View File
@@ -1,66 +1,155 @@
import qs.Config
import qs.Modules
import QtQuick import QtQuick
import QtQuick.Templates import QtQuick.Templates
import QtQuick.Shapes import QtQuick.Shapes
import qs.Config
Switch { Switch {
id: root id: root
property int cLayer: 1 property int cLayer: 1
implicitWidth: implicitIndicatorWidth implicitHeight: implicitIndicatorHeight
implicitHeight: implicitIndicatorHeight implicitWidth: implicitIndicatorWidth
indicator: CustomRect { indicator: CustomRect {
radius: 1000 color: root.checked ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, root.cLayer)
color: root.checked ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, root.cLayer) implicitHeight: 13 + 7 * 2
implicitWidth: implicitHeight * 1.7
radius: 1000
implicitWidth: implicitHeight * 1.7 CustomRect {
implicitHeight: 13 + 7 * 2 readonly property real nonAnimWidth: root.pressed ? implicitHeight * 1.3 : implicitHeight
CustomRect { anchors.verticalCenter: parent.verticalCenter
readonly property real nonAnimWidth: root.pressed ? implicitHeight * 1.3 : implicitHeight color: root.checked ? DynamicColors.palette.m3onPrimary : DynamicColors.layer(DynamicColors.palette.m3outline, root.cLayer + 1)
implicitHeight: parent.implicitHeight - 10
implicitWidth: nonAnimWidth
radius: 1000
x: root.checked ? parent.implicitWidth - nonAnimWidth - 10 / 2 : 10 / 2
radius: 1000 Behavior on implicitWidth {
color: root.checked ? DynamicColors.palette.m3onPrimary : DynamicColors.layer(DynamicColors.palette.m3outline, root.cLayer + 1) Anim {
}
}
Behavior on x {
Anim {
}
}
x: root.checked ? parent.implicitWidth - nonAnimWidth - 10 / 2 : 10 / 2 CustomRect {
implicitWidth: nonAnimWidth anchors.fill: parent
implicitHeight: parent.implicitHeight - 10 color: root.checked ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurface
anchors.verticalCenter: parent.verticalCenter opacity: root.pressed ? 0.1 : root.hovered ? 0.08 : 0
radius: parent.radius
CustomRect { Behavior on opacity {
anchors.fill: parent Anim {
radius: parent.radius }
}
}
color: root.checked ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurface Shape {
opacity: root.pressed ? 0.1 : root.hovered ? 0.08 : 0 id: icon
Behavior on opacity { property point end1: {
Anim {} if (root.pressed) {
} if (root.checked)
} return Qt.point(width * 0.4, height / 2);
return Qt.point(width * 0.8, height / 2);
}
if (root.checked)
return Qt.point(width * 0.4, height * 0.7);
return Qt.point(width * 0.85, height * 0.85);
}
property point end2: {
if (root.pressed)
return Qt.point(width, height / 2);
if (root.checked)
return Qt.point(width * 0.85, height * 0.2);
return Qt.point(width * 0.85, height * 0.15);
}
property point start1: {
if (root.pressed)
return Qt.point(width * 0.1, height / 2);
if (root.checked)
return Qt.point(width * 0.15, height / 2);
return Qt.point(width * 0.15, height * 0.15);
}
property point start2: {
if (root.pressed) {
if (root.checked)
return Qt.point(width * 0.4, height / 2);
return Qt.point(width * 0.2, height / 2);
}
if (root.checked)
return Qt.point(width * 0.4, height * 0.7);
return Qt.point(width * 0.15, height * 0.85);
}
Behavior on x { anchors.centerIn: parent
Anim {} asynchronous: true
} height: parent.implicitHeight - Appearance.padding.small * 2
preferredRendererType: Shape.CurveRenderer
width: height
Behavior on implicitWidth { Behavior on end1 {
Anim {} PropAnim {
} }
} }
} Behavior on end2 {
PropAnim {
}
}
Behavior on start1 {
PropAnim {
}
}
Behavior on start2 {
PropAnim {
}
}
MouseArea { ShapePath {
anchors.fill: parent capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
cursorShape: Qt.PointingHandCursor fillColor: "transparent"
enabled: false startX: icon.start1.x
} startY: icon.start1.y
strokeColor: root.checked ? DynamicColors.palette.m3primary : DynamicColors.palette.m3surfaceContainerHighest
strokeWidth: Appearance.font.size.larger * 0.15
component PropAnim: PropertyAnimation { Behavior on strokeColor {
CAnim {
}
}
PathLine {
x: icon.end1.x
y: icon.end1.y
}
PathMove {
x: icon.start2.x
y: icon.start2.y
}
PathLine {
x: icon.end2.x
y: icon.end2.y
}
}
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
enabled: false
}
component PropAnim: PropertyAnimation {
duration: MaterialEasing.expressiveEffectsTime duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects easing.bezierCurve: MaterialEasing.expressiveEffects
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
} }
} }
+38 -36
View File
@@ -2,48 +2,50 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import qs.Config import qs.Config
import qs.Modules
Text { Text {
id: root id: root
property bool animate: false property bool animate: false
property string animateProp: "scale" property int animateDuration: 400
property real animateFrom: 0 property real animateFrom: 0
property real animateTo: 1 property string animateProp: "scale"
property int animateDuration: 400 property real animateTo: 1
renderType: Text.NativeRendering color: DynamicColors.palette.m3onSurface
textFormat: Text.PlainText font.family: Appearance.font.family.sans
color: DynamicColors.palette.m3onSurface font.pointSize: Appearance.font.size.normal
font.family: Appearance.font.family.sans renderType: Text.NativeRendering
font.pointSize: 12 textFormat: Text.PlainText
Behavior on color { Behavior on color {
CAnim {} CAnim {
} }
}
Behavior on text {
enabled: root.animate
Behavior on text { SequentialAnimation {
enabled: root.animate Anim {
easing.bezierCurve: MaterialEasing.standardAccel
to: root.animateFrom
}
SequentialAnimation { PropertyAction {
Anim { }
to: root.animateFrom
easing.bezierCurve: MaterialEasing.standardAccel
}
PropertyAction {}
Anim {
to: root.animateTo
easing.bezierCurve: MaterialEasing.standardDecel
}
}
}
component Anim: NumberAnimation { Anim {
target: root easing.bezierCurve: MaterialEasing.standardDecel
property: root.animateProp.split(",").length === 1 ? root.animateProp : "" to: root.animateTo
properties: root.animateProp.split(",").length > 1 ? root.animateProp : "" }
duration: root.animateDuration / 2 }
easing.type: Easing.BezierSpline }
}
component Anim: NumberAnimation {
duration: root.animateDuration / 2
easing.type: Easing.BezierSpline
properties: root.animateProp.split(",").length > 1 ? root.animateProp : ""
property: root.animateProp.split(",").length === 1 ? root.animateProp : ""
target: root
}
} }
+54 -55
View File
@@ -2,75 +2,74 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import qs.Helpers
import qs.Config import qs.Config
import qs.Modules
TextField { TextField {
id: root id: root
color: DynamicColors.palette.m3onSurface background: null
placeholderTextColor: DynamicColors.palette.m3outline color: DynamicColors.palette.m3onSurface
font.family: Appearance.font.family.sans cursorVisible: !readOnly
font.pointSize: Appearance.font.size.smaller font.family: Appearance.font.family.sans
renderType: echoMode === TextField.Password ? TextField.QtRendering : TextField.NativeRendering font.pointSize: Appearance.font.size.smaller
cursorVisible: !readOnly placeholderTextColor: DynamicColors.palette.m3outline
renderType: echoMode === TextField.Password ? TextField.QtRendering : TextField.NativeRendering
background: null Behavior on color {
CAnim {
}
}
cursorDelegate: CustomRect {
id: cursor
cursorDelegate: CustomRect { property bool disableBlink
id: cursor
property bool disableBlink color: DynamicColors.palette.m3primary
implicitWidth: 2
radius: Appearance.rounding.normal
implicitWidth: 2 Behavior on opacity {
color: DynamicColors.palette.m3primary Anim {
radius: Appearance.rounding.normal duration: Appearance.anim.durations.small
}
}
Connections { Connections {
target: root function onCursorPositionChanged(): void {
if (root.activeFocus && root.cursorVisible) {
cursor.opacity = 1;
cursor.disableBlink = true;
enableBlink.restart();
}
}
function onCursorPositionChanged(): void { target: root
if (root.activeFocus && root.cursorVisible) { }
cursor.opacity = 1;
cursor.disableBlink = true;
enableBlink.restart();
}
}
}
Timer { Timer {
id: enableBlink id: enableBlink
interval: 100 interval: 100
onTriggered: cursor.disableBlink = false
}
Timer { onTriggered: cursor.disableBlink = false
running: root.activeFocus && root.cursorVisible && !cursor.disableBlink }
repeat: true
triggeredOnStart: true
interval: 500
onTriggered: parent.opacity = parent.opacity === 1 ? 0 : 1
}
Binding { Timer {
when: !root.activeFocus || !root.cursorVisible interval: 500
cursor.opacity: 0 repeat: true
} running: root.activeFocus && root.cursorVisible && !cursor.disableBlink
triggeredOnStart: true
Behavior on opacity { onTriggered: parent.opacity = parent.opacity === 1 ? 0 : 1
Anim { }
duration: Appearance.anim.durations.small
}
}
}
Behavior on color { Binding {
CAnim {} cursor.opacity: 0
} when: !root.activeFocus || !root.cursorVisible
}
Behavior on placeholderTextColor { }
CAnim {} Behavior on placeholderTextColor {
} CAnim {
}
}
} }
+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
}
}
+17 -15
View File
@@ -3,21 +3,23 @@ import QtQuick.Controls
import qs.Components import qs.Components
ToolTip { ToolTip {
id: root id: root
property bool extraVisibleCondition: true
property bool alternativeVisibleCondition: false
readonly property bool internalVisibleCondition: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition
verticalPadding: 5
horizontalPadding: 10
background: null
visible: internalVisibleCondition property bool alternativeVisibleCondition: false
property bool extraVisibleCondition: true
readonly property bool internalVisibleCondition: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition
contentItem: CustomTooltipContent { background: null
id: contentItem horizontalPadding: 10
text: root.text verticalPadding: 5
shown: root.internalVisibleCondition visible: internalVisibleCondition
horizontalPadding: root.horizontalPadding
verticalPadding: root.verticalPadding contentItem: CustomTooltipContent {
} id: contentItem
horizontalPadding: root.horizontalPadding
shown: root.internalVisibleCondition
text: root.text
verticalPadding: root.verticalPadding
}
} }
+44 -38
View File
@@ -1,48 +1,54 @@
import QtQuick import QtQuick
import qs.Components import qs.Components
import qs.Modules
import qs.Config import qs.Config
Item { Item {
id: root id: root
required property string text
property bool shown: false
property real horizontalPadding: 10
property real verticalPadding: 5
implicitWidth: tooltipTextObject.implicitWidth + 2 * root.horizontalPadding
implicitHeight: tooltipTextObject.implicitHeight + 2 * root.verticalPadding
property bool isVisible: backgroundRectangle.implicitHeight > 0 property real horizontalPadding: 10
property bool isVisible: backgroundRectangle.implicitHeight > 0
property bool shown: false
required property string text
property real verticalPadding: 5
Rectangle { implicitHeight: tooltipTextObject.implicitHeight + 2 * root.verticalPadding
id: backgroundRectangle implicitWidth: tooltipTextObject.implicitWidth + 2 * root.horizontalPadding
anchors {
bottom: root.bottom
horizontalCenter: root.horizontalCenter
}
color: DynamicColors.tPalette.m3inverseSurface ?? "#3C4043"
radius: 8
opacity: shown ? 1 : 0
implicitWidth: shown ? (tooltipTextObject.implicitWidth + 2 * root.horizontalPadding) : 0
implicitHeight: shown ? (tooltipTextObject.implicitHeight + 2 * root.verticalPadding) : 0
clip: true
Behavior on implicitWidth { Rectangle {
Anim {} id: backgroundRectangle
}
Behavior on implicitHeight {
Anim {}
}
Behavior on opacity {
Anim {}
}
CustomText { clip: true
id: tooltipTextObject color: DynamicColors.tPalette.m3inverseSurface ?? "#3C4043"
anchors.centerIn: parent implicitHeight: shown ? (tooltipTextObject.implicitHeight + 2 * root.verticalPadding) : 0
text: root.text implicitWidth: shown ? (tooltipTextObject.implicitWidth + 2 * root.horizontalPadding) : 0
color: DynamicColors.palette.m3inverseOnSurface ?? "#FFFFFF" opacity: shown ? 1 : 0
wrapMode: Text.Wrap radius: 8
}
} Behavior on implicitHeight {
Anim {
}
}
Behavior on implicitWidth {
Anim {
}
}
Behavior on opacity {
Anim {
}
}
anchors {
bottom: root.bottom
horizontalCenter: root.horizontalCenter
}
CustomText {
id: tooltipTextObject
anchors.centerIn: parent
color: DynamicColors.palette.m3inverseOnSurface ?? "#FFFFFF"
text: root.text
wrapMode: Text.Wrap
}
}
} }
+9
View File
@@ -0,0 +1,9 @@
import Quickshell
import Quickshell.Wayland
PanelWindow {
required property string name
WlrLayershell.namespace: `ZShell-${name}`
color: "transparent"
}
+10 -10
View File
@@ -1,18 +1,18 @@
import qs.Config import qs.Config
import qs.Modules
import QtQuick import QtQuick
import QtQuick.Effects import QtQuick.Effects
RectangularShadow { RectangularShadow {
property int level property real dp: [0, 1, 3, 6, 8, 12][level]
property real dp: [0, 1, 3, 6, 8, 12][level] property int level
color: Qt.alpha(DynamicColors.palette.m3shadow, 0.7) blur: (dp * 5) ** 0.7
blur: (dp * 5) ** 0.7 color: Qt.alpha(DynamicColors.palette.m3shadow, 0.7)
spread: -dp * 0.3 + (dp * 0.1) ** 2 offset.y: dp / 2
offset.y: dp / 2 spread: -dp * 0.3 + (dp * 0.1) ** 2
Behavior on dp { Behavior on dp {
Anim {} Anim {
} }
}
} }
+34 -39
View File
@@ -1,49 +1,44 @@
import qs.Config import qs.Config
import qs.Modules
import QtQuick import QtQuick
CustomRect { CustomRect {
required property int extra required property int extra
anchors.right: parent.right anchors.margins: 8
anchors.margins: 8 anchors.right: parent.right
color: DynamicColors.palette.m3tertiary
implicitHeight: count.implicitHeight + 4 * 2
implicitWidth: count.implicitWidth + 8 * 2
opacity: extra > 0 ? 1 : 0
radius: 8
scale: extra > 0 ? 1 : 0.5
color: DynamicColors.palette.m3tertiary Behavior on opacity {
radius: 8 Anim {
implicitWidth: count.implicitWidth + 8 * 2
implicitHeight: count.implicitHeight + 4 * 2
opacity: extra > 0 ? 1 : 0
scale: extra > 0 ? 1 : 0.5
Elevation {
anchors.fill: parent
radius: parent.radius
opacity: parent.opacity
z: -1
level: 2
}
CustomText {
id: count
anchors.centerIn: parent
animate: parent.opacity > 0
text: qsTr("+%1").arg(parent.extra)
color: DynamicColors.palette.m3onTertiary
}
Behavior on opacity {
Anim {
duration: MaterialEasing.expressiveEffectsTime duration: MaterialEasing.expressiveEffectsTime
} }
} }
Behavior on scale {
Behavior on scale { Anim {
Anim {
duration: MaterialEasing.expressiveEffectsTime duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects easing.bezierCurve: MaterialEasing.expressiveEffects
} }
} }
Elevation {
anchors.fill: parent
level: 2
opacity: parent.opacity
radius: parent.radius
z: -1
}
CustomText {
id: count
anchors.centerIn: parent
animate: parent.opacity > 0
color: DynamicColors.palette.m3onTertiary
text: qsTr("+%1").arg(parent.extra)
}
} }
+21 -138
View File
@@ -1,146 +1,29 @@
import QtQuick import QtQuick
import QtQuick.Templates
import qs.Helpers
import qs.Config import qs.Config
import qs.Modules
Slider { BaseStyledSlider {
id: root id: root
required property string icon trackContent: Component {
property real oldValue Item {
property bool initialized property var groove
property color color: DynamicColors.palette.m3secondary 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
radius: groove?.radius
y: root.handle.y width: rootSlider?.isHorizontal ? handleWidth + rootSlider?.visualPosition * (groove?.width - handleWidth) : groove?.width
implicitHeight: parent.height - y x: rootSlider?.isHorizontal ? (rootSlider?.mirrored ? groove?.width - width : 0) : 0
y: rootSlider?.isVertical ? groove?.height - height : 0
color: root.color }
radius: parent.radius }
} }
}
handle: Item {
id: handle
property alias moving: icon.moving
y: root.visualPosition * (root.availableHeight - height)
implicitWidth: root.width
implicitHeight: root.width
Elevation {
anchors.fill: parent
radius: rect.radius
level: handleInteraction.containsMouse ? 2 : 1
}
CustomRect {
id: rect
anchors.fill: parent
color: DynamicColors.palette.m3inverseSurface
radius: Appearance.rounding.full
MouseArea {
id: handleInteraction
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.NoButton
}
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;
}
text: root.icon
color: DynamicColors.palette.m3inverseOnSurface
anchors.centerIn: parent
onMovingChanged: anim.restart()
Binding {
id: binding
target: icon
property: "text"
value: Math.round(root.value * 100)
when: false
}
SequentialAnimation {
id: anim
Anim {
target: icon
property: "scale"
to: 0
duration: Appearance.anim.durations.normal / 2
easing.bezierCurve: Appearance.anim.curves.standardAccel
}
ScriptAction {
script: icon.update()
}
Anim {
target: icon
property: "scale"
to: 1
duration: Appearance.anim.durations.normal / 2
easing.bezierCurve: Appearance.anim.curves.standardDecel
}
}
}
}
}
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;
}
}
Behavior on value {
Anim {
duration: Appearance.anim.durations.large
}
}
} }
+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
}
}
}
}
}
}
+62 -64
View File
@@ -1,82 +1,80 @@
import qs.Config import qs.Config
import qs.Modules
import QtQuick import QtQuick
CustomRect { CustomRect {
id: root id: root
enum Type { enum Type {
Filled, Filled,
Tonal, Tonal,
Text Text
} }
property alias icon: label.text property color activeColour: type === IconButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondary
property bool checked property color activeOnColour: type === IconButton.Filled ? DynamicColors.palette.m3onPrimary : type === IconButton.Tonal ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3primary
property bool toggle property bool checked
property real padding: type === IconButton.Text ? 10 / 2 : 7 property bool disabled
property alias font: label.font property color disabledColour: Qt.alpha(DynamicColors.palette.m3onSurface, 0.1)
property int type: IconButton.Filled property color disabledOnColour: Qt.alpha(DynamicColors.palette.m3onSurface, 0.38)
property bool disabled property alias font: label.font
property alias icon: label.text
property color inactiveColour: {
if (!toggle && type === IconButton.Filled)
return DynamicColors.palette.m3primary;
return type === IconButton.Filled ? DynamicColors.tPalette.m3surfaceContainer : DynamicColors.palette.m3secondaryContainer;
}
property color inactiveOnColour: {
if (!toggle && type === IconButton.Filled)
return DynamicColors.palette.m3onPrimary;
return type === IconButton.Tonal ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurfaceVariant;
}
property bool internalChecked
property alias label: label
property real padding: type === IconButton.Text ? 10 / 2 : 7
property alias radiusAnim: radiusAnim
property alias stateLayer: stateLayer
property bool toggle
property int type: IconButton.Filled
property alias stateLayer: stateLayer signal clicked
property alias label: label
property alias radiusAnim: radiusAnim
property bool internalChecked color: type === IconButton.Text ? "transparent" : disabled ? disabledColour : internalChecked ? activeColour : inactiveColour
property color activeColour: type === IconButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondary implicitHeight: label.implicitHeight + padding * 2
property color inactiveColour: { implicitWidth: implicitHeight
if (!toggle && type === IconButton.Filled) radius: internalChecked ? 6 : implicitHeight / 2 * Math.min(1, 1)
return DynamicColors.palette.m3primary;
return type === IconButton.Filled ? DynamicColors.tPalette.m3surfaceContainer : DynamicColors.palette.m3secondaryContainer;
}
property color activeOnColour: type === IconButton.Filled ? DynamicColors.palette.m3onPrimary : type === IconButton.Tonal ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3primary
property color inactiveOnColour: {
if (!toggle && type === IconButton.Filled)
return DynamicColors.palette.m3onPrimary;
return type === IconButton.Tonal ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurfaceVariant;
}
property color disabledColour: Qt.alpha(DynamicColors.palette.m3onSurface, 0.1)
property color disabledOnColour: Qt.alpha(DynamicColors.palette.m3onSurface, 0.38)
signal clicked Behavior on radius {
Anim {
id: radiusAnim
onCheckedChanged: internalChecked = checked }
}
radius: internalChecked ? 6 : implicitHeight / 2 * Math.min(1, 1) onCheckedChanged: internalChecked = checked
color: type === IconButton.Text ? "transparent" : disabled ? disabledColour : internalChecked ? activeColour : inactiveColour
implicitWidth: implicitHeight StateLayer {
implicitHeight: label.implicitHeight + padding * 2 id: stateLayer
StateLayer { function onClicked(): void {
id: stateLayer if (root.toggle)
root.internalChecked = !root.internalChecked;
root.clicked();
}
color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour
disabled: root.disabled disabled: root.disabled
}
function onClicked(): void { MaterialIcon {
if (root.toggle) id: label
root.internalChecked = !root.internalChecked;
root.clicked();
}
}
MaterialIcon { anchors.centerIn: parent
id: label color: root.disabled ? root.disabledOnColour : root.internalChecked ? root.activeOnColour : root.inactiveOnColour
fill: !root.toggle || root.internalChecked ? 1 : 0
anchors.centerIn: parent Behavior on fill {
color: root.disabled ? root.disabledOnColour : root.internalChecked ? root.activeOnColour : root.inactiveOnColour Anim {
fill: !root.toggle || root.internalChecked ? 1 : 0 }
}
Behavior on fill { }
Anim {}
}
}
Behavior on radius {
Anim {
id: radiusAnim
}
}
} }
+221
View File
@@ -0,0 +1,221 @@
import QtQuick
import qs.Config
Item {
id: root
property alias anim: marqueeAnim
property bool animate: false
property color color: DynamicColors.palette.m3onSurface
property int fadeStrengthAnimMs: 180
property real fadeStrengthIdle: 0.0
property real fadeStrengthMoving: 1.0
property alias font: elideText.font
property int gap: 40
property alias horizontalAlignment: elideText.horizontalAlignment
property bool leftFadeEnabled: false
property real leftFadeStrength: overflowing && leftFadeEnabled ? fadeStrengthMoving : fadeStrengthIdle
property int leftFadeWidth: 28
property bool marqueeEnabled: true
readonly property bool overflowing: metrics.width > root.width
property int pauseMs: 1200
property real pixelsPerSecond: 40
property real rightFadeStrength: overflowing ? fadeStrengthMoving : fadeStrengthIdle
property int rightFadeWidth: 28
property bool sliding: false
property alias text: elideText.text
function durationForDistance(px): int {
return Math.max(1, Math.round(Math.abs(px) / root.pixelsPerSecond * 1000));
}
function resetMarquee() {
marqueeAnim.stop();
strip.x = 0;
root.sliding = false;
root.leftFadeEnabled = false;
if (root.marqueeEnabled && root.overflowing && root.visible) {
marqueeAnim.restart();
}
}
clip: true
implicitHeight: elideText.implicitHeight
Behavior on leftFadeStrength {
Anim {
}
}
Behavior on rightFadeStrength {
Anim {
}
}
onTextChanged: resetMarquee()
onVisibleChanged: if (!visible)
resetMarquee()
onWidthChanged: resetMarquee()
TextMetrics {
id: metrics
font: elideText.font
text: elideText.text
}
CustomText {
id: elideText
anchors.verticalCenter: parent.verticalCenter
animate: root.animate
animateProp: "scale,opacity"
color: root.color
elide: Text.ElideNone
visible: !root.overflowing
width: root.width
}
Item {
id: marqueeViewport
anchors.fill: parent
clip: true
layer.enabled: true
visible: root.overflowing
layer.effect: OpacityMask {
maskSource: rightFadeMask
}
Item {
id: strip
anchors.verticalCenter: parent.verticalCenter
height: t1.implicitHeight
width: t1.width + root.gap + t2.width
x: 0
CustomText {
id: t1
animate: root.animate
animateProp: "opacity"
color: root.color
text: elideText.text
}
CustomText {
id: t2
animate: root.animate
animateProp: "opacity"
color: root.color
text: t1.text
x: t1.width + root.gap
}
}
SequentialAnimation {
id: marqueeAnim
running: false
onFinished: pauseTimer.restart()
ScriptAction {
script: {
root.sliding = true;
root.leftFadeEnabled = true;
}
}
Anim {
duration: root.durationForDistance(t1.width)
easing.bezierCurve: Easing.Linear
easing.type: Easing.Linear
from: 0
property: "x"
target: strip
to: -t1.width
}
ScriptAction {
script: {
root.leftFadeEnabled = false;
}
}
Anim {
duration: root.durationForDistance(root.gap)
easing.bezierCurve: Easing.Linear
easing.type: Easing.Linear
from: -t1.width
property: "x"
target: strip
to: -(t1.width + root.gap)
}
ScriptAction {
script: {
root.sliding = false;
strip.x = 0;
}
}
}
Timer {
id: pauseTimer
interval: root.pauseMs
repeat: false
running: true
onTriggered: {
if (root.marqueeEnabled)
marqueeAnim.start();
}
}
}
Rectangle {
id: rightFadeMask
readonly property real fadeStartPos: {
const w = Math.max(1, width);
return Math.max(0, Math.min(1, (w - root.rightFadeWidth) / w));
}
readonly property real leftFadeEndPos: {
const w = Math.max(1, width);
return Math.max(0, Math.min(1, root.leftFadeWidth / w));
}
anchors.fill: marqueeViewport
layer.enabled: true
visible: false
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop {
color: Qt.rgba(1, 1, 1, 1.0 - root.leftFadeStrength)
position: 0.0
}
GradientStop {
color: Qt.rgba(1, 1, 1, 1.0)
position: rightFadeMask.leftFadeEndPos
}
GradientStop {
color: Qt.rgba(1, 1, 1, 1.0)
position: rightFadeMask.fadeStartPos
}
GradientStop {
color: Qt.rgba(1, 1, 1, 1.0 - root.rightFadeStrength)
position: 1.0
}
}
}
}
+10 -10
View File
@@ -1,15 +1,15 @@
import qs.Config import qs.Config
CustomText { CustomText {
property real fill property real fill
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,
opsz: fontInfo.pixelSize, opsz: fontInfo.pixelSize,
wght: fontInfo.weight wght: fontInfo.weight
}) })
} }
+115
View File
@@ -0,0 +1,115 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Config
Elevation {
id: root
property MenuItem active: items[0] ?? null
property bool expanded
property list<MenuItem> items
signal itemSelected(item: MenuItem)
implicitHeight: root.expanded ? column.implicitHeight + Appearance.padding.small * 2 : 0
implicitWidth: Math.max(200, column.implicitWidth)
level: 2
opacity: root.expanded ? 1 : 0
radius: Appearance.rounding.normal
Behavior on implicitHeight {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
}
}
CustomClippingRect {
anchors.fill: parent
color: DynamicColors.palette.m3surfaceContainer
radius: parent.radius
ColumnLayout {
id: column
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 5
Repeater {
model: root.items
CustomRect {
id: item
readonly property bool active: modelData === root.active
required property int index
required property MenuItem modelData
Layout.fillWidth: true
implicitHeight: menuOptionRow.implicitHeight + 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 {
function onClicked(): void {
root.itemSelected(item.modelData);
root.active = item.modelData;
root.expanded = false;
}
color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
disabled: !root.expanded
}
}
RowLayout {
id: menuOptionRow
anchors.fill: parent
anchors.margins: Appearance.padding.normal
spacing: Appearance.spacing.small
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurfaceVariant
text: item.modelData.icon
}
CustomText {
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
text: item.modelData.text
}
Loader {
Layout.alignment: Qt.AlignVCenter
active: item.modelData.trailingIcon.length > 0
visible: active
sourceComponent: MaterialIcon {
color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
text: item.modelData.trailingIcon
}
}
}
}
}
}
}
}
+12
View File
@@ -0,0 +1,12 @@
import QtQuick
QtObject {
property string activeIcon: icon
property string activeText: text
property string icon
required property string text
property string trailingIcon
property var value
signal clicked
}
+3 -3
View File
@@ -2,8 +2,8 @@ import Quickshell
import QtQuick import QtQuick
ShaderEffect { ShaderEffect {
required property Item source required property Item maskSource
required property Item maskSource required property Item source
fragmentShader: Qt.resolvedUrl(`${Quickshell.shellDir}/assets/shaders/opacitymask.frag.qsb`) fragmentShader: Qt.resolvedUrl(`${Quickshell.shellDir}/assets/shaders/opacitymask.frag.qsb`)
} }
+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
}
}
+180
View File
@@ -0,0 +1,180 @@
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
z: root.expanded ? 100 : 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
z: root.z
// 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
}
}
}
+3 -3
View File
@@ -1,8 +1,8 @@
import QtQuick import QtQuick
QtObject { QtObject {
required property var service required property var service
Component.onCompleted: service.refCount++ Component.onCompleted: service.refCount++
Component.onDestruction: service.refCount-- Component.onDestruction: service.refCount--
} }
+50
View File
@@ -0,0 +1,50 @@
import QtQuick
import QtQuick.Layouts
import qs.Config
CustomRect {
id: root
required property string label
required property real max
required property real min
property var onValueModified: function (value) {}
property real step: 1
required property real value
Layout.fillWidth: true
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, 2)
implicitHeight: row.implicitHeight + Appearance.padding.large * 2
radius: Appearance.rounding.normal
Behavior on implicitHeight {
Anim {
}
}
RowLayout {
id: row
anchors.left: parent.left
anchors.margins: Appearance.padding.large
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.normal
CustomText {
Layout.fillWidth: true
text: root.label
}
CustomSpinBox {
max: root.max
min: root.min
step: root.step
value: root.value
onValueModified: value => {
root.onValueModified(value);
}
}
}
}
+72 -72
View File
@@ -1,96 +1,96 @@
import qs.Config import qs.Config
import qs.Modules
import QtQuick import QtQuick
MouseArea { MouseArea {
id: root id: root
property bool disabled property color color: DynamicColors.palette.m3onSurface
property color color: DynamicColors.palette.m3onSurface property bool disabled
property real radius: parent?.radius ?? 0 property real radius: parent?.radius ?? 0
property alias rect: hoverLayer property alias rect: hoverLayer
function onClicked(): void { function onClicked(): void {
} }
anchors.fill: parent anchors.fill: parent
cursorShape: disabled ? undefined : Qt.PointingHandCursor
enabled: !disabled
hoverEnabled: true
enabled: !disabled onClicked: event => !disabled && onClicked(event)
cursorShape: disabled ? undefined : Qt.PointingHandCursor onPressed: event => {
hoverEnabled: true if (disabled)
return;
onPressed: event => { rippleAnim.x = event.x;
if (disabled) rippleAnim.y = event.y;
return;
rippleAnim.x = event.x; const dist = (ox, oy) => ox * ox + oy * oy;
rippleAnim.y = event.y; rippleAnim.radius = Math.sqrt(Math.max(dist(event.x, event.y), dist(event.x, height - event.y), dist(width - event.x, event.y), dist(width - event.x, height - event.y)));
const dist = (ox, oy) => ox * ox + oy * oy; rippleAnim.restart();
rippleAnim.radius = Math.sqrt(Math.max(dist(event.x, event.y), dist(event.x, height - event.y), dist(width - event.x, event.y), dist(width - event.x, height - event.y))); }
rippleAnim.restart(); SequentialAnimation {
} id: rippleAnim
onClicked: event => !disabled && onClicked(event) property real radius
property real x
property real y
SequentialAnimation { PropertyAction {
id: rippleAnim property: "x"
target: ripple
value: rippleAnim.x
}
property real x PropertyAction {
property real y property: "y"
property real radius target: ripple
value: rippleAnim.y
}
PropertyAction { PropertyAction {
target: ripple property: "opacity"
property: "x" target: ripple
value: rippleAnim.x value: 0.08
} }
PropertyAction {
target: ripple
property: "y"
value: rippleAnim.y
}
PropertyAction {
target: ripple
property: "opacity"
value: 0.08
}
Anim {
target: ripple
properties: "implicitWidth,implicitHeight"
from: 0
to: rippleAnim.radius * 2
easing.bezierCurve: MaterialEasing.standardDecel
}
Anim {
target: ripple
property: "opacity"
to: 0
}
}
CustomClippingRect { Anim {
id: hoverLayer easing.bezierCurve: MaterialEasing.standardDecel
from: 0
properties: "implicitWidth,implicitHeight"
target: ripple
to: rippleAnim.radius * 2
}
anchors.fill: parent Anim {
property: "opacity"
target: ripple
to: 0
}
}
CustomClippingRect {
id: hoverLayer
anchors.fill: parent
border.pixelAligned: false border.pixelAligned: false
color: Qt.alpha(root.color, root.disabled ? 0 : root.pressed ? 0.1 : root.containsMouse ? 0.08 : 0)
radius: root.radius
color: Qt.alpha(root.color, root.disabled ? 0 : root.pressed ? 0.1 : root.containsMouse ? 0.08 : 0) CustomRect {
radius: root.radius id: ripple
CustomRect {
id: ripple
radius: 1000
color: root.color
opacity: 0
border.pixelAligned: false border.pixelAligned: false
color: root.color
opacity: 0
radius: 1000
transform: Translate { transform: Translate {
x: -ripple.width / 2 x: -ripple.width / 2
y: -ripple.height / 2 y: -ripple.height / 2
} }
} }
} }
} }
+110 -114
View File
@@ -1,135 +1,131 @@
import ZShell import ZShell
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import qs.Modules
import qs.Components import qs.Components
import qs.Helpers
import qs.Config import qs.Config
CustomRect { CustomRect {
id: root id: root
required property Toast modelData required property Toast modelData
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
implicitHeight: layout.implicitHeight + Appearance.padding.smaller * 2 border.color: {
let colour = DynamicColors.palette.m3outlineVariant;
if (root.modelData.type === Toast.Success)
colour = DynamicColors.palette.m3success;
if (root.modelData.type === Toast.Warning)
colour = DynamicColors.palette.m3secondaryContainer;
if (root.modelData.type === Toast.Error)
colour = DynamicColors.palette.m3error;
return Qt.alpha(colour, 0.3);
}
border.width: 1
color: {
if (root.modelData.type === Toast.Success)
return DynamicColors.palette.m3successContainer;
if (root.modelData.type === Toast.Warning)
return DynamicColors.palette.m3secondary;
if (root.modelData.type === Toast.Error)
return DynamicColors.palette.m3errorContainer;
return DynamicColors.palette.m3surface;
}
implicitHeight: layout.implicitHeight + Appearance.padding.smaller * 2
radius: Appearance.rounding.normal
radius: Appearance.rounding.normal Behavior on border.color {
color: { CAnim {
if (root.modelData.type === Toast.Success) }
return DynamicColors.palette.m3successContainer; }
if (root.modelData.type === Toast.Warning)
return DynamicColors.palette.m3secondary;
if (root.modelData.type === Toast.Error)
return DynamicColors.palette.m3errorContainer;
return DynamicColors.palette.m3surface;
}
border.width: 1 Elevation {
border.color: { anchors.fill: parent
let colour = DynamicColors.palette.m3outlineVariant; level: 3
if (root.modelData.type === Toast.Success) opacity: parent.opacity
colour = DynamicColors.palette.m3success; radius: parent.radius
if (root.modelData.type === Toast.Warning) z: -1
colour = DynamicColors.palette.m3secondaryContainer; }
if (root.modelData.type === Toast.Error)
colour = DynamicColors.palette.m3error;
return Qt.alpha(colour, 0.3);
}
Elevation { RowLayout {
anchors.fill: parent id: layout
radius: parent.radius
opacity: parent.opacity
z: -1
level: 3
}
RowLayout { anchors.fill: parent
id: layout anchors.leftMargin: Appearance.padding.normal
anchors.margins: Appearance.padding.smaller
anchors.rightMargin: Appearance.padding.normal
spacing: Appearance.spacing.normal
anchors.fill: parent CustomRect {
anchors.margins: Appearance.padding.smaller color: {
anchors.leftMargin: Appearance.padding.normal if (root.modelData.type === Toast.Success)
anchors.rightMargin: Appearance.padding.normal return DynamicColors.palette.m3success;
spacing: Appearance.spacing.normal if (root.modelData.type === Toast.Warning)
return DynamicColors.palette.m3secondaryContainer;
if (root.modelData.type === Toast.Error)
return DynamicColors.palette.m3error;
return DynamicColors.palette.m3surfaceContainerHigh;
}
implicitHeight: icon.implicitHeight + Appearance.padding.smaller * 2
implicitWidth: implicitHeight
radius: Appearance.rounding.normal
CustomRect { MaterialIcon {
radius: Appearance.rounding.normal id: icon
color: {
if (root.modelData.type === Toast.Success)
return DynamicColors.palette.m3success;
if (root.modelData.type === Toast.Warning)
return DynamicColors.palette.m3secondaryContainer;
if (root.modelData.type === Toast.Error)
return DynamicColors.palette.m3error;
return DynamicColors.palette.m3surfaceContainerHigh;
}
implicitWidth: implicitHeight anchors.centerIn: parent
implicitHeight: icon.implicitHeight + Appearance.padding.smaller * 2 color: {
if (root.modelData.type === Toast.Success)
return DynamicColors.palette.m3onSuccess;
if (root.modelData.type === Toast.Warning)
return DynamicColors.palette.m3onSecondaryContainer;
if (root.modelData.type === Toast.Error)
return DynamicColors.palette.m3onError;
return DynamicColors.palette.m3onSurfaceVariant;
}
font.pointSize: Math.round(Appearance.font.size.large * 1.2)
text: root.modelData.icon
}
}
MaterialIcon { ColumnLayout {
id: icon Layout.fillWidth: true
spacing: 0
anchors.centerIn: parent CustomText {
text: root.modelData.icon id: title
color: {
if (root.modelData.type === Toast.Success)
return DynamicColors.palette.m3onSuccess;
if (root.modelData.type === Toast.Warning)
return DynamicColors.palette.m3onSecondaryContainer;
if (root.modelData.type === Toast.Error)
return DynamicColors.palette.m3onError;
return DynamicColors.palette.m3onSurfaceVariant;
}
font.pointSize: Math.round(Appearance.font.size.large * 1.2)
}
}
ColumnLayout { Layout.fillWidth: true
Layout.fillWidth: true color: {
spacing: 0 if (root.modelData.type === Toast.Success)
return DynamicColors.palette.m3onSuccessContainer;
if (root.modelData.type === Toast.Warning)
return DynamicColors.palette.m3onSecondary;
if (root.modelData.type === Toast.Error)
return DynamicColors.palette.m3onErrorContainer;
return DynamicColors.palette.m3onSurface;
}
elide: Text.ElideRight
font.pointSize: Appearance.font.size.normal
text: root.modelData.title
}
CustomText { CustomText {
id: title Layout.fillWidth: true
color: {
Layout.fillWidth: true if (root.modelData.type === Toast.Success)
text: root.modelData.title return DynamicColors.palette.m3onSuccessContainer;
color: { if (root.modelData.type === Toast.Warning)
if (root.modelData.type === Toast.Success) return DynamicColors.palette.m3onSecondary;
return DynamicColors.palette.m3onSuccessContainer; if (root.modelData.type === Toast.Error)
if (root.modelData.type === Toast.Warning) return DynamicColors.palette.m3onErrorContainer;
return DynamicColors.palette.m3onSecondary; return DynamicColors.palette.m3onSurface;
if (root.modelData.type === Toast.Error) }
return DynamicColors.palette.m3onErrorContainer; elide: Text.ElideRight
return DynamicColors.palette.m3onSurface; opacity: 0.8
} text: root.modelData.message
font.pointSize: Appearance.font.size.normal textFormat: Text.StyledText
elide: Text.ElideRight }
} }
}
CustomText {
Layout.fillWidth: true
textFormat: Text.StyledText
text: root.modelData.message
color: {
if (root.modelData.type === Toast.Success)
return DynamicColors.palette.m3onSuccessContainer;
if (root.modelData.type === Toast.Warning)
return DynamicColors.palette.m3onSecondary;
if (root.modelData.type === Toast.Error)
return DynamicColors.palette.m3onErrorContainer;
return DynamicColors.palette.m3onSurface;
}
opacity: 0.8
elide: Text.ElideRight
}
}
}
Behavior on border.color {
CAnim {}
}
} }
+114 -116
View File
@@ -1,144 +1,142 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import qs.Components
import qs.Config
import qs.Modules
import ZShell import ZShell
import Quickshell import Quickshell
import QtQuick import QtQuick
import qs.Components
import qs.Config
Item { Item {
id: root id: root
readonly property int spacing: Appearance.spacing.small property bool flag
property bool flag readonly property int spacing: Appearance.spacing.small
implicitWidth: Config.utilities.sizes.toastWidth - Appearance.padding.normal * 2 implicitHeight: {
implicitHeight: { let h = -spacing;
let h = -spacing; for (let i = 0; i < repeater.count; i++) {
for (let i = 0; i < repeater.count; i++) { const item = repeater.itemAt(i) as ToastWrapper;
const item = repeater.itemAt(i) as ToastWrapper; if (!item.modelData.closed && !item.previewHidden)
if (!item.modelData.closed && !item.previewHidden) h += item.implicitHeight + spacing;
h += item.implicitHeight + spacing; }
} return h;
return h; }
} implicitWidth: Config.utilities.sizes.toastWidth - Appearance.padding.normal * 2
Repeater { Repeater {
id: repeater id: repeater
model: ScriptModel { model: ScriptModel {
values: { values: {
const toasts = []; const toasts = [];
let count = 0; let count = 0;
for (const toast of Toaster.toasts) { for (const toast of Toaster.toasts) {
toasts.push(toast); toasts.push(toast);
if (!toast.closed) { if (!toast.closed) {
count++; count++;
if (count > Config.utilities.maxToasts) if (count > Config.utilities.maxToasts)
break; break;
} }
} }
return toasts; return toasts;
} }
onValuesChanged: root.flagChanged()
}
ToastWrapper {} onValuesChanged: root.flagChanged()
} }
component ToastWrapper: MouseArea { ToastWrapper {
id: toast }
}
required property int index component ToastWrapper: MouseArea {
required property Toast modelData id: toast
readonly property bool previewHidden: { required property int index
let extraHidden = 0; required property Toast modelData
for (let i = 0; i < index; i++) readonly property bool previewHidden: {
if (Toaster.toasts[i].closed) let extraHidden = 0;
extraHidden++; for (let i = 0; i < index; i++)
return index >= Config.utilities.maxToasts + extraHidden; if (Toaster.toasts[i].closed)
} extraHidden++;
return index >= Config.utilities.maxToasts + extraHidden;
}
onPreviewHiddenChanged: { acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
if (initAnim.running && previewHidden) anchors.bottom: parent.bottom
initAnim.stop(); anchors.bottomMargin: {
} root.flag; // Force update
let y = 0;
for (let i = 0; i < index; i++) {
const item = repeater.itemAt(i) as ToastWrapper;
if (item && !item.modelData.closed && !item.previewHidden)
y += item.implicitHeight + root.spacing;
}
return y;
}
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: toastInner.implicitHeight
opacity: modelData.closed || previewHidden ? 0 : 1
scale: modelData.closed || previewHidden ? 0.7 : 1
opacity: modelData.closed || previewHidden ? 0 : 1 Behavior on anchors.bottomMargin {
scale: modelData.closed || previewHidden ? 0.7 : 1 Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
anchors.bottomMargin: { Component.onCompleted: modelData.lock(this)
root.flag; // Force update onClicked: modelData.close()
let y = 0; onPreviewHiddenChanged: {
for (let i = 0; i < index; i++) { if (initAnim.running && previewHidden)
const item = repeater.itemAt(i) as ToastWrapper; initAnim.stop();
if (item && !item.modelData.closed && !item.previewHidden) }
y += item.implicitHeight + root.spacing;
}
return y;
}
anchors.left: parent.left Anim {
anchors.right: parent.right id: initAnim
anchors.bottom: parent.bottom
implicitHeight: toastInner.implicitHeight
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton duration: Appearance.anim.durations.expressiveDefaultSpatial
onClicked: modelData.close() easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
from: 0
properties: "opacity,scale"
target: toast
to: 1
Component.onCompleted: modelData.lock(this) Component.onCompleted: running = !toast.previewHidden
}
Anim { ParallelAnimation {
id: initAnim running: toast.modelData.closed
Component.onCompleted: running = !toast.previewHidden onFinished: toast.modelData.unlock(toast)
onStarted: toast.anchors.bottomMargin = toast.anchors.bottomMargin
target: toast Anim {
properties: "opacity,scale" property: "opacity"
from: 0 target: toast
to: 1 to: 0
duration: Appearance.anim.durations.expressiveDefaultSpatial }
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
ParallelAnimation { Anim {
running: toast.modelData.closed property: "scale"
onStarted: toast.anchors.bottomMargin = toast.anchors.bottomMargin target: toast
onFinished: toast.modelData.unlock(toast) to: 0.7
}
}
Anim { ToastItem {
target: toast id: toastInner
property: "opacity"
to: 0
}
Anim {
target: toast
property: "scale"
to: 0.7
}
}
ToastItem { modelData: toast.modelData
id: toastInner }
}
modelData: toast.modelData
}
Behavior on opacity {
Anim {}
}
Behavior on scale {
Anim {}
}
Behavior on anchors.bottomMargin {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
}
} }
+8 -7
View File
@@ -1,12 +1,13 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property Accents accents: Accents {} property Accents accents: Accents {
}
component Accents: JsonObject { component Accents: JsonObject {
property string primary: "#4080ff" property string primary: "#4080ff"
property string primaryAlt: "#60a0ff" property string primaryAlt: "#60a0ff"
property string warning: "#ff6b6b" property string warning: "#ff6b6b"
property string warningAlt: "#ff8787" property string warningAlt: "#ff8787"
} }
} }
+8 -8
View File
@@ -3,12 +3,12 @@ pragma Singleton
import Quickshell import Quickshell
Singleton { Singleton {
// Literally just here to shorten accessing stuff :woe: readonly property AppearanceConf.Anim anim: Config.appearance.anim
// Also kinda so I can keep accessing it with `Appearance.xxx` instead of `Conf.appearance.xxx` readonly property AppearanceConf.FontStuff font: Config.appearance.font
readonly property AppearanceConf.Rounding rounding: Config.appearance.rounding readonly property AppearanceConf.Padding padding: Config.appearance.padding
readonly property AppearanceConf.Spacing spacing: Config.appearance.spacing // Literally just here to shorten accessing stuff :woe:
readonly property AppearanceConf.Padding padding: Config.appearance.padding // Also kinda so I can keep accessing it with `Appearance.xxx` instead of `Conf.appearance.xxx`
readonly property AppearanceConf.FontStuff font: Config.appearance.font readonly property AppearanceConf.Rounding rounding: Config.appearance.rounding
readonly property AppearanceConf.Anim anim: Config.appearance.anim readonly property AppearanceConf.Spacing spacing: Config.appearance.spacing
readonly property AppearanceConf.Transparency transparency: Config.appearance.transparency readonly property AppearanceConf.Transparency transparency: Config.appearance.transparency
} }
+92 -89
View File
@@ -1,94 +1,97 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property Rounding rounding: Rounding {} property Anim anim: Anim {
property Spacing spacing: Spacing {} }
property Padding padding: Padding {} property FontStuff font: FontStuff {
property FontStuff font: FontStuff {} }
property Anim anim: Anim {} property Padding padding: Padding {
property Transparency transparency: Transparency {} }
property Rounding rounding: Rounding {
}
property Spacing spacing: Spacing {
}
property Transparency transparency: Transparency {
}
component Rounding: JsonObject { component Anim: JsonObject {
property real scale: 1 property AnimCurves curves: AnimCurves {
property int small: 12 * scale }
property int normal: 17 * scale property AnimDurations durations: AnimDurations {
property int large: 25 * scale }
property int full: 1000 * scale property real mediaGifSpeedAdjustment: 300
} property real sessionGifSpeed: 0.7
}
component Spacing: JsonObject { component AnimCurves: JsonObject {
property real scale: 1 property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1]
property int small: 7 * scale property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
property int smaller: 10 * scale property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
property int normal: 12 * scale property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1]
property int larger: 15 * scale property list<real> expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1]
property int large: 20 * scale property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 1, 1]
} property list<real> standard: [0.2, 0, 0, 1, 1, 1]
property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1]
component Padding: JsonObject { property list<real> standardDecel: [0, 0, 0, 1, 1, 1]
property real scale: 1 }
property int small: 5 * scale component AnimDurations: JsonObject {
property int smaller: 7 * scale property int expressiveDefaultSpatial: 500 * scale
property int normal: 10 * scale property int expressiveEffects: 200 * scale
property int larger: 12 * scale property int expressiveFastSpatial: 350 * scale
property int large: 15 * scale property int extraLarge: 1000 * scale
} property int large: 600 * scale
property int normal: 400 * scale
component FontFamily: JsonObject { property real scale: 1
property string sans: "Segoe UI Variable Text" property int small: 200 * scale
property string mono: "CaskaydiaCove NF" }
property string material: "Material Symbols Rounded" component FontFamily: JsonObject {
property string clock: "Rubik" property string clock: "Rubik"
} property string material: "Material Symbols Rounded"
property string mono: "CaskaydiaCove NF"
component FontSize: JsonObject { property string sans: "Segoe UI Variable Text"
property real scale: 1 }
property int small: 11 * scale component FontSize: JsonObject {
property int smaller: 12 * scale property int extraLarge: 28 * scale
property int normal: 13 * scale property int large: 18 * scale
property int larger: 15 * scale property int larger: 15 * scale
property int large: 18 * scale property int normal: 13 * scale
property int extraLarge: 28 * scale property real scale: 1
} property int small: 11 * scale
property int smaller: 12 * scale
component FontStuff: JsonObject { }
property FontFamily family: FontFamily {} component FontStuff: JsonObject {
property FontSize size: FontSize {} property FontFamily family: FontFamily {
} }
property FontSize size: FontSize {
component AnimCurves: JsonObject { }
property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1] }
property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1] component Padding: JsonObject {
property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1] property int large: 15 * scale
property list<real> standard: [0.2, 0, 0, 1, 1, 1] property int larger: 12 * scale
property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1] property int normal: 10 * scale
property list<real> standardDecel: [0, 0, 0, 1, 1, 1] property real scale: 1
property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 1, 1] property int small: 5 * scale
property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1] property int smaller: 7 * scale
property list<real> expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1] property int smallest: 2 * scale
} }
component Rounding: JsonObject {
component AnimDurations: JsonObject { property int full: 1000 * scale
property real scale: 1 property int large: 25 * scale
property int small: 200 * scale property int normal: 17 * scale
property int normal: 400 * scale property real scale: 1
property int large: 600 * scale property int small: 12 * scale
property int extraLarge: 1000 * scale property int smallest: 8 * scale
property int expressiveFastSpatial: 350 * scale }
property int expressiveDefaultSpatial: 500 * scale component Spacing: JsonObject {
property int expressiveEffects: 200 * scale property int large: 20 * scale
} property int larger: 15 * scale
property int normal: 12 * scale
component Anim: JsonObject { property real scale: 1
property real mediaGifSpeedAdjustment: 300 property int small: 7 * scale
property real sessionGifSpeed: 0.7 property int smaller: 10 * scale
property AnimCurves curves: AnimCurves {} }
property AnimDurations durations: AnimDurations {} component Transparency: JsonObject {
} property real base: 0.85
property bool enabled: false
component Transparency: JsonObject { property real layers: 0.4
property bool enabled: false }
property real base: 0.85
property real layers: 0.4
}
} }
+2 -2
View File
@@ -2,6 +2,6 @@ import Quickshell.Io
import qs.Config import qs.Config
JsonObject { JsonObject {
property int wallFadeDuration: MaterialEasing.standardTime property bool enabled: true
property bool enabled: true property int wallFadeDuration: MaterialEasing.standardTime
} }
+16 -6
View File
@@ -2,9 +2,7 @@ import Quickshell.Io
JsonObject { JsonObject {
property bool autoHide: false property bool autoHide: false
property int rounding: 8 property int border: 8
property Popouts popouts: Popouts {}
property list<var> entries: [ property list<var> entries: [
{ {
id: "workspaces", id: "workspaces",
@@ -14,6 +12,10 @@ JsonObject {
id: "audio", id: "audio",
enabled: true enabled: true
}, },
{
id: "media",
enabled: true
},
{ {
id: "resources", id: "resources",
enabled: true enabled: true
@@ -38,6 +40,10 @@ JsonObject {
id: "spacer", id: "spacer",
enabled: true enabled: true
}, },
{
id: "hyprsunset",
enabled: true
},
{ {
id: "tray", id: "tray",
enabled: true enabled: true
@@ -59,14 +65,18 @@ JsonObject {
enabled: true enabled: true
}, },
] ]
property int height: 34
property Popouts popouts: Popouts {
}
property int rounding: 8
component Popouts: JsonObject { component Popouts: JsonObject {
property bool tray: true
property bool audio: true
property bool activeWindow: true property bool activeWindow: true
property bool resources: true property bool audio: true
property bool clock: true property bool clock: true
property bool network: true property bool network: true
property bool resources: true
property bool tray: true
property bool upower: true property bool upower: true
} }
} }
+349 -297
View File
@@ -4,27 +4,28 @@ import Quickshell
import Quickshell.Io import Quickshell.Io
import ZShell import ZShell
import QtQuick import QtQuick
import qs.Modules as Modules
import qs.Helpers import qs.Helpers
import qs.Paths import qs.Paths
Singleton { Singleton {
id: root id: root
property alias background: adapter.background property alias appearance: adapter.appearance
property alias barConfig: adapter.barConfig property alias background: adapter.background
property alias lock: adapter.lock property alias barConfig: adapter.barConfig
property alias overview: adapter.overview property alias colors: adapter.colors
property alias services: adapter.services property alias dashboard: adapter.dashboard
property alias dock: adapter.dock
property alias general: adapter.general
property alias launcher: adapter.launcher
property alias lock: adapter.lock
property alias notifs: adapter.notifs property alias notifs: adapter.notifs
property alias osd: adapter.osd
property alias overview: adapter.overview
property bool recentlySaved: false
property alias services: adapter.services
property alias sidebar: adapter.sidebar property alias sidebar: adapter.sidebar
property alias utilities: adapter.utilities property alias utilities: adapter.utilities
property alias general: adapter.general
property alias dashboard: adapter.dashboard
property alias appearance: adapter.appearance
property alias osd: adapter.osd
property alias launcher: adapter.launcher
property alias colors: adapter.colors
function save(): void { function save(): void {
saveTimer.restart(); saveTimer.restart();
@@ -32,16 +33,302 @@ Singleton {
recentSaveCooldown.restart(); recentSaveCooldown.restart();
} }
property bool recentlySaved: false function saveNoToast(): void {
saveTimer.restart();
}
function serializeAppearance(): var {
return {
rounding: {
scale: appearance.rounding.scale
},
spacing: {
scale: appearance.spacing.scale
},
padding: {
scale: appearance.padding.scale
},
font: {
family: {
sans: appearance.font.family.sans,
mono: appearance.font.family.mono,
material: appearance.font.family.material,
clock: appearance.font.family.clock
},
size: {
scale: appearance.font.size.scale
}
},
anim: {
mediaGifSpeedAdjustment: appearance.anim.mediaGifSpeedAdjustment,
sessionGifSpeed: appearance.anim.sessionGifSpeed,
durations: {
scale: appearance.anim.durations.scale
}
},
transparency: {
enabled: appearance.transparency.enabled,
base: appearance.transparency.base,
layers: appearance.transparency.layers
}
};
}
function serializeBackground(): var {
return {
wallFadeDuration: background.wallFadeDuration,
enabled: background.enabled
};
}
function serializeBar(): var {
return {
autoHide: barConfig.autoHide,
rounding: barConfig.rounding,
border: barConfig.border,
height: barConfig.height,
popouts: {
tray: barConfig.popouts.tray,
audio: barConfig.popouts.audio,
activeWindow: barConfig.popouts.activeWindow,
resources: barConfig.popouts.resources,
clock: barConfig.popouts.clock,
network: barConfig.popouts.network,
upower: barConfig.popouts.upower
},
entries: barConfig.entries
};
}
function serializeColors(): var {
return {
schemeType: colors.schemeType
};
}
function serializeConfig(): var {
return {
barConfig: serializeBar(),
lock: serializeLock(),
general: serializeGeneral(),
services: serializeServices(),
notifs: serializeNotifs(),
sidebar: serializeSidebar(),
utilities: serializeUtilities(),
dashboard: serializeDashboard(),
appearance: serializeAppearance(),
osd: serializeOsd(),
background: serializeBackground(),
launcher: serializeLauncher(),
colors: serializeColors(),
dock: serializeDock()
};
}
function serializeDashboard(): var {
return {
enabled: dashboard.enabled,
mediaUpdateInterval: dashboard.mediaUpdateInterval,
resourceUpdateInterval: dashboard.resourceUpdateInterval,
dragThreshold: dashboard.dragThreshold,
performance: {
showBattery: dashboard.performance.showBattery,
showGpu: dashboard.performance.showGpu,
showCpu: dashboard.performance.showCpu,
showMemory: dashboard.performance.showMemory,
showStorage: dashboard.performance.showStorage,
showNetwork: dashboard.performance.showNetwork
},
sizes: {
tabIndicatorHeight: dashboard.sizes.tabIndicatorHeight,
tabIndicatorSpacing: dashboard.sizes.tabIndicatorSpacing,
infoWidth: dashboard.sizes.infoWidth,
infoIconSize: dashboard.sizes.infoIconSize,
dateTimeWidth: dashboard.sizes.dateTimeWidth,
mediaWidth: dashboard.sizes.mediaWidth,
mediaProgressSweep: dashboard.sizes.mediaProgressSweep,
mediaProgressThickness: dashboard.sizes.mediaProgressThickness,
resourceProgessThickness: dashboard.sizes.resourceProgessThickness,
weatherWidth: dashboard.sizes.weatherWidth,
mediaCoverArtSize: dashboard.sizes.mediaCoverArtSize,
mediaVisualiserSize: dashboard.sizes.mediaVisualiserSize,
resourceSize: dashboard.sizes.resourceSize
}
};
}
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 {
return {
logo: general.logo,
wallpaperPath: general.wallpaperPath,
desktopIcons: general.desktopIcons,
color: {
mode: general.color.mode,
smart: general.color.smart,
scheduleDark: general.color.scheduleDark,
scheduleHyprsunset: general.color.scheduleHyprsunset,
scheduleHyprsunsetStart: general.color.scheduleHyprsunsetStart,
hyprsunsetTemp: general.color.hyprsunsetTemp,
scheduleHyprsunsetEnd: general.color.scheduleHyprsunsetEnd,
schemeGeneration: general.color.schemeGeneration,
scheduleDarkStart: general.color.scheduleDarkStart,
scheduleDarkEnd: general.color.scheduleDarkEnd,
neovimColors: general.color.neovimColors
},
apps: {
terminal: general.apps.terminal,
audio: general.apps.audio,
playback: general.apps.playback,
explorer: general.apps.explorer
},
idle: {
timeouts: general.idle.timeouts
}
};
}
function serializeLauncher(): var {
return {
maxAppsShown: launcher.maxAppsShown,
maxWallpapers: launcher.maxWallpapers,
actionPrefix: launcher.actionPrefix,
specialPrefix: launcher.specialPrefix,
useFuzzy: {
apps: launcher.useFuzzy.apps,
actions: launcher.useFuzzy.actions,
schemes: launcher.useFuzzy.schemes,
variants: launcher.useFuzzy.variants,
wallpapers: launcher.useFuzzy.wallpapers
},
sizes: {
itemWidth: launcher.sizes.itemWidth,
itemHeight: launcher.sizes.itemHeight,
wallpaperWidth: launcher.sizes.wallpaperWidth,
wallpaperHeight: launcher.sizes.wallpaperHeight
},
actions: launcher.actions
};
}
function serializeLock(): var {
return {
recolorLogo: lock.recolorLogo,
enableFprint: lock.enableFprint,
maxFprintTries: lock.maxFprintTries,
blurAmount: lock.blurAmount,
sizes: {
heightMult: lock.sizes.heightMult,
ratio: lock.sizes.ratio,
centerWidth: lock.sizes.centerWidth
}
};
}
function serializeNotifs(): var {
return {
expire: notifs.expire,
defaultExpireTimeout: notifs.defaultExpireTimeout,
appNotifCooldown: notifs.appNotifCooldown,
clearThreshold: notifs.clearThreshold,
expandThreshold: notifs.expandThreshold,
actionOnClick: notifs.actionOnClick,
groupPreviewNum: notifs.groupPreviewNum,
sizes: {
width: notifs.sizes.width,
image: notifs.sizes.image,
badge: notifs.sizes.badge
}
};
}
function serializeOsd(): var {
return {
enabled: osd.enabled,
hideDelay: osd.hideDelay,
enableBrightness: osd.enableBrightness,
enableMicrophone: osd.enableMicrophone,
allMonBrightness: osd.allMonBrightness,
sizes: {
sliderWidth: osd.sizes.sliderWidth,
sliderHeight: osd.sizes.sliderHeight
}
};
}
function serializeServices(): var {
return {
weatherLocation: services.weatherLocation,
useFahrenheit: services.useFahrenheit,
ddcutilService: services.ddcutilService,
useTwelveHourClock: services.useTwelveHourClock,
gpuType: services.gpuType,
audioIncrement: services.audioIncrement,
brightnessIncrement: services.brightnessIncrement,
maxVolume: services.maxVolume,
defaultPlayer: services.defaultPlayer,
playerAliases: services.playerAliases,
visualizerBars: services.visualizerBars
};
}
function serializeSidebar(): var {
return {
enabled: sidebar.enabled,
sizes: {
width: sidebar.sizes.width
}
};
}
function serializeUtilities(): var {
return {
enabled: utilities.enabled,
maxToasts: utilities.maxToasts,
sizes: {
width: utilities.sizes.width,
toastWidth: utilities.sizes.toastWidth
},
toasts: {
configLoaded: utilities.toasts.configLoaded,
chargingChanged: utilities.toasts.chargingChanged,
gameModeChanged: utilities.toasts.gameModeChanged,
dndChanged: utilities.toasts.dndChanged,
audioOutputChanged: utilities.toasts.audioOutputChanged,
audioInputChanged: utilities.toasts.audioInputChanged,
capsLockChanged: utilities.toasts.capsLockChanged,
numLockChanged: utilities.toasts.numLockChanged,
kbLayoutChanged: utilities.toasts.kbLayoutChanged,
vpnChanged: utilities.toasts.vpnChanged,
nowPlaying: utilities.toasts.nowPlaying
},
vpn: {
enabled: utilities.vpn.enabled,
provider: utilities.vpn.provider
}
};
}
ElapsedTimer { ElapsedTimer {
id: timer id: timer
} }
Timer { Timer {
id: saveTimer id: saveTimer
interval: 500 interval: 500
onTriggered: { onTriggered: {
timer.restart(); timer.restart();
try { try {
@@ -65,316 +352,81 @@ Singleton {
id: recentSaveCooldown id: recentSaveCooldown
interval: 2000 interval: 2000
onTriggered: { onTriggered: {
root.recentlySaved = false; root.recentlySaved = false;
} }
} }
function serializeConfig(): var { FileView {
return { id: fileView
barConfig: serializeBar(),
lock: serializeLock(),
general: serializeGeneral(),
services: serializeServices(),
notifs: serializeNotifs(),
sidebar: serializeSidebar(),
utilities: serializeUtilities(),
dashboard: serializeDashboard(),
appearance: serializeAppearance(),
osd: serializeOsd(),
background: serializeBackground(),
launcher: serializeLauncher(),
colors: serializeColors()
}
}
function serializeBar(): var { path: `${Paths.config}/config.json`
return { watchChanges: true
autoHide: barConfig.autoHide,
rounding: barConfig.rounding,
popouts: {
tray: barConfig.popouts.tray,
audio: barConfig.popouts.audio,
activeWindow: barConfig.popouts.activeWindow,
resources: barConfig.popouts.resources,
clock: barConfig.popouts.clock,
network: barConfig.popouts.network,
upower: barConfig.popouts.upower
},
entries: barConfig.entries
}
}
function serializeLock(): var { onFileChanged: {
return { if (!root.recentlySaved) {
recolorLogo: lock.recolorLogo,
enableFprint: lock.enableFprint,
maxFprintTries: lock.maxFprintTries,
blurAmount: lock.blurAmount,
sizes: {
heightMult: lock.sizes.heightMult,
ratio: lock.sizes.ratio,
centerWidth: lock.sizes.centerWidth
}
};
}
function serializeGeneral(): var {
return {
logo: general.logo,
wallpaperPath: general.wallpaperPath,
color: {
wallust: general.color.wallust,
mode: general.color.mode,
schemeGeneration: general.color.schemeGeneration,
scheduleDarkStart: general.color.scheduleDarkStart,
scheduleDarkEnd: general.color.scheduleDarkEnd,
neovimColors: general.color.neovimColors
},
apps: {
terminal: general.apps.terminal,
audio: general.apps.audio,
playback: general.apps.playback,
explorer: general.apps.explorer,
},
idle: {
timouts: general.idle.timeouts
}
}
}
function serializeServices(): var {
return {
weatherLocation: services.weatherLocation,
useFahrenheit: services.useFahrenheit,
useTwelveHourClock: services.useTwelveHourClock,
gpuType: services.gpuType,
audioIncrement: services.audioIncrement,
brightnessIncrement: services.brightnessIncrement,
maxVolume: services.maxVolume,
defaultPlayer: services.defaultPlayer,
playerAliases: services.playerAliases
};
}
function serializeNotifs(): var {
return {
expire: notifs.expire,
defaultExpireTimeout: notifs.defaultExpireTimeout,
clearThreshold: notifs.clearThreshold,
expandThreshold: notifs.expandThreshold,
actionOnClick: notifs.actionOnClick,
groupPreviewNum: notifs.groupPreviewNum,
sizes: {
width: notifs.sizes.width,
image: notifs.sizes.image,
badge: notifs.sizes.badge
}
};
}
function serializeSidebar(): var {
return {
enabled: sidebar.enabled,
sizes: {
width: sidebar.sizes.width
}
};
}
function serializeUtilities(): var {
return {
enabled: utilities.enabled,
maxToasts: utilities.maxToasts,
sizes: {
width: utilities.sizes.width,
toastWidth: utilities.sizes.toastWidth
},
toasts: {
configLoaded: utilities.toasts.configLoaded,
chargingChanged: utilities.toasts.chargingChanged,
gameModeChanged: utilities.toasts.gameModeChanged,
dndChanged: utilities.toasts.dndChanged,
audioOutputChanged: utilities.toasts.audioOutputChanged,
audioInputChanged: utilities.toasts.audioInputChanged,
capsLockChanged: utilities.toasts.capsLockChanged,
numLockChanged: utilities.toasts.numLockChanged,
kbLayoutChanged: utilities.toasts.kbLayoutChanged,
vpnChanged: utilities.toasts.vpnChanged,
nowPlaying: utilities.toasts.nowPlaying
},
vpn: {
enabled: utilities.vpn.enabled,
provider: utilities.vpn.provider
}
};
}
function serializeDashboard(): var {
return {
enabled: dashboard.enabled,
mediaUpdateInterval: dashboard.mediaUpdateInterval,
dragThreshold: dashboard.dragThreshold,
sizes: {
tabIndicatorHeight: dashboard.sizes.tabIndicatorHeight,
tabIndicatorSpacing: dashboard.sizes.tabIndicatorSpacing,
infoWidth: dashboard.sizes.infoWidth,
infoIconSize: dashboard.sizes.infoIconSize,
dateTimeWidth: dashboard.sizes.dateTimeWidth,
mediaWidth: dashboard.sizes.mediaWidth,
mediaProgressSweep: dashboard.sizes.mediaProgressSweep,
mediaProgressThickness: dashboard.sizes.mediaProgressThickness,
resourceProgessThickness: dashboard.sizes.resourceProgessThickness,
weatherWidth: dashboard.sizes.weatherWidth,
mediaCoverArtSize: dashboard.sizes.mediaCoverArtSize,
mediaVisualiserSize: dashboard.sizes.mediaVisualiserSize,
resourceSize: dashboard.sizes.resourceSize
}
};
}
function serializeOsd(): var {
return {
enabled: osd.enabled,
hideDelay: osd.hideDelay,
enableBrightness: osd.enableBrightness,
enableMicrophone: osd.enableMicrophone,
allMonBrightness: osd.allMonBrightness,
sizes: {
sliderWidth: osd.sizes.sliderWidth,
sliderHeight: osd.sizes.sliderHeight
}
};
}
function serializeLauncher(): var {
return {
maxAppsShown: launcher.maxAppsShown,
maxWallpapers: launcher.maxWallpapers,
actionPrefix: launcher.actionPrefix,
specialPrefix: launcher.specialPrefix,
useFuzzy: {
apps: launcher.useFuzzy.apps,
actions: launcher.useFuzzy.actions,
schemes: launcher.useFuzzy.schemes,
variants: launcher.useFuzzy.variants,
wallpapers: launcher.useFuzzy.wallpapers
},
sizes: {
itemWidth: launcher.sizes.itemWidth,
itemHeight: launcher.sizes.itemHeight,
wallpaperWidth: launcher.sizes.wallpaperWidth,
wallpaperHeight: launcher.sizes.wallpaperHeight
},
actions: launcher.actions
}
}
function serializeBackground(): var {
return {
wallFadeDuration: background.wallFadeDuration,
enabled: background.enabled
}
}
function serializeAppearance(): var {
return {
rounding: {
scale: appearance.rounding.scale
},
spacing: {
scale: appearance.spacing.scale
},
padding: {
scale: appearance.padding.scale
},
font: {
family: {
sans: appearance.font.family.sans,
mono: appearance.font.family.mono,
material: appearance.font.family.material,
clock: appearance.font.family.clock
},
size: {
scale: appearance.font.size.scale
}
},
anim: {
mediaGifSpeedAdjustment: 300,
sessionGifSpeed: 0.7,
durations: {
scale: appearance.anim.durations.scale
}
},
transparency: {
enabled: appearance.transparency.enabled,
base: appearance.transparency.base,
layers: appearance.transparency.layers
}
};
}
function serializeColors(): var {
return {
schemeType: colors.schemeType,
}
}
FileView {
id: fileView
path: `${Paths.config}/config.json`
watchChanges: true
onFileChanged: {
if ( !root.recentlySaved ) {
timer.restart(); timer.restart();
reload(); reload();
} else { } else {
reload(); reload();
} }
} }
onLoadFailed: err => {
if (err !== FileViewError.FileNotFound)
Toaster.toast(qsTr("Failed to read config"), FileViewError.toString(err), "settings_alert", Toast.Warning);
}
onLoaded: { onLoaded: {
ModeScheduler.checkStartup(); ModeScheduler.checkStartup();
Hyprsunset.checkStartup();
try { try {
JSON.parse(text()); JSON.parse(text());
const elapsed = timer.elapsedMs(); const elapsed = timer.elapsedMs();
if ( adapter.utilities.toasts.configLoaded && !root.recentlySaved ) { if (adapter.utilities.toasts.configLoaded && !root.recentlySaved) {
Toaster.toast(qsTr("Config loaded"), qsTr("Config loaded in %1ms").arg(elapsed), "rule_settings"); Toaster.toast(qsTr("Config loaded"), qsTr("Config loaded in %1ms").arg(elapsed), "rule_settings");
} else if ( adapter.utilities.toasts.configLoaded && root.recentlySaved ) { } else if (adapter.utilities.toasts.configLoaded && root.recentlySaved) {
Toaster.toast(qsTr("Config saved"), qsTr("Config reloaded in %1ms").arg(elapsed), "settings_alert"); Toaster.toast(qsTr("Config saved"), qsTr("Config reloaded in %1ms").arg(elapsed), "settings_alert");
} }
} catch (e) { } catch (e) {
Toaster.toast(qsTr("Failed to load config"), e.message, "settings_alert", Toast.Error); Toaster.toast(qsTr("Failed to load config"), e.message, "settings_alert", Toast.Error);
} }
} }
onLoadFailed: err => {
if ( err !== FileViewError.FileNotFound )
Toaster.toast(qsTr("Failed to read config"), FileViewError.toString(err), "settings_alert", Toast.Warning);
}
onSaveFailed: err => Toaster.toast(qsTr("Failed to save config"), FileViewError.toString(err), "settings_alert", Toast.Error) onSaveFailed: err => Toaster.toast(qsTr("Failed to save config"), FileViewError.toString(err), "settings_alert", Toast.Error)
JsonAdapter { JsonAdapter {
id: adapter id: adapter
property BackgroundConfig background: BackgroundConfig {}
property BarConfig barConfig: BarConfig {} property AppearanceConf appearance: AppearanceConf {
property LockConf lock: LockConf {} }
property Overview overview: Overview {} property BackgroundConfig background: BackgroundConfig {
property Services services: Services {} }
property NotifConfig notifs: NotifConfig {} property BarConfig barConfig: BarConfig {
property SidebarConfig sidebar: SidebarConfig {} }
property UtilConfig utilities: UtilConfig {} property Colors colors: Colors {
property General general: General {} }
property DashboardConfig dashboard: DashboardConfig {} property DashboardConfig dashboard: DashboardConfig {
property AppearanceConf appearance: AppearanceConf {} }
property Osd osd: Osd {} property DockConfig dock: DockConfig {
property Launcher launcher: Launcher {} }
property Colors colors: Colors {} property General general: General {
} }
} property Launcher launcher: Launcher {
}
property LockConf lock: LockConf {
}
property NotifConfig notifs: NotifConfig {
}
property Osd osd: Osd {
}
property Overview overview: Overview {
}
property Services services: Services {
}
property SidebarConfig sidebar: SidebarConfig {
}
property UtilConfig utilities: UtilConfig {
}
}
}
} }
+31 -19
View File
@@ -1,24 +1,36 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property bool enabled: true property int dragThreshold: 50
property int mediaUpdateInterval: 500 property bool enabled: true
property int dragThreshold: 50 property int mediaUpdateInterval: 500
property Sizes sizes: Sizes {} property Performance performance: Performance {
}
property int resourceUpdateInterval: 1000
property Sizes sizes: Sizes {
}
component Sizes: JsonObject { component Performance: JsonObject {
readonly property int tabIndicatorHeight: 3 property bool showBattery: true
readonly property int tabIndicatorSpacing: 5 property bool showCpu: true
readonly property int infoWidth: 200 property bool showGpu: true
readonly property int infoIconSize: 25 property bool showMemory: true
readonly property int dateTimeWidth: 110 property bool showNetwork: true
readonly property int mediaWidth: 200 property bool showStorage: true
readonly property int mediaProgressSweep: 180 }
readonly property int mediaProgressThickness: 8 component Sizes: JsonObject {
readonly property int resourceProgessThickness: 10 readonly property int dateTimeWidth: 110
readonly property int weatherWidth: 250 readonly property int infoIconSize: 25
readonly property int mediaCoverArtSize: 150 readonly property int infoWidth: 200
readonly property int mediaVisualiserSize: 80 readonly property int mediaCoverArtSize: 150
readonly property int resourceSize: 200 readonly property int mediaProgressSweep: 180
} readonly property int mediaProgressThickness: 8
readonly property int mediaVisualiserSize: 80
readonly property int mediaWidth: 200
readonly property int resourceProgessThickness: 10
readonly property int resourceSize: 200
readonly property int tabIndicatorHeight: 3
readonly property int tabIndicatorSpacing: 5
readonly property int weatherWidth: 250
}
} }
+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
}
+240 -232
View File
@@ -9,270 +9,278 @@ import qs.Helpers
import qs.Paths import qs.Paths
Singleton { Singleton {
id: root id: root
property bool showPreview readonly property M3Palette current: M3Palette {
property string scheme }
property string flavour property bool currentLight
readonly property bool light: showPreview ? previewLight : currentLight property string flavour
property bool currentLight readonly property bool light: showPreview ? previewLight : currentLight
property bool previewLight readonly property M3Palette palette: showPreview ? preview : current
readonly property M3Palette palette: showPreview ? preview : current readonly property M3Palette preview: M3Palette {
readonly property M3TPalette tPalette: M3TPalette {} }
readonly property M3Palette current: M3Palette {} property bool previewLight
readonly property M3Palette preview: M3Palette {} property string scheme
readonly property Transparency transparency: Transparency {} property bool showPreview
readonly property alias wallLuminance: analyser.luminance readonly property M3TPalette tPalette: M3TPalette {
}
readonly property Transparency transparency: Transparency {
}
readonly property alias wallLuminance: analyser.luminance
function getLuminance(c: color): real { function alterColor(c: color, a: real, layer: int): color {
if (c.r == 0 && c.g == 0 && c.b == 0) const luminance = getLuminance(c);
return 0;
return Math.sqrt(0.299 * (c.r ** 2) + 0.587 * (c.g ** 2) + 0.114 * (c.b ** 2));
}
function alterColor(c: color, a: real, layer: int): color { const offset = (!light || layer == 1 ? 1 : -layer / 2) * (light ? 0.2 : 0.3) * (1 - transparency.base) * (1 + wallLuminance * (light ? (layer == 1 ? 3 : 1) : 2.5));
const luminance = getLuminance(c); const scale = (luminance + offset) / luminance;
const r = Math.max(0, Math.min(1, c.r * scale));
const g = Math.max(0, Math.min(1, c.g * scale));
const b = Math.max(0, Math.min(1, c.b * scale));
const offset = (!light || layer == 1 ? 1 : -layer / 2) * (light ? 0.2 : 0.3) * (1 - transparency.base) * (1 + wallLuminance * (light ? (layer == 1 ? 3 : 1) : 2.5)); return Qt.rgba(r, g, b, a);
const scale = (luminance + offset) / luminance; }
const r = Math.max(0, Math.min(1, c.r * scale));
const g = Math.max(0, Math.min(1, c.g * scale));
const b = Math.max(0, Math.min(1, c.b * scale));
return Qt.rgba(r, g, b, a); function getLuminance(c: color): real {
} if (c.r == 0 && c.g == 0 && c.b == 0)
return 0;
return Math.sqrt(0.299 * (c.r ** 2) + 0.587 * (c.g ** 2) + 0.114 * (c.b ** 2));
}
function layer(c: color, layer: var): color { function layer(c: color, layer: var): color {
if (!transparency.enabled) if (!transparency.enabled)
return c; return c;
return layer === 0 ? Qt.alpha(c, transparency.base) : alterColor(c, transparency.layers, layer ?? 1); return layer === 0 ? Qt.alpha(c, transparency.base) : alterColor(c, transparency.layers, layer ?? 1);
} }
function on(c: color): color { function load(data: string, isPreview: bool): void {
if (c.hslLightness < 0.5) const colors = isPreview ? preview : current;
return Qt.hsla(c.hslHue, c.hslSaturation, 0.9, 1); const scheme = JSON.parse(data);
return Qt.hsla(c.hslHue, c.hslSaturation, 0.1, 1);
}
function load(data: string, isPreview: bool): void { if (!isPreview) {
const colors = isPreview ? preview : current; root.scheme = scheme.name;
const scheme = JSON.parse(data); flavour = scheme.flavor;
currentLight = scheme.mode === "light";
} else {
previewLight = scheme.mode === "light";
}
if (!isPreview) { for (const [name, color] of Object.entries(scheme.colors)) {
root.scheme = scheme.name; const propName = name.startsWith("term") ? name : `m3${name}`;
flavour = scheme.flavor; if (colors.hasOwnProperty(propName))
currentLight = scheme.mode === "light"; colors[propName] = `${color}`;
} else { }
previewLight = scheme.mode === "light"; }
}
for (const [name, color] of Object.entries(scheme.colors)) { function on(c: color): color {
const propName = name.startsWith("term") ? name : `m3${name}`; if (c.hslLightness < 0.5)
if (colors.hasOwnProperty(propName)) return Qt.hsla(c.hslHue, c.hslSaturation, 0.9, 1);
colors[propName] = `${color}`; return Qt.hsla(c.hslHue, c.hslSaturation, 0.1, 1);
} }
}
FileView { function setMode(mode: string): void {
path: `${Paths.state}/scheme.json` Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", mode]);
watchChanges: true Config.general.color.mode = mode;
onFileChanged: reload() Config.save();
onLoaded: root.load(text(), false) }
}
ImageAnalyser { FileView {
id: analyser path: `${Paths.state}/scheme.json`
watchChanges: true
source: WallpaperPath.currentWallpaperPath onFileChanged: reload()
} onLoaded: root.load(text(), false)
}
component Transparency: QtObject { ImageAnalyser {
readonly property bool enabled: Appearance.transparency.enabled id: analyser
readonly property real base: Appearance.transparency.base - (root.light ? 0.1 : 0)
readonly property real layers: Appearance.transparency.layers
}
component M3TPalette: QtObject { source: WallpaperPath.currentWallpaperPath
readonly property color m3primary_paletteKeyColor: root.layer(root.palette.m3primary_paletteKeyColor) }
readonly property color m3secondary_paletteKeyColor: root.layer(root.palette.m3secondary_paletteKeyColor)
readonly property color m3tertiary_paletteKeyColor: root.layer(root.palette.m3tertiary_paletteKeyColor)
readonly property color m3neutral_paletteKeyColor: root.layer(root.palette.m3neutral_paletteKeyColor)
readonly property color m3neutral_variant_paletteKeyColor: root.layer(root.palette.m3neutral_variant_paletteKeyColor)
readonly property color m3background: root.layer(root.palette.m3background, 0)
readonly property color m3onBackground: root.layer(root.palette.m3onBackground)
readonly property color m3surface: root.layer(root.palette.m3surface, 0)
readonly property color m3surfaceDim: root.layer(root.palette.m3surfaceDim, 0)
readonly property color m3surfaceBright: root.layer(root.palette.m3surfaceBright, 0)
readonly property color m3surfaceContainerLowest: root.layer(root.palette.m3surfaceContainerLowest)
readonly property color m3surfaceContainerLow: root.layer(root.palette.m3surfaceContainerLow)
readonly property color m3surfaceContainer: root.layer(root.palette.m3surfaceContainer)
readonly property color m3surfaceContainerHigh: root.layer(root.palette.m3surfaceContainerHigh)
readonly property color m3surfaceContainerHighest: root.layer(root.palette.m3surfaceContainerHighest)
readonly property color m3onSurface: root.layer(root.palette.m3onSurface)
readonly property color m3surfaceVariant: root.layer(root.palette.m3surfaceVariant, 0)
readonly property color m3onSurfaceVariant: root.layer(root.palette.m3onSurfaceVariant)
readonly property color m3inverseSurface: root.layer(root.palette.m3inverseSurface, 0)
readonly property color m3inverseOnSurface: root.layer(root.palette.m3inverseOnSurface)
readonly property color m3outline: root.layer(root.palette.m3outline)
readonly property color m3outlineVariant: root.layer(root.palette.m3outlineVariant)
readonly property color m3shadow: root.layer(root.palette.m3shadow)
readonly property color m3scrim: root.layer(root.palette.m3scrim)
readonly property color m3surfaceTint: root.layer(root.palette.m3surfaceTint)
readonly property color m3primary: root.layer(root.palette.m3primary)
readonly property color m3onPrimary: root.layer(root.palette.m3onPrimary)
readonly property color m3primaryContainer: root.layer(root.palette.m3primaryContainer)
readonly property color m3onPrimaryContainer: root.layer(root.palette.m3onPrimaryContainer)
readonly property color m3inversePrimary: root.layer(root.palette.m3inversePrimary)
readonly property color m3secondary: root.layer(root.palette.m3secondary)
readonly property color m3onSecondary: root.layer(root.palette.m3onSecondary)
readonly property color m3secondaryContainer: root.layer(root.palette.m3secondaryContainer)
readonly property color m3onSecondaryContainer: root.layer(root.palette.m3onSecondaryContainer)
readonly property color m3tertiary: root.layer(root.palette.m3tertiary)
readonly property color m3onTertiary: root.layer(root.palette.m3onTertiary)
readonly property color m3tertiaryContainer: root.layer(root.palette.m3tertiaryContainer)
readonly property color m3onTertiaryContainer: root.layer(root.palette.m3onTertiaryContainer)
readonly property color m3error: root.layer(root.palette.m3error)
readonly property color m3onError: root.layer(root.palette.m3onError)
readonly property color m3errorContainer: root.layer(root.palette.m3errorContainer)
readonly property color m3onErrorContainer: root.layer(root.palette.m3onErrorContainer)
readonly property color m3success: root.layer(root.palette.m3success)
readonly property color m3onSuccess: root.layer(root.palette.m3onSuccess)
readonly property color m3successContainer: root.layer(root.palette.m3successContainer)
readonly property color m3onSuccessContainer: root.layer(root.palette.m3onSuccessContainer)
readonly property color m3primaryFixed: root.layer(root.palette.m3primaryFixed)
readonly property color m3primaryFixedDim: root.layer(root.palette.m3primaryFixedDim)
readonly property color m3onPrimaryFixed: root.layer(root.palette.m3onPrimaryFixed)
readonly property color m3onPrimaryFixedVariant: root.layer(root.palette.m3onPrimaryFixedVariant)
readonly property color m3secondaryFixed: root.layer(root.palette.m3secondaryFixed)
readonly property color m3secondaryFixedDim: root.layer(root.palette.m3secondaryFixedDim)
readonly property color m3onSecondaryFixed: root.layer(root.palette.m3onSecondaryFixed)
readonly property color m3onSecondaryFixedVariant: root.layer(root.palette.m3onSecondaryFixedVariant)
readonly property color m3tertiaryFixed: root.layer(root.palette.m3tertiaryFixed)
readonly property color m3tertiaryFixedDim: root.layer(root.palette.m3tertiaryFixedDim)
readonly property color m3onTertiaryFixed: root.layer(root.palette.m3onTertiaryFixed)
readonly property color m3onTertiaryFixedVariant: root.layer(root.palette.m3onTertiaryFixedVariant)
}
component M3Palette: QtObject {
property color m3primary_paletteKeyColor: "#a8627b"
property color m3secondary_paletteKeyColor: "#8e6f78"
property color m3tertiary_paletteKeyColor: "#986e4c"
property color m3neutral_paletteKeyColor: "#807477"
property color m3neutral_variant_paletteKeyColor: "#837377"
property color m3background: "#191114"
property color m3onBackground: "#efdfe2"
property color m3surface: "#191114"
property color m3surfaceDim: "#191114"
property color m3surfaceBright: "#403739"
property color m3surfaceContainerLowest: "#130c0e"
property color m3surfaceContainerLow: "#22191c"
property color m3surfaceContainer: "#261d20"
property color m3surfaceContainerHigh: "#31282a"
property color m3surfaceContainerHighest: "#3c3235"
property color m3onSurface: "#efdfe2"
property color m3surfaceVariant: "#514347"
property color m3onSurfaceVariant: "#d5c2c6"
property color m3inverseSurface: "#efdfe2"
property color m3inverseOnSurface: "#372e30"
property color m3outline: "#9e8c91"
property color m3outlineVariant: "#514347"
property color m3shadow: "#000000"
property color m3scrim: "#000000"
property color m3surfaceTint: "#ffb0ca"
property color m3primary: "#ffb0ca"
property color m3onPrimary: "#541d34"
property color m3primaryContainer: "#6f334a"
property color m3onPrimaryContainer: "#ffd9e3"
property color m3inversePrimary: "#8b4a62"
property color m3secondary: "#e2bdc7"
property color m3onSecondary: "#422932"
property color m3secondaryContainer: "#5a3f48"
property color m3onSecondaryContainer: "#ffd9e3"
property color m3tertiary: "#f0bc95"
property color m3onTertiary: "#48290c"
property color m3tertiaryContainer: "#b58763"
property color m3onTertiaryContainer: "#000000"
property color m3error: "#ffb4ab"
property color m3onError: "#690005"
property color m3errorContainer: "#93000a"
property color m3onErrorContainer: "#ffdad6"
property color m3success: "#B5CCBA"
property color m3onSuccess: "#213528"
property color m3successContainer: "#374B3E"
property color m3onSuccessContainer: "#D1E9D6"
property color m3primaryFixed: "#ffd9e3"
property color m3primaryFixedDim: "#ffb0ca"
property color m3onPrimaryFixed: "#39071f"
property color m3onPrimaryFixedVariant: "#6f334a"
property color m3secondaryFixed: "#ffd9e3"
property color m3secondaryFixedDim: "#e2bdc7"
property color m3onSecondaryFixed: "#2b151d"
property color m3onSecondaryFixedVariant: "#5a3f48"
property color m3tertiaryFixed: "#ffdcc3"
property color m3tertiaryFixedDim: "#f0bc95"
property color m3onTertiaryFixed: "#2f1500"
property color m3onTertiaryFixedVariant: "#623f21"
}
component M3MaccchiatoPalette: QtObject { component M3MaccchiatoPalette: QtObject {
property color m3primary_paletteKeyColor: "#6a73ac" property color m3background: "#131317"
property color m3secondary_paletteKeyColor: "#72758e" property color m3error: "#ffb4ab"
property color m3tertiary_paletteKeyColor: "#9b6592" property color m3errorContainer: "#93000a"
property color m3inverseOnSurface: "#303034"
property color m3inversePrimary: "#525b92"
property color m3inverseSurface: "#e4e1e7"
property color m3neutral_paletteKeyColor: "#77767b" property color m3neutral_paletteKeyColor: "#77767b"
property color m3neutral_variant_paletteKeyColor: "#767680" property color m3neutral_variant_paletteKeyColor: "#767680"
property color m3background: "#131317"
property color m3onBackground: "#e4e1e7" property color m3onBackground: "#e4e1e7"
property color m3onError: "#690005"
property color m3onErrorContainer: "#ffdad6"
property color m3onPrimary: "#232c60"
property color m3onPrimaryContainer: "#ffffff"
property color m3onPrimaryFixed: "#0b154b"
property color m3onPrimaryFixedVariant: "#3a4378"
property color m3onSecondary: "#2c2f44"
property color m3onSecondaryContainer: "#b1b3ce"
property color m3onSecondaryFixed: "#171a2e"
property color m3onSecondaryFixedVariant: "#42455c"
property color m3onSuccess: "#213528"
property color m3onSuccessContainer: "#D1E9D6"
property color m3onSurface: "#e4e1e7"
property color m3onSurfaceVariant: "#c6c5d1"
property color m3onTertiary: "#4c1f48"
property color m3onTertiaryContainer: "#000000"
property color m3onTertiaryFixed: "#340831"
property color m3onTertiaryFixedVariant: "#66365f"
property color m3outline: "#90909a"
property color m3outlineVariant: "#46464f"
property color m3primary: "#bac3ff"
property color m3primaryContainer: "#6a73ac"
property color m3primaryFixed: "#dee0ff"
property color m3primaryFixedDim: "#bac3ff"
property color m3primary_paletteKeyColor: "#6a73ac"
property color m3scrim: "#000000"
property color m3secondary: "#c3c5e0"
property color m3secondaryContainer: "#42455c"
property color m3secondaryFixed: "#dfe1fd"
property color m3secondaryFixedDim: "#c3c5e0"
property color m3secondary_paletteKeyColor: "#72758e"
property color m3shadow: "#000000"
property color m3success: "#B5CCBA"
property color m3successContainer: "#374B3E"
property color m3surface: "#131317" property color m3surface: "#131317"
property color m3surfaceDim: "#131317"
property color m3surfaceBright: "#39393d" property color m3surfaceBright: "#39393d"
property color m3surfaceContainerLowest: "#0e0e12"
property color m3surfaceContainerLow: "#1b1b1f"
property color m3surfaceContainer: "#1f1f23" property color m3surfaceContainer: "#1f1f23"
property color m3surfaceContainerHigh: "#2a2a2e" property color m3surfaceContainerHigh: "#2a2a2e"
property color m3surfaceContainerHighest: "#353438" property color m3surfaceContainerHighest: "#353438"
property color m3onSurface: "#e4e1e7" property color m3surfaceContainerLow: "#1b1b1f"
property color m3surfaceVariant: "#46464f" property color m3surfaceContainerLowest: "#0e0e12"
property color m3onSurfaceVariant: "#c6c5d1" property color m3surfaceDim: "#131317"
property color m3inverseSurface: "#e4e1e7"
property color m3inverseOnSurface: "#303034"
property color m3outline: "#90909a"
property color m3outlineVariant: "#46464f"
property color m3shadow: "#000000"
property color m3scrim: "#000000"
property color m3surfaceTint: "#bac3ff" property color m3surfaceTint: "#bac3ff"
property color m3primary: "#bac3ff" property color m3surfaceVariant: "#46464f"
property color m3onPrimary: "#232c60"
property color m3primaryContainer: "#6a73ac"
property color m3onPrimaryContainer: "#ffffff"
property color m3inversePrimary: "#525b92"
property color m3secondary: "#c3c5e0"
property color m3onSecondary: "#2c2f44"
property color m3secondaryContainer: "#42455c"
property color m3onSecondaryContainer: "#b1b3ce"
property color m3tertiary: "#f1b3e5" property color m3tertiary: "#f1b3e5"
property color m3onTertiary: "#4c1f48"
property color m3tertiaryContainer: "#b77ead" property color m3tertiaryContainer: "#b77ead"
property color m3onTertiaryContainer: "#000000"
property color m3error: "#ffb4ab"
property color m3onError: "#690005"
property color m3errorContainer: "#93000a"
property color m3onErrorContainer: "#ffdad6"
property color m3primaryFixed: "#dee0ff"
property color m3primaryFixedDim: "#bac3ff"
property color m3onPrimaryFixed: "#0b154b"
property color m3onPrimaryFixedVariant: "#3a4378"
property color m3secondaryFixed: "#dfe1fd"
property color m3secondaryFixedDim: "#c3c5e0"
property color m3onSecondaryFixed: "#171a2e"
property color m3onSecondaryFixedVariant: "#42455c"
property color m3tertiaryFixed: "#ffd7f4" property color m3tertiaryFixed: "#ffd7f4"
property color m3tertiaryFixedDim: "#f1b3e5" property color m3tertiaryFixedDim: "#f1b3e5"
property color m3onTertiaryFixed: "#340831" property color m3tertiary_paletteKeyColor: "#9b6592"
property color m3onTertiaryFixedVariant: "#66365f" }
property color m3success: "#B5CCBA" component M3Palette: QtObject {
property color m3background: "#191114"
property color m3error: "#ffb4ab"
property color m3errorContainer: "#93000a"
property color m3inverseOnSurface: "#372e30"
property color m3inversePrimary: "#8b4a62"
property color m3inverseSurface: "#efdfe2"
property color m3neutral_paletteKeyColor: "#807477"
property color m3neutral_variant_paletteKeyColor: "#837377"
property color m3onBackground: "#efdfe2"
property color m3onError: "#690005"
property color m3onErrorContainer: "#ffdad6"
property color m3onPrimary: "#541d34"
property color m3onPrimaryContainer: "#ffd9e3"
property color m3onPrimaryFixed: "#39071f"
property color m3onPrimaryFixedVariant: "#6f334a"
property color m3onSecondary: "#422932"
property color m3onSecondaryContainer: "#ffd9e3"
property color m3onSecondaryFixed: "#2b151d"
property color m3onSecondaryFixedVariant: "#5a3f48"
property color m3onSuccess: "#213528" property color m3onSuccess: "#213528"
property color m3successContainer: "#374B3E"
property color m3onSuccessContainer: "#D1E9D6" property color m3onSuccessContainer: "#D1E9D6"
property color m3onSurface: "#efdfe2"
property color m3onSurfaceVariant: "#d5c2c6"
property color m3onTertiary: "#48290c"
property color m3onTertiaryContainer: "#000000"
property color m3onTertiaryFixed: "#2f1500"
property color m3onTertiaryFixedVariant: "#623f21"
property color m3outline: "#9e8c91"
property color m3outlineVariant: "#514347"
property color m3primary: "#ffb0ca"
property color m3primaryContainer: "#6f334a"
property color m3primaryFixed: "#ffd9e3"
property color m3primaryFixedDim: "#ffb0ca"
property color m3primary_paletteKeyColor: "#a8627b"
property color m3scrim: "#000000"
property color m3secondary: "#e2bdc7"
property color m3secondaryContainer: "#5a3f48"
property color m3secondaryFixed: "#ffd9e3"
property color m3secondaryFixedDim: "#e2bdc7"
property color m3secondary_paletteKeyColor: "#8e6f78"
property color m3shadow: "#000000"
property color m3success: "#B5CCBA"
property color m3successContainer: "#374B3E"
property color m3surface: "#191114"
property color m3surfaceBright: "#403739"
property color m3surfaceContainer: "#261d20"
property color m3surfaceContainerHigh: "#31282a"
property color m3surfaceContainerHighest: "#3c3235"
property color m3surfaceContainerLow: "#22191c"
property color m3surfaceContainerLowest: "#130c0e"
property color m3surfaceDim: "#191114"
property color m3surfaceTint: "#ffb0ca"
property color m3surfaceVariant: "#514347"
property color m3tertiary: "#f0bc95"
property color m3tertiaryContainer: "#b58763"
property color m3tertiaryFixed: "#ffdcc3"
property color m3tertiaryFixedDim: "#f0bc95"
property color m3tertiary_paletteKeyColor: "#986e4c"
}
component M3TPalette: QtObject {
readonly property color m3background: root.layer(root.palette.m3background, 0)
readonly property color m3error: root.layer(root.palette.m3error)
readonly property color m3errorContainer: root.layer(root.palette.m3errorContainer)
readonly property color m3inverseOnSurface: root.layer(root.palette.m3inverseOnSurface)
readonly property color m3inversePrimary: root.layer(root.palette.m3inversePrimary)
readonly property color m3inverseSurface: root.layer(root.palette.m3inverseSurface, 0)
readonly property color m3neutral_paletteKeyColor: root.layer(root.palette.m3neutral_paletteKeyColor)
readonly property color m3neutral_variant_paletteKeyColor: root.layer(root.palette.m3neutral_variant_paletteKeyColor)
readonly property color m3onBackground: root.layer(root.palette.m3onBackground)
readonly property color m3onError: root.layer(root.palette.m3onError)
readonly property color m3onErrorContainer: root.layer(root.palette.m3onErrorContainer)
readonly property color m3onPrimary: root.layer(root.palette.m3onPrimary)
readonly property color m3onPrimaryContainer: root.layer(root.palette.m3onPrimaryContainer)
readonly property color m3onPrimaryFixed: root.layer(root.palette.m3onPrimaryFixed)
readonly property color m3onPrimaryFixedVariant: root.layer(root.palette.m3onPrimaryFixedVariant)
readonly property color m3onSecondary: root.layer(root.palette.m3onSecondary)
readonly property color m3onSecondaryContainer: root.layer(root.palette.m3onSecondaryContainer)
readonly property color m3onSecondaryFixed: root.layer(root.palette.m3onSecondaryFixed)
readonly property color m3onSecondaryFixedVariant: root.layer(root.palette.m3onSecondaryFixedVariant)
readonly property color m3onSuccess: root.layer(root.palette.m3onSuccess)
readonly property color m3onSuccessContainer: root.layer(root.palette.m3onSuccessContainer)
readonly property color m3onSurface: root.layer(root.palette.m3onSurface)
readonly property color m3onSurfaceVariant: root.layer(root.palette.m3onSurfaceVariant)
readonly property color m3onTertiary: root.layer(root.palette.m3onTertiary)
readonly property color m3onTertiaryContainer: root.layer(root.palette.m3onTertiaryContainer)
readonly property color m3onTertiaryFixed: root.layer(root.palette.m3onTertiaryFixed)
readonly property color m3onTertiaryFixedVariant: root.layer(root.palette.m3onTertiaryFixedVariant)
readonly property color m3outline: root.layer(root.palette.m3outline)
readonly property color m3outlineVariant: root.layer(root.palette.m3outlineVariant)
readonly property color m3primary: root.layer(root.palette.m3primary)
readonly property color m3primaryContainer: root.layer(root.palette.m3primaryContainer)
readonly property color m3primaryFixed: root.layer(root.palette.m3primaryFixed)
readonly property color m3primaryFixedDim: root.layer(root.palette.m3primaryFixedDim)
readonly property color m3primary_paletteKeyColor: root.layer(root.palette.m3primary_paletteKeyColor)
readonly property color m3scrim: root.layer(root.palette.m3scrim)
readonly property color m3secondary: root.layer(root.palette.m3secondary)
readonly property color m3secondaryContainer: root.layer(root.palette.m3secondaryContainer)
readonly property color m3secondaryFixed: root.layer(root.palette.m3secondaryFixed)
readonly property color m3secondaryFixedDim: root.layer(root.palette.m3secondaryFixedDim)
readonly property color m3secondary_paletteKeyColor: root.layer(root.palette.m3secondary_paletteKeyColor)
readonly property color m3shadow: root.layer(root.palette.m3shadow)
readonly property color m3success: root.layer(root.palette.m3success)
readonly property color m3successContainer: root.layer(root.palette.m3successContainer)
readonly property color m3surface: root.layer(root.palette.m3surface, 0)
readonly property color m3surfaceBright: root.layer(root.palette.m3surfaceBright, 0)
readonly property color m3surfaceContainer: root.layer(root.palette.m3surfaceContainer)
readonly property color m3surfaceContainerHigh: root.layer(root.palette.m3surfaceContainerHigh)
readonly property color m3surfaceContainerHighest: root.layer(root.palette.m3surfaceContainerHighest)
readonly property color m3surfaceContainerLow: root.layer(root.palette.m3surfaceContainerLow)
readonly property color m3surfaceContainerLowest: root.layer(root.palette.m3surfaceContainerLowest)
readonly property color m3surfaceDim: root.layer(root.palette.m3surfaceDim, 0)
readonly property color m3surfaceTint: root.layer(root.palette.m3surfaceTint)
readonly property color m3surfaceVariant: root.layer(root.palette.m3surfaceVariant, 0)
readonly property color m3tertiary: root.layer(root.palette.m3tertiary)
readonly property color m3tertiaryContainer: root.layer(root.palette.m3tertiaryContainer)
readonly property color m3tertiaryFixed: root.layer(root.palette.m3tertiaryFixed)
readonly property color m3tertiaryFixedDim: root.layer(root.palette.m3tertiaryFixedDim)
readonly property color m3tertiary_paletteKeyColor: root.layer(root.palette.m3tertiary_paletteKeyColor)
}
component Transparency: QtObject {
readonly property real base: Appearance.transparency.base - (root.light ? 0.1 : 0)
readonly property bool enabled: Appearance.transparency.enabled
readonly property real layers: Appearance.transparency.layers
} }
} }
+27 -18
View File
@@ -2,35 +2,44 @@ import Quickshell.Io
import Quickshell import Quickshell
JsonObject { JsonObject {
property Apps apps: Apps {
}
property Color color: Color {
}
property bool desktopIcons: false
property Idle idle: Idle {
}
property string logo: "" property string logo: ""
property string wallpaperPath: Quickshell.env("HOME") + "/Pictures/Wallpapers" property string wallpaperPath: Quickshell.env("HOME") + "/Pictures/Wallpapers"
property Color color: Color {}
property Apps apps: Apps {}
property Idle idle: Idle {}
component Color: JsonObject { component Apps: JsonObject {
property bool wallust: false property list<string> audio: ["pavucontrol"]
property bool schemeGeneration: true property list<string> explorer: ["dolphin"]
property string mode: "dark" property list<string> playback: ["mpv"]
property int scheduleDarkStart: 0 property list<string> terminal: ["kitty"]
property int scheduleDarkEnd: 0 }
property bool neovimColors: false component Color: JsonObject {
property int hyprsunsetTemp: 5000
property string mode: "dark"
property bool neovimColors: false
property bool scheduleDark: false
property int scheduleDarkEnd: 0
property int scheduleDarkStart: 0
property bool scheduleHyprsunset: false
property int scheduleHyprsunsetEnd: 0
property int scheduleHyprsunsetStart: 0
property bool schemeGeneration: true
property bool smart: false
} }
component Apps: JsonObject {
property list<string> terminal: ["kitty"]
property list<string> audio: ["pavucontrol"]
property list<string> playback: ["mpv"]
property list<string> explorer: ["dolphin"]
}
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"
+94 -70
View File
@@ -1,84 +1,108 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property int maxAppsShown: 10
property int maxWallpapers: 7
property string actionPrefix: ">" property string actionPrefix: ">"
property string specialPrefix: "@"
property Sizes sizes: Sizes {}
property UseFuzzy useFuzzy: UseFuzzy {}
component UseFuzzy: JsonObject {
property bool apps: false
property bool actions: false
property bool schemes: false
property bool variants: false
property bool wallpapers: false
}
component Sizes: JsonObject {
property int itemWidth: 600
property int itemHeight: 50
property int wallpaperWidth: 280
property int wallpaperHeight: 200
}
property list<var> actions: [ property list<var> actions: [
{ {
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: "Wallpaper", name: "Light",
icon: "image", icon: "light_mode",
description: "Change the current wallpaper", description: "Change to light mode",
command: ["autocomplete", "wallpaper"], command: ["setMode", "light"],
enabled: true, enabled: true,
dangerous: false dangerous: false
}, },
{ {
name: "Shutdown", name: "Dark",
icon: "power_settings_new", icon: "dark_mode",
description: "Shutdown the system", description: "Change to dark mode",
command: ["systemctl", "poweroff"], command: ["setMode", "dark"],
enabled: true, enabled: true,
dangerous: true dangerous: false
}, },
{ {
name: "Reboot", name: "Wallpaper",
icon: "cached", icon: "image",
description: "Reboot the system", description: "Change the current wallpaper",
command: ["systemctl", "reboot"], command: ["autocomplete", "wallpaper"],
enabled: true, enabled: true,
dangerous: true dangerous: false
}, },
{ {
name: "Logout", name: "Variant",
icon: "exit_to_app", icon: "colors",
description: "Log out of the current session", description: "Change the current scheme variant",
command: ["loginctl", "terminate-user", ""], command: ["autocomplete", "variant"],
enabled: true, enabled: true,
dangerous: true dangerous: false
}, },
{ {
name: "Lock", name: "Shutdown",
icon: "lock", icon: "power_settings_new",
description: "Lock the current session", description: "Shutdown the system",
command: ["loginctl", "lock-session"], command: ["systemctl", "poweroff"],
enabled: true, enabled: true,
dangerous: false dangerous: true
}, },
{ {
name: "Sleep", name: "Reboot",
icon: "bedtime", icon: "cached",
description: "Suspend then hibernate", description: "Reboot the system",
command: ["systemctl", "suspend-then-hibernate"], command: ["systemctl", "reboot"],
enabled: true, enabled: true,
dangerous: false dangerous: true
}, },
{
name: "Logout",
icon: "logout",
description: "Log out of the current session",
command: ["loginctl", "terminate-user", ""],
enabled: true,
dangerous: true
},
{
name: "Lock",
icon: "lock",
description: "Lock the current session",
command: ["loginctl", "lock-session"],
enabled: true,
dangerous: false
},
{
name: "Sleep",
icon: "bedtime",
description: "Suspend then hibernate",
command: ["systemctl", "suspend-then-hibernate"],
enabled: true,
dangerous: false
},
] ]
property int maxAppsShown: 10
property int maxWallpapers: 7
property Sizes sizes: Sizes {
}
property string specialPrefix: "@"
property UseFuzzy useFuzzy: UseFuzzy {
}
component Sizes: JsonObject {
property int itemHeight: 50
property int itemWidth: 600
property int wallpaperHeight: 200
property int wallpaperWidth: 280
}
component UseFuzzy: JsonObject {
property bool actions: false
property bool apps: false
property bool schemes: false
property bool variants: false
property bool wallpapers: false
}
} }
+10 -9
View File
@@ -1,15 +1,16 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property bool recolorLogo: false
property bool enableFprint: true
property int maxFprintTries: 3
property Sizes sizes: Sizes {}
property int blurAmount: 40 property int blurAmount: 40
property bool enableFprint: true
property int maxFprintTries: 3
property bool recolorLogo: false
property Sizes sizes: Sizes {
}
component Sizes: JsonObject { component Sizes: JsonObject {
property real heightMult: 0.7 property int centerWidth: 600
property real ratio: 16 / 9 property real heightMult: 0.7
property int centerWidth: 600 property real ratio: 16 / 9
} }
} }
+20 -21
View File
@@ -2,26 +2,25 @@ pragma Singleton
import Quickshell import Quickshell
Singleton { Singleton {
id: root id: root
property real scale: Appearance.anim.durations.scale readonly property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1]
readonly property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
readonly property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1] readonly property int emphasizedAccelTime: 200 * scale
readonly property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1] readonly property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
readonly property int emphasizedAccelTime: 200 * scale readonly property int emphasizedDecelTime: 400 * scale
readonly property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1] readonly property int emphasizedTime: 500 * scale
readonly property int emphasizedDecelTime: 400 * scale readonly property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1.00, 1, 1]
readonly property int emphasizedTime: 500 * scale readonly property int expressiveDefaultSpatialTime: 500 * scale
readonly property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1.00, 1, 1] readonly property list<real> expressiveEffects: [0.34, 0.80, 0.34, 1.00, 1, 1]
readonly property int expressiveDefaultSpatialTime: 500 * scale readonly property int expressiveEffectsTime: 200 * scale
readonly property list<real> expressiveEffects: [0.34, 0.80, 0.34, 1.00, 1, 1] readonly property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.90, 1, 1]
readonly property int expressiveEffectsTime: 200 * scale readonly property int expressiveFastSpatialTime: 350 * scale
readonly property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.90, 1, 1] property real scale: Appearance.anim.durations.scale
readonly property int expressiveFastSpatialTime: 350 * scale readonly property list<real> standard: [0.2, 0, 0, 1, 1, 1]
readonly property list<real> standard: [0.2, 0, 0, 1, 1, 1] readonly property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1]
readonly property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1] readonly property int standardAccelTime: 200 * scale
readonly property int standardAccelTime: 200 * scale readonly property list<real> standardDecel: [0, 0, 0, 1, 1, 1]
readonly property list<real> standardDecel: [0, 0, 0, 1, 1, 1] readonly property int standardDecelTime: 250 * scale
readonly property int standardDecelTime: 250 * scale readonly property int standardTime: 300 * scale
readonly property int standardTime: 300 * scale
} }
+15 -13
View File
@@ -1,18 +1,20 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property bool expire: true property bool actionOnClick: false
property int defaultExpireTimeout: 5000 property int appNotifCooldown: 0
property real clearThreshold: 0.3 property real clearThreshold: 0.3
property int expandThreshold: 20 property int defaultExpireTimeout: 5000
property bool actionOnClick: false property int expandThreshold: 20
property int groupPreviewNum: 3 property bool expire: true
property bool openExpanded: false property int groupPreviewNum: 3
property Sizes sizes: Sizes {} property bool openExpanded: false
property Sizes sizes: Sizes {
}
component Sizes: JsonObject { component Sizes: JsonObject {
property int width: 400 property int badge: 20
property int image: 41 property int image: 41
property int badge: 20 property int width: 400
} }
} }
+10 -9
View File
@@ -1,15 +1,16 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property bool enabled: true
property int hideDelay: 3000
property bool enableBrightness: true
property bool enableMicrophone: true
property bool allMonBrightness: false property bool allMonBrightness: false
property Sizes sizes: Sizes {} property bool enableBrightness: true
property bool enableMicrophone: true
property bool enabled: true
property int hideDelay: 3000
property Sizes sizes: Sizes {
}
component Sizes: JsonObject { component Sizes: JsonObject {
property int sliderWidth: 30 property int sliderHeight: 150
property int sliderHeight: 150 property int sliderWidth: 30
} }
} }
+2 -2
View File
@@ -1,8 +1,8 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property int rows: 2
property int columns: 5 property int columns: 5
property real scale: 0.16
property bool enable: false property bool enable: false
property int rows: 2
property real scale: 0.16
} }
+16 -14
View File
@@ -2,18 +2,20 @@ import Quickshell.Io
import QtQuick import QtQuick
JsonObject { JsonObject {
property string weatherLocation: "" property real audioIncrement: 0.1
property bool useFahrenheit: false property real brightnessIncrement: 0.1
property bool useTwelveHourClock: Qt.locale().timeFormat(Locale.ShortFormat).toLowerCase().includes("a") property bool ddcutilService: false
property string gpuType: "" property string defaultPlayer: "Spotify"
property real audioIncrement: 0.1 property string gpuType: ""
property real brightnessIncrement: 0.1 property real maxVolume: 1.0
property real maxVolume: 1.0 property list<var> playerAliases: [
property string defaultPlayer: "Spotify" {
property list<var> playerAliases: [ "from": "com.github.th_ch.youtube_music",
{ "to": "YT Music"
"from": "com.github.th_ch.youtube_music", }
"to": "YT Music" ]
} property bool useFahrenheit: false
] property bool useTwelveHourClock: Qt.locale().timeFormat(Locale.ShortFormat).toLowerCase().includes("a")
property int visualizerBars: 30
property string weatherLocation: ""
} }
+6 -5
View File
@@ -1,10 +1,11 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property bool enabled: true property bool enabled: true
property Sizes sizes: Sizes {} property Sizes sizes: Sizes {
}
component Sizes: JsonObject { component Sizes: JsonObject {
property int width: 430 property int width: 430
} }
} }
+3 -3
View File
@@ -1,7 +1,7 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property bool enabled: false property real base: 0.85
property real base: 0.85 property bool enabled: false
property real layers: 0.4 property real layers: 0.4
} }
+30 -30
View File
@@ -1,35 +1,35 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property bool enabled: true property bool enabled: true
property int maxToasts: 4 property int maxToasts: 4
property Sizes sizes: Sizes {
}
property Toasts toasts: Toasts {
}
property Vpn vpn: Vpn {
}
property Sizes sizes: Sizes {} component Sizes: JsonObject {
property Toasts toasts: Toasts {} property int toastWidth: 430
property Vpn vpn: Vpn {} property int width: 430
}
component Sizes: JsonObject { component Toasts: JsonObject {
property int width: 430 property bool audioInputChanged: true
property int toastWidth: 430 property bool audioOutputChanged: true
} property bool capsLockChanged: true
property bool chargingChanged: true
component Toasts: JsonObject { property bool configLoaded: true
property bool configLoaded: true property bool dndChanged: true
property bool chargingChanged: true property bool gameModeChanged: true
property bool gameModeChanged: true property bool kbLayoutChanged: true
property bool dndChanged: true property bool kbLimit: true
property bool audioOutputChanged: true property bool nowPlaying: false
property bool audioInputChanged: true property bool numLockChanged: true
property bool capsLockChanged: true property bool vpnChanged: true
property bool numLockChanged: true }
property bool kbLayoutChanged: true component Vpn: JsonObject {
property bool kbLimit: true property bool enabled: false
property bool vpnChanged: true property list<var> provider: ["netbird"]
property bool nowPlaying: false }
}
component Vpn: JsonObject {
property bool enabled: false
property list<var> provider: ["netbird"]
}
} }
+2 -2
View File
@@ -1,6 +1,6 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property string textColor: "black" property string inactiveTextColor: "white"
property string inactiveTextColor: "white" property string textColor: "black"
} }
+114 -108
View File
@@ -1,144 +1,150 @@
pragma Singleton pragma Singleton
import qs.Config
import ZShell.Services import ZShell.Services
import ZShell import ZShell
import Quickshell import Quickshell
import Quickshell.Services.Pipewire import Quickshell.Services.Pipewire
import QtQuick import QtQuick
import qs.Config
Singleton { Singleton {
id: root id: root
property string previousSinkName: "" readonly property alias beatTracker: beatTracker
property string previousSourceName: "" readonly property alias cava: cava
readonly property bool muted: !!sink?.audio?.muted
readonly property var nodes: Pipewire.nodes.values.reduce((acc, node) => {
if (!node.isStream) {
if (node.isSink)
acc.sinks.push(node);
else if (node.audio)
acc.sources.push(node);
} else if (node.isStream && node.audio) {
// Application streams (output streams)
acc.streams.push(node);
}
return acc;
}, {
sources: [],
sinks: [],
streams: []
})
property string previousSinkName: ""
property string previousSourceName: ""
readonly property PwNode sink: Pipewire.defaultAudioSink
readonly property list<PwNode> sinks: nodes.sinks
readonly property PwNode source: Pipewire.defaultAudioSource
readonly property bool sourceMuted: !!source?.audio?.muted
readonly property real sourceVolume: source?.audio?.volume ?? 0
readonly property list<PwNode> sources: nodes.sources
readonly property list<PwNode> streams: nodes.streams
readonly property real volume: sink?.audio?.volume ?? 0
readonly property var nodes: Pipewire.nodes.values.reduce((acc, node) => { function decrementSourceVolume(amount: real): void {
if (!node.isStream) { setSourceVolume(sourceVolume - (amount || Config.services.audioIncrement));
if (node.isSink) }
acc.sinks.push(node);
else if (node.audio)
acc.sources.push(node);
} else if (node.isStream && node.audio) {
// Application streams (output streams)
acc.streams.push(node);
}
return acc;
}, {
sources: [],
sinks: [],
streams: []
})
readonly property list<PwNode> sinks: nodes.sinks function decrementVolume(amount: real): void {
readonly property list<PwNode> sources: nodes.sources setVolume(volume - (amount || Config.services.audioIncrement));
readonly property list<PwNode> streams: nodes.streams }
readonly property PwNode sink: Pipewire.defaultAudioSink function getStreamMuted(stream: PwNode): bool {
readonly property PwNode source: Pipewire.defaultAudioSource return !!stream?.audio?.muted;
}
readonly property bool muted: !!sink?.audio?.muted function getStreamName(stream: PwNode): string {
readonly property real volume: sink?.audio?.volume ?? 0 if (!stream)
return qsTr("Unknown");
// Try application name first, then description, then name
return stream.applicationName || stream.description || stream.name || qsTr("Unknown Application");
}
readonly property bool sourceMuted: !!source?.audio?.muted function getStreamVolume(stream: PwNode): real {
readonly property real sourceVolume: source?.audio?.volume ?? 0 return stream?.audio?.volume ?? 0;
}
function setVolume(newVolume: real): void { function incrementSourceVolume(amount: real): void {
if (sink?.ready && sink?.audio) { setSourceVolume(sourceVolume + (amount || Config.services.audioIncrement));
sink.audio.muted = false; }
sink.audio.volume = Math.max(0, Math.min(Config.services.maxVolume, newVolume));
}
}
function incrementVolume(amount: real): void { function incrementVolume(amount: real): void {
setVolume(volume + (amount || Config.services.audioIncrement)); setVolume(volume + (amount || Config.services.audioIncrement));
} }
function decrementVolume(amount: real): void { function setAudioSink(newSink: PwNode): void {
setVolume(volume - (amount || Config.services.audioIncrement)); Pipewire.preferredDefaultAudioSink = newSink;
} }
function setSourceVolume(newVolume: real): void { function setAudioSource(newSource: PwNode): void {
if (source?.ready && source?.audio) { Pipewire.preferredDefaultAudioSource = newSource;
source.audio.muted = false; }
source.audio.volume = Math.max(0, Math.min(Config.services.maxVolume, newVolume));
}
}
function incrementSourceVolume(amount: real): void { function setSourceVolume(newVolume: real): void {
setSourceVolume(sourceVolume + (amount || Config.services.audioIncrement)); if (source?.ready && source?.audio) {
} source.audio.muted = false;
source.audio.volume = Math.max(0, Math.min(Config.services.maxVolume, newVolume));
}
}
function decrementSourceVolume(amount: real): void { function setStreamMuted(stream: PwNode, muted: bool): void {
setSourceVolume(sourceVolume - (amount || Config.services.audioIncrement)); if (stream?.ready && stream?.audio) {
} stream.audio.muted = muted;
}
}
function setAudioSink(newSink: PwNode): void { function setStreamVolume(stream: PwNode, newVolume: real): void {
Pipewire.preferredDefaultAudioSink = newSink; if (stream?.ready && stream?.audio) {
} stream.audio.muted = false;
stream.audio.volume = Math.max(0, Math.min(Config.services.maxVolume, newVolume));
}
}
function setAudioSource(newSource: PwNode): void { function setVolume(newVolume: real): void {
Pipewire.preferredDefaultAudioSource = newSource; if (sink?.ready && sink?.audio) {
} sink.audio.muted = false;
sink.audio.volume = Math.max(0, Math.min(Config.services.maxVolume, newVolume));
}
}
function setStreamVolume(stream: PwNode, newVolume: real): void { Component.onCompleted: {
if (stream?.ready && stream?.audio) { previousSinkName = sink?.description || sink?.name || qsTr("Unknown Device");
stream.audio.muted = false; previousSourceName = source?.description || source?.name || qsTr("Unknown Device");
stream.audio.volume = Math.max(0, Math.min(Config.services.maxVolume, newVolume)); }
} onSinkChanged: {
} if (!sink?.ready)
return;
function setStreamMuted(stream: PwNode, muted: bool): void { const newSinkName = sink.description || sink.name || qsTr("Unknown Device");
if (stream?.ready && stream?.audio) {
stream.audio.muted = muted;
}
}
function getStreamVolume(stream: PwNode): real { if (previousSinkName && previousSinkName !== newSinkName && Config.utilities.toasts.audioOutputChanged)
return stream?.audio?.volume ?? 0; Toaster.toast(qsTr("Audio output changed"), qsTr("Now using: %1").arg(newSinkName), "volume_up");
}
function getStreamMuted(stream: PwNode): bool { previousSinkName = newSinkName;
return !!stream?.audio?.muted; }
} onSourceChanged: {
if (!source?.ready)
return;
function getStreamName(stream: PwNode): string { const newSourceName = source.description || source.name || qsTr("Unknown Device");
if (!stream)
return qsTr("Unknown");
// Try application name first, then description, then name
return stream.applicationName || stream.description || stream.name || qsTr("Unknown Application");
}
onSinkChanged: { if (previousSourceName && previousSourceName !== newSourceName && Config.utilities.toasts.audioInputChanged)
if (!sink?.ready) Toaster.toast(qsTr("Audio input changed"), qsTr("Now using: %1").arg(newSourceName), "mic");
return;
const newSinkName = sink.description || sink.name || qsTr("Unknown Device"); previousSourceName = newSourceName;
}
if (previousSinkName && previousSinkName !== newSinkName && Config.utilities.toasts.audioOutputChanged) CavaProvider {
Toaster.toast(qsTr("Audio output changed"), qsTr("Now using: %1").arg(newSinkName), "volume_up"); id: cava
previousSinkName = newSinkName; bars: Config.services.visualizerBars
} }
onSourceChanged: { BeatTracker {
if (!source?.ready) id: beatTracker
return;
const newSourceName = source.description || source.name || qsTr("Unknown Device"); }
if (previousSourceName && previousSourceName !== newSourceName && Config.utilities.toasts.audioInputChanged) PwObjectTracker {
Toaster.toast(qsTr("Audio input changed"), qsTr("Now using: %1").arg(newSourceName), "mic"); objects: [...root.sinks, ...root.sources, ...root.streams]
}
previousSourceName = newSourceName;
}
Component.onCompleted: {
previousSinkName = sink?.description || sink?.name || qsTr("Unknown Device");
previousSourceName = source?.description || source?.name || qsTr("Unknown Device");
}
PwObjectTracker {
objects: [...root.sinks, ...root.sources, ...root.streams]
}
} }
+326
View File
@@ -0,0 +1,326 @@
pragma Singleton
import Quickshell
import Quickshell.Io
import QtQuick
Singleton {
id: root
readonly property AccessPoint active: networks.find(n => n.active) ?? null
readonly property var activeEthernet: ethernetDevices.find(d => d.connected) ?? null
property int ethernetDeviceCount: 0
property var ethernetDeviceDetails: null
property list<var> ethernetDevices: []
property bool ethernetProcessRunning: false
readonly property list<AccessPoint> networks: []
property var pendingConnection: null
property list<string> savedConnectionSsids: []
property list<string> savedConnections: []
readonly property bool scanning: Nmcli.scanning
property bool wifiEnabled: true
property var wirelessDeviceDetails: null
signal connectionFailed(string ssid)
function cidrToSubnetMask(cidr: string): string {
// Convert CIDR notation (e.g., "24") to subnet mask (e.g., "255.255.255.0")
const cidrNum = parseInt(cidr);
if (isNaN(cidrNum) || cidrNum < 0 || cidrNum > 32) {
return "";
}
const mask = (0xffffffff << (32 - cidrNum)) >>> 0;
const octets = [(mask >>> 24) & 0xff, (mask >>> 16) & 0xff, (mask >>> 8) & 0xff, mask & 0xff];
return octets.join(".");
}
function connectEthernet(connectionName: string, interfaceName: string): void {
Nmcli.connectEthernet(connectionName, interfaceName, result => {
if (result.success) {
getEthernetDevices();
// Refresh device details after connection
Qt.callLater(() => {
const activeDevice = root.ethernetDevices.find(function (d) {
return d.connected;
});
if (activeDevice && activeDevice.interface) {
updateEthernetDeviceDetails(activeDevice.interface);
}
}, 1000);
}
});
}
function connectToNetwork(ssid: string, password: string, bssid: string, callback: var): void {
// Set up pending connection tracking if callback provided
if (callback) {
const hasBssid = bssid !== undefined && bssid !== null && bssid.length > 0;
root.pendingConnection = {
ssid: ssid,
bssid: hasBssid ? bssid : "",
callback: callback
};
}
Nmcli.connectToNetwork(ssid, password, bssid, result => {
if (result && result.success) {
// Connection successful
if (callback)
callback(result);
root.pendingConnection = null;
} else if (result && result.needsPassword) {
// Password needed - callback will handle showing dialog
if (callback)
callback(result);
} else {
// Connection failed
if (result && result.error) {
root.connectionFailed(ssid);
}
if (callback)
callback(result);
root.pendingConnection = null;
}
});
}
function connectToNetworkWithPasswordCheck(ssid: string, isSecure: bool, callback: var, bssid: string): void {
// Set up pending connection tracking
const hasBssid = bssid !== undefined && bssid !== null && bssid.length > 0;
root.pendingConnection = {
ssid: ssid,
bssid: hasBssid ? bssid : "",
callback: callback
};
Nmcli.connectToNetworkWithPasswordCheck(ssid, isSecure, result => {
if (result && result.success) {
// Connection successful
if (callback)
callback(result);
root.pendingConnection = null;
} else if (result && result.needsPassword) {
// Password needed - callback will handle showing dialog
if (callback)
callback(result);
} else {
// Connection failed
if (result && result.error) {
root.connectionFailed(ssid);
}
if (callback)
callback(result);
root.pendingConnection = null;
}
}, bssid);
}
function disconnectEthernet(connectionName: string): void {
Nmcli.disconnectEthernet(connectionName, result => {
if (result.success) {
getEthernetDevices();
// Clear device details after disconnection
Qt.callLater(() => {
root.ethernetDeviceDetails = null;
});
}
});
}
function disconnectFromNetwork(): void {
// Try to disconnect - use connection name if available, otherwise use device
Nmcli.disconnectFromNetwork();
// Refresh network list after disconnection
Qt.callLater(() => {
Nmcli.getNetworks(() => {
syncNetworksFromNmcli();
});
}, 500);
}
function enableWifi(enabled: bool): void {
Nmcli.enableWifi(enabled, result => {
if (result.success) {
root.getWifiStatus();
Nmcli.getNetworks(() => {
syncNetworksFromNmcli();
});
}
});
}
function forgetNetwork(ssid: string): void {
// Delete the connection profile for this network
// This will remove the saved password and connection settings
Nmcli.forgetNetwork(ssid, result => {
if (result.success) {
// Refresh network list after deletion
Qt.callLater(() => {
Nmcli.getNetworks(() => {
syncNetworksFromNmcli();
});
}, 500);
}
});
}
function getEthernetDevices(): void {
root.ethernetProcessRunning = true;
Nmcli.getEthernetInterfaces(interfaces => {
root.ethernetDevices = Nmcli.ethernetDevices;
root.ethernetDeviceCount = Nmcli.ethernetDevices.length;
root.ethernetProcessRunning = false;
});
}
function getWifiStatus(): void {
Nmcli.getWifiStatus(enabled => {
root.wifiEnabled = enabled;
});
}
function hasSavedProfile(ssid: string): bool {
// Use Nmcli's hasSavedProfile which has the same logic
return Nmcli.hasSavedProfile(ssid);
}
function rescanWifi(): void {
Nmcli.rescanWifi();
}
function syncNetworksFromNmcli(): void {
const rNetworks = root.networks;
const nNetworks = Nmcli.networks;
// Build a map of existing networks by key
const existingMap = new Map();
for (const rn of rNetworks) {
const key = `${rn.frequency}:${rn.ssid}:${rn.bssid}`;
existingMap.set(key, rn);
}
// Build a map of new networks by key
const newMap = new Map();
for (const nn of nNetworks) {
const key = `${nn.frequency}:${nn.ssid}:${nn.bssid}`;
newMap.set(key, nn);
}
// Remove networks that no longer exist
for (const [key, network] of existingMap) {
if (!newMap.has(key)) {
const index = rNetworks.indexOf(network);
if (index >= 0) {
rNetworks.splice(index, 1);
network.destroy();
}
}
}
// Add or update networks from Nmcli
for (const [key, nNetwork] of newMap) {
const existing = existingMap.get(key);
if (existing) {
// Update existing network's lastIpcObject
existing.lastIpcObject = nNetwork.lastIpcObject;
} else {
// Create new AccessPoint from Nmcli's data
rNetworks.push(apComp.createObject(root, {
lastIpcObject: nNetwork.lastIpcObject
}));
}
}
}
function toggleWifi(): void {
Nmcli.toggleWifi(result => {
if (result.success) {
root.getWifiStatus();
Nmcli.getNetworks(() => {
syncNetworksFromNmcli();
});
}
});
}
function updateEthernetDeviceDetails(interfaceName: string): void {
Nmcli.getEthernetDeviceDetails(interfaceName, details => {
root.ethernetDeviceDetails = details;
});
}
function updateWirelessDeviceDetails(): void {
// Find the wireless interface by looking for wifi devices
// Pass empty string to let Nmcli find the active interface automatically
Nmcli.getWirelessDeviceDetails("", details => {
root.wirelessDeviceDetails = details;
});
}
Component.onCompleted: {
// Trigger ethernet device detection after initialization
Qt.callLater(() => {
getEthernetDevices();
});
// Load saved connections on startup
Nmcli.loadSavedConnections(() => {
root.savedConnections = Nmcli.savedConnections;
root.savedConnectionSsids = Nmcli.savedConnectionSsids;
});
// Get initial WiFi status
Nmcli.getWifiStatus(enabled => {
root.wifiEnabled = enabled;
});
// Sync networks from Nmcli on startup
Qt.callLater(() => {
syncNetworksFromNmcli();
}, 100);
}
// Sync saved connections from Nmcli when they're updated
Connections {
function onSavedConnectionSsidsChanged() {
root.savedConnectionSsids = Nmcli.savedConnectionSsids;
}
function onSavedConnectionsChanged() {
root.savedConnections = Nmcli.savedConnections;
}
target: Nmcli
}
Component {
id: apComp
AccessPoint {
}
}
Process {
command: ["nmcli", "m"]
running: true
stdout: SplitParser {
onRead: {
Nmcli.getNetworks(() => {
syncNetworksFromNmcli();
});
getEthernetDevices();
}
}
}
component AccessPoint: QtObject {
readonly property bool active: lastIpcObject.active
readonly property string bssid: lastIpcObject.bssid
readonly property int frequency: lastIpcObject.frequency
readonly property bool isSecure: security.length > 0
required property var lastIpcObject
readonly property string security: lastIpcObject.security
readonly property string ssid: lastIpcObject.ssid
readonly property int strength: lastIpcObject.strength
}
}
+1358
View File
File diff suppressed because it is too large Load Diff
+304 -279
View File
@@ -14,335 +14,360 @@ import qs.Paths
import qs.Config import qs.Config
Singleton { Singleton {
id: root id: root
property list<Notif> list: [] readonly property var appCooldownMap: new Map()
readonly property list<Notif> notClosed: list.filter( n => !n.closed ) property alias dnd: props.dnd
readonly property list<Notif> popups: list.filter( n => n.popup ) property list<Notif> list: []
property alias dnd: props.dnd property bool loaded
property alias server: server readonly property list<Notif> notClosed: list.filter(n => !n.closed)
readonly property list<Notif> popups: list.filter(n => n.popup)
property alias server: server
property bool loaded function shouldThrottle(appName: string): bool {
if (props.dnd)
return false;
onListChanged: { const key = (appName || "unknown").trim().toLowerCase();
if ( loaded ) { const cooldownSec = Config.notifs.appNotifCooldown;
saveTimer.restart(); const cooldownMs = Math.max(0, cooldownSec * 1000);
}
if ( root.list.length > 0 ) {
HasNotifications.hasNotifications = true;
} else {
HasNotifications.hasNotifications = false;
}
}
Timer { if (cooldownMs <= 0)
id: saveTimer return true;
interval: 1000
onTriggered: storage.setText( JSON.stringify( root.notClosed.map( n => ({
time: n.time,
id: n.id,
summary: n.summary,
body: n.body,
appIcon: n.appIcon,
appName: n.appName,
image: n.image,
expireTimeout: n.expireTimeout,
urgency: n.urgency,
resident: n.resident,
hasActionIcons: n.hasActionIcons,
actions: n.actions
}))));
}
PersistentProperties { const now = Date.now();
id: props const until = appCooldownMap.get(key) ?? 0;
property bool dnd if (now < until)
return false;
reloadableId: "notifs" appCooldownMap.set(key, now + cooldownMs);
} return true;
}
NotificationServer { onListChanged: {
id: server if (loaded) {
saveTimer.restart();
}
if (root.list.length > 0) {
HasNotifications.hasNotifications = true;
} else {
HasNotifications.hasNotifications = false;
}
}
keepOnReload: false Timer {
actionsSupported: true id: saveTimer
bodyHyperlinksSupported: true
bodyImagesSupported: true
bodyMarkupSupported: true
imageSupported: true
persistenceSupported: true
onNotification: notif => { interval: 1000
notif.tracked = true;
const comp = notifComp.createObject(root, { onTriggered: storage.setText(JSON.stringify(root.notClosed.map(n => ({
popup: !props.dnd, time: n.time,
notification: notif id: n.id,
}); summary: n.summary,
root.list = [comp, ...root.list]; body: n.body,
} appIcon: n.appIcon,
} appName: n.appName,
image: n.image,
expireTimeout: n.expireTimeout,
urgency: n.urgency,
resident: n.resident,
hasActionIcons: n.hasActionIcons,
actions: n.actions
}))))
}
FileView { PersistentProperties {
id: storage id: props
path: `${Paths.state}/notifs.json`
onLoaded: { property bool dnd
const data = JSON.parse(text());
for (const notif of data)
root.list.push(notifComp.createObject(root, notif));
root.list.sort((a, b) => b.time - a.time);
root.loaded = true;
}
onLoadFailed: err => { reloadableId: "notifs"
if (err === FileViewError.FileNotFound) { }
root.loaded = true;
setText("[]");
}
}
}
CustomShortcut { NotificationServer {
name: "clearnotifs" id: server
description: "Clear all notifications"
onPressed: {
for (const notif of root.list.slice())
notif.close();
}
}
IpcHandler { actionsSupported: true
target: "notifs" bodyHyperlinksSupported: true
bodyImagesSupported: true
bodyMarkupSupported: true
imageSupported: true
keepOnReload: false
persistenceSupported: true
function clear(): void { onNotification: notif => {
for (const notif of root.list.slice()) notif.tracked = true;
notif.close();
}
function isDndEnabled(): bool { const is_popup = root.shouldThrottle(notif.appName);
return props.dnd;
}
function toggleDnd(): void { const comp = notifComp.createObject(root, {
props.dnd = !props.dnd; popup: is_popup,
} notification: notif
});
root.list = [comp, ...root.list];
}
}
function enableDnd(): void { FileView {
props.dnd = true; id: storage
}
function disableDnd(): void { path: `${Paths.state}/notifs.json`
props.dnd = false;
}
}
component Notif: QtObject { onLoadFailed: err => {
id: notif if (err === FileViewError.FileNotFound) {
root.loaded = true;
setText("[]");
}
}
onLoaded: {
const data = JSON.parse(text());
for (const notif of data)
root.list.push(notifComp.createObject(root, notif));
root.list.sort((a, b) => b.time - a.time);
root.loaded = true;
}
}
property bool popup CustomShortcut {
property bool closed description: "Clear all notifications"
property var locks: new Set() name: "clearnotifs"
property date time: new Date() onPressed: {
readonly property string timeStr: { for (const notif of root.list.slice())
const diff = Time.date.getTime() - time.getTime(); notif.close();
const m = Math.floor(diff / 60000); }
}
if (m < 1) IpcHandler {
return qsTr("now"); function clear(): void {
for (const notif of root.list.slice())
notif.close();
}
const h = Math.floor(m / 60); function disableDnd(): void {
const d = Math.floor(h / 24); props.dnd = false;
}
if (d > 0) function enableDnd(): void {
return `${d}d`; props.dnd = true;
if (h > 0) }
return `${h}h`;
return `${m}m`;
}
property Notification notification function isDndEnabled(): bool {
property string id return props.dnd;
property string summary }
property string body
property string appIcon
property string appName
property string image
property real expireTimeout: 5
property int urgency: NotificationUrgency.Normal
property bool resident
property bool hasActionIcons
property list<var> actions
readonly property Timer timer: Timer { function toggleDnd(): void {
property int totalTime: Config.notifs.defaultExpireTimeout props.dnd = !props.dnd;
property int remainingTime: totalTime }
property bool paused: false
running: !paused target: "notifs"
repeat: true }
interval: 50
onTriggered: {
remainingTime -= interval;
if ( remainingTime <= 0 ) { Component {
remainingTime = 0; id: notifComp
notif.popup = false;
stop(); Notif {
}
}
component Notif: QtObject {
id: notif
property list<var> actions
property string appIcon
property string appName
property string body
property bool closed
readonly property Connections conn: Connections {
function onActionsChanged(): void {
notif.actions = notif.notification.actions.map(a => ({
identifier: a.identifier,
text: a.text,
invoke: () => a.invoke()
}));
}
function onAppIconChanged(): void {
notif.appIcon = notif.notification.appIcon;
}
function onAppNameChanged(): void {
notif.appName = notif.notification.appName;
}
function onBodyChanged(): void {
notif.body = notif.notification.body;
}
function onClosed(): void {
notif.close();
}
function onExpireTimeoutChanged(): void {
notif.expireTimeout = notif.notification.expireTimeout;
}
function onHasActionIconsChanged(): void {
notif.hasActionIcons = notif.notification.hasActionIcons;
}
function onImageChanged(): void {
notif.image = notif.notification.image;
if (notif.notification?.image)
notif.dummyImageLoader.active = true;
}
function onResidentChanged(): void {
notif.resident = notif.notification.resident;
}
function onSummaryChanged(): void {
notif.summary = notif.notification.summary;
}
function onUrgencyChanged(): void {
notif.urgency = notif.notification.urgency;
}
target: notif.notification
}
readonly property LazyLoader dummyImageLoader: LazyLoader {
active: false
PanelWindow {
color: "transparent"
implicitHeight: Config.notifs.sizes.image
implicitWidth: Config.notifs.sizes.image
mask: Region {
} }
}
}
readonly property LazyLoader dummyImageLoader: LazyLoader { Image {
active: false function tryCache(): void {
if (status !== Image.Ready || width != Config.notifs.sizes.image || height != Config.notifs.sizes.image)
return;
PanelWindow { const cacheKey = notif.appName + notif.summary + notif.id;
implicitWidth: Config.notifs.sizes.image let h1 = 0xdeadbeef, h2 = 0x41c6ce57, ch;
implicitHeight: Config.notifs.sizes.image for (let i = 0; i < cacheKey.length; i++) {
color: "transparent" ch = cacheKey.charCodeAt(i);
mask: Region {} h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
const hash = (h2 >>> 0).toString(16).padStart(8, 0) + (h1 >>> 0).toString(16).padStart(8, 0);
Image { const cache = `${Paths.notifimagecache}/${hash}.png`;
function tryCache(): void {
if (status !== Image.Ready || width != Config.notifs.sizes.image || height != Config.notifs.sizes.image)
return;
const cacheKey = notif.appName + notif.summary + notif.id;
let h1 = 0xdeadbeef, h2 = 0x41c6ce57, ch;
for (let i = 0; i < cacheKey.length; i++) {
ch = cacheKey.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
const hash = (h2 >>> 0).toString(16).padStart(8, 0) + (h1 >>> 0).toString(16).padStart(8, 0);
const cache = `${Paths.notifimagecache}/${hash}.png`;
ZShellIo.saveItem(this, Qt.resolvedUrl(cache), () => { ZShellIo.saveItem(this, Qt.resolvedUrl(cache), () => {
notif.image = cache; notif.image = cache;
notif.dummyImageLoader.active = false; notif.dummyImageLoader.active = false;
}); });
} }
anchors.fill: parent anchors.fill: parent
source: Qt.resolvedUrl(notif.image) asynchronous: true
fillMode: Image.PreserveAspectCrop cache: false
cache: false fillMode: Image.PreserveAspectCrop
asynchronous: true opacity: 0
opacity: 0 source: Qt.resolvedUrl(notif.image)
onStatusChanged: tryCache() onHeightChanged: tryCache()
onWidthChanged: tryCache() onStatusChanged: tryCache()
onHeightChanged: tryCache() onWidthChanged: tryCache()
} }
} }
} }
property real expireTimeout: 5
property bool hasActionIcons
property string id
property string image
property var locks: new Set()
property Notification notification
property bool popup
property bool resident
property string summary
property date time: new Date()
readonly property string timeStr: {
const diff = Time.date.getTime() - time.getTime();
const m = Math.floor(diff / 60000);
readonly property Connections conn: Connections { if (m < 1)
target: notif.notification return qsTr("now");
function onClosed(): void { const h = Math.floor(m / 60);
notif.close(); const d = Math.floor(h / 24);
}
function onSummaryChanged(): void { if (d > 0)
notif.summary = notif.notification.summary; return `${d}d`;
} if (h > 0)
return `${h}h`;
return `${m}m`;
}
readonly property Timer timer: Timer {
property bool paused: false
property int remainingTime: totalTime
property int totalTime: Config.notifs.defaultExpireTimeout
function onBodyChanged(): void { interval: 50
notif.body = notif.notification.body; repeat: true
} running: !paused
function onAppIconChanged(): void { onTriggered: {
notif.appIcon = notif.notification.appIcon; remainingTime -= interval;
}
function onAppNameChanged(): void { if (remainingTime <= 0) {
notif.appName = notif.notification.appName; remainingTime = 0;
} notif.popup = false;
stop();
}
}
}
property int urgency: NotificationUrgency.Normal
function onImageChanged(): void { function close(): void {
notif.image = notif.notification.image; closed = true;
if (notif.notification?.image) if (locks.size === 0 && root.list.includes(this)) {
notif.dummyImageLoader.active = true; root.list = root.list.filter(n => n !== this);
} notification?.dismiss();
destroy();
}
}
function onExpireTimeoutChanged(): void { function lock(item: Item): void {
notif.expireTimeout = notif.notification.expireTimeout; locks.add(item);
} }
function onUrgencyChanged(): void { function unlock(item: Item): void {
notif.urgency = notif.notification.urgency; locks.delete(item);
} if (closed)
close();
}
function onResidentChanged(): void { Component.onCompleted: {
notif.resident = notif.notification.resident; if (!notification)
} return;
function onHasActionIconsChanged(): void { id = notification.id;
notif.hasActionIcons = notif.notification.hasActionIcons; summary = notification.summary;
} body = notification.body;
appIcon = notification.appIcon;
function onActionsChanged(): void { appName = notification.appName;
notif.actions = notif.notification.actions.map(a => ({ image = notification.image;
identifier: a.identifier, if (notification?.image)
text: a.text, dummyImageLoader.active = true;
invoke: () => a.invoke() expireTimeout = notification.expireTimeout;
})); urgency = notification.urgency;
} resident = notification.resident;
} hasActionIcons = notification.hasActionIcons;
actions = notification.actions.map(a => ({
function lock(item: Item): void { identifier: a.identifier,
locks.add(item); text: a.text,
} invoke: () => a.invoke()
}));
function unlock(item: Item): void { }
locks.delete(item); }
if (closed)
close();
}
function close(): void {
closed = true;
if (locks.size === 0 && root.list.includes(this)) {
root.list = root.list.filter(n => n !== this);
notification?.dismiss();
destroy();
}
}
Component.onCompleted: {
if (!notification)
return;
id = notification.id;
summary = notification.summary;
body = notification.body;
appIcon = notification.appIcon;
appName = notification.appName;
image = notification.image;
if (notification?.image)
dummyImageLoader.active = true;
expireTimeout = notification.expireTimeout;
urgency = notification.urgency;
resident = notification.resident;
hasActionIcons = notification.hasActionIcons;
actions = notification.actions.map(a => ({
identifier: a.identifier,
text: a.text,
invoke: () => a.invoke()
}));
}
}
Component {
id: notifComp
Notif {}
}
} }
+58 -31
View File
@@ -1,6 +1,8 @@
import Quickshell import Quickshell
import QtQuick import QtQuick
import QtQuick.Shapes import QtQuick.Shapes
import qs.Components
import qs.Config
import qs.Modules as Modules import qs.Modules as Modules
import qs.Modules.Notifications as Notifications import qs.Modules.Notifications as Notifications
import qs.Modules.Notifications.Sidebar as Sidebar import qs.Modules.Notifications.Sidebar as Sidebar
@@ -8,73 +10,98 @@ import qs.Modules.Notifications.Sidebar.Utils as Utils
import qs.Modules.Dashboard as Dashboard 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.Drawing as Drawing
import qs.Modules.Settings as Settings
import qs.Modules.Dock as Dock
Shape { Shape {
id: root id: root
required property Panels panels required property Item bar
required property Item bar required property Panels panels
required property PersistentProperties visibilities required property PersistentProperties visibilities
anchors.fill: parent anchors.fill: parent
// anchors.margins: 8 anchors.margins: Config.barConfig.border
anchors.topMargin: bar.implicitHeight anchors.topMargin: bar.implicitHeight
preferredRendererType: Shape.CurveRenderer asynchronous: true
preferredRendererType: Shape.CurveRenderer
Component.onCompleted: console.log(root.bar.implicitHeight, root.bar.anchors.topMargin) Drawing.Background {
startX: 0
Osd.Background { startY: wrapper.y - rounding
wrapper: root.panels.osd wrapper: root.panels.drawing
startX: root.width - root.panels.sidebar.width
startY: ( root.height - wrapper.height ) / 2 - rounding
} }
Modules.Background { Resources.Background {
wrapper: root.panels.popouts startX: 0 - rounding
invertBottomRounding: wrapper.x <= 0 startY: 0
wrapper: root.panels.resources
}
startX: wrapper.x - 8 Osd.Background {
startY: wrapper.y startX: root.width - root.panels.sidebar.width
} startY: (root.height - wrapper.height) / 2 - rounding
wrapper: root.panels.osd
}
Modules.Background {
invertBottomRounding: wrapper.x <= 0
rounding: root.panels.popouts.currentName.startsWith("updates") ? Appearance.rounding.normal : Appearance.rounding.smallest
startX: wrapper.x - rounding
startY: wrapper.y
wrapper: root.panels.popouts
}
Notifications.Background { Notifications.Background {
wrapper: root.panels.notifications
sidebar: sidebar sidebar: sidebar
startX: root.width startX: root.width
startY: 0 startY: 0
wrapper: root.panels.notifications
} }
Launcher.Background { Launcher.Background {
wrapper: root.panels.launcher startX: (root.width - wrapper.width) / 2 - rounding
startX: ( root.width - wrapper.width ) / 2 - rounding
startY: root.height startY: root.height
wrapper: root.panels.launcher
} }
Dashboard.Background { Dashboard.Background {
wrapper: root.panels.dashboard
startX: root.width - root.panels.dashboard.width - rounding startX: root.width - root.panels.dashboard.width - rounding
startY: 0 startY: 0
wrapper: root.panels.dashboard
} }
Utils.Background { Utils.Background {
wrapper: root.panels.utilities
sidebar: sidebar sidebar: sidebar
startX: root.width startX: root.width
startY: root.height startY: root.height
wrapper: root.panels.utilities
} }
Sidebar.Background { Sidebar.Background {
id: sidebar id: sidebar
wrapper: root.panels.sidebar
panels: root.panels panels: root.panels
startX: root.width startX: root.width
startY: root.panels.notifications.height startY: root.panels.notifications.height
wrapper: root.panels.sidebar
}
Settings.Background {
id: settings
startX: (root.width - wrapper.width) / 2 - rounding
startY: 0
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 {
}
}
}
+165 -235
View File
@@ -5,274 +5,204 @@ import qs.Config
import qs.Modules as BarPopouts import qs.Modules as BarPopouts
CustomMouseArea { CustomMouseArea {
id: root id: root
required property ShellScreen screen required property Item bar
required property BarPopouts.Wrapper popouts property bool dashboardShortcutActive
required property PersistentProperties visibilities property point dragStart
required property Panels panels required property Drawing drawing
required property Item bar required property DrawingInput input
property bool osdShortcutActive
required property Panels panels
required property BarPopouts.Wrapper popouts
required property ShellScreen screen
property bool utilitiesShortcutActive
required property PersistentProperties visibilities
property point dragStart function inBottomPanel(panel: Item, x: real, y: real): bool {
property bool dashboardShortcutActive return y > root.height - panel.height - Config.barConfig.border && withinPanelWidth(panel, x, y);
property bool osdShortcutActive }
property bool utilitiesShortcutActive
function withinPanelHeight(panel: Item, x: real, y: real): bool { function inLeftPanel(panel: Item, x: real, y: real): bool {
const panelY = panel.y + bar.implicitHeight; return x < panel.x + panel.width + Config.barConfig.border && withinPanelHeight(panel, x, y);
return y >= panelY && y <= panelY + panel.height; }
}
function withinPanelWidth(panel: Item, x: real, y: real): bool { function inRightPanel(panel: Item, x: real, y: real): bool {
const panelX = panel.x; return x > panel.x - Config.barConfig.border && withinPanelHeight(panel, x, y);
return x >= panelX && x <= panelX + panel.width; }
}
function inLeftPanel(panel: Item, x: real, y: real): bool { function inTopPanel(panel: Item, x: real, y: real): bool {
return x < panel.x + panel.width && withinPanelHeight(panel, x, y); return y < bar.implicitHeight + panel.height && withinPanelWidth(panel, x, y);
} }
function inRightPanel(panel: Item, x: real, y: real): bool { function onWheel(event: WheelEvent): void {
return x > panel.x && withinPanelHeight(panel, x, y); if (event.x < bar.implicitWidth) {
} bar.handleWheel(event.y, event.angleDelta);
}
}
function inTopPanel(panel: Item, x: real, y: real): bool { function withinPanelHeight(panel: Item, x: real, y: real): bool {
return y < bar.implicitHeight + panel.height && withinPanelWidth(panel, x, y); const panelY = panel.y + bar.implicitHeight;
} return y >= panelY && y <= panelY + panel.height;
}
function inBottomPanel(panel: Item, x: real, y: real): bool { function withinPanelWidth(panel: Item, x: real, y: real): bool {
return y > root.height - panel.height && withinPanelWidth(panel, x, y); const panelX = panel.x;
} return x >= panelX && x <= panelX + panel.width;
}
function onWheel(event: WheelEvent): void { anchors.fill: parent
if (event.x < bar.implicitWidth) { hoverEnabled: true
bar.handleWheel(event.y, event.angleDelta); propagateComposedEvents: true
}
}
anchors.fill: parent onContainsMouseChanged: {
hoverEnabled: true if (!containsMouse) {
if (!osdShortcutActive) {
visibilities.osd = false;
root.panels.osd.hovered = false;
}
// onPressed: event => { if (!popouts.currentName.startsWith("traymenu")) {
// if ( root.popouts.hasCurrent && !inTopPanel( root.popouts, event.x, event.y )) { popouts.hasCurrent = false;
// 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: { if (Config.barConfig.autoHide)
if (!containsMouse) { bar.isHovered = false;
// Only hide if not activated by shortcut }
if (!osdShortcutActive) { }
visibilities.osd = false; onPositionChanged: event => {
root.panels.osd.hovered = false; if (popouts.isDetached)
} return;
if (!popouts.currentName.startsWith("traymenu")) { const x = event.x;
popouts.hasCurrent = false; const y = event.y;
} const dragX = x - dragStart.x;
const dragY = y - dragStart.y;
if (Config.barConfig.autoHide && !root.visibilities.sidebar && !root.visibilities.dashboard) if (root.visibilities.isDrawing && !root.inLeftPanel(root.panels.drawing, x, y)) {
root.visibilities.bar = false; root.input.z = 2;
} root.panels.drawing.expanded = false;
} }
onPositionChanged: event => { if (!visibilities.bar && Config.barConfig.autoHide && y < bar.implicitHeight)
if (popouts.isDetached) bar.isHovered = true;
return;
const x = event.x; if (panels.sidebar.width === 0) {
const y = event.y; const showOsd = inRightPanel(panels.osd, x, y);
const dragX = x - dragStart.x;
const dragY = y - dragStart.y;
// Show bar in non-exclusive mode on hover if (showOsd) {
if (!visibilities.bar && Config.barConfig.autoHide && y < bar.implicitHeight + bar.anchors.topMargin) osdShortcutActive = false;
visibilities.bar = true; root.panels.osd.hovered = true;
}
} else {
const outOfSidebar = x < width - panels.sidebar.width;
const showOsd = outOfSidebar && inRightPanel(panels.osd, x, y);
if (panels.sidebar.width === 0) { if (!osdShortcutActive) {
// Show osd on hover visibilities.osd = showOsd;
const showOsd = inRightPanel(panels.osd, x, y); root.panels.osd.hovered = showOsd;
} else if (showOsd) {
osdShortcutActive = false;
root.panels.osd.hovered = true;
}
}
// // Always update visibility based on hover if not in shortcut mode if (!visibilities.dock && !visibilities.launcher && inBottomPanel(panels.dock, x, y))
if (!osdShortcutActive) { visibilities.dock = true;
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;
root.panels.osd.hovered = true;
}
// const showSidebar = pressed && dragStart.x > bar.implicitWidth + panels.sidebar.x; if (y < root.bar.implicitHeight) {
// root.bar.checkPopout(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 {
const outOfSidebar = x < width - panels.sidebar.width;
// Show osd on hover
const showOsd = outOfSidebar && inRightPanel(panels.osd, x, y);
// Always update visibility based on hover if not in shortcut mode Connections {
if (!osdShortcutActive) { function onDashboardChanged() {
visibilities.osd = showOsd; if (root.visibilities.dashboard) {
root.panels.osd.hovered = showOsd; const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY);
} else if (showOsd) { if (!inDashboardArea) {
// If hovering over OSD area while in shortcut mode, transition to hover control root.dashboardShortcutActive = true;
osdShortcutActive = false; }
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 root.visibilities.settings = false;
// if (Config.launcher.showOnHover) { root.visibilities.sidebar = false;
// if (!visibilities.launcher && inBottomPanel(panels.launcher, x, y)) root.popouts.hasCurrent = false;
// visibilities.launcher = true; } else {
// } else if (pressed && inBottomPanel(panels.launcher, dragStart.x, dragStart.y) && withinPanelWidth(panels.launcher, x, y)) { root.dashboardShortcutActive = false;
// 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 function onIsDrawingChanged() {
if (y < bar.implicitHeight) { if (!root.visibilities.isDrawing)
bar.checkPopout(x); root.drawing.clear();
} }
}
// Monitor individual visibility changes function onLauncherChanged() {
Connections { if (!root.visibilities.launcher) {
target: root.visibilities root.dashboardShortcutActive = false;
root.osdShortcutActive = false;
root.utilitiesShortcutActive = false;
function onLauncherChanged() { const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
// If launcher is hidden, clear shortcut flags for dashboard and OSD
if (!root.visibilities.launcher) {
root.dashboardShortcutActive = false;
root.osdShortcutActive = false;
root.utilitiesShortcutActive = false;
// Also hide dashboard and OSD if they're not being hovered if (!inOsdArea) {
const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY); root.visibilities.osd = false;
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY); root.panels.osd.hovered = false;
}
}
if (!inDashboardArea) { if (root.visibilities.launcher) {
root.visibilities.dashboard = false; root.visibilities.dock = false;
} root.visibilities.settings = false;
if (!inOsdArea) { }
root.visibilities.osd = false; }
root.panels.osd.hovered = false;
} function onOsdChanged() {
} if (root.visibilities.osd) {
} const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
if (!inOsdArea) {
root.osdShortcutActive = true;
}
} else {
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;
root.popouts.hasCurrent = false; root.popouts.hasCurrent = false;
} }
} }
function onDashboardChanged() { function onUtilitiesChanged() {
if (root.visibilities.dashboard) { if (root.visibilities.utilities) {
// Dashboard became visible, immediately check if this should be shortcut mode const inUtilitiesArea = root.inBottomPanel(root.panels.utilities, root.mouseX, root.mouseY);
const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY); if (!inUtilitiesArea) {
if (!inDashboardArea) { root.utilitiesShortcutActive = true;
root.dashboardShortcutActive = true; }
} } else {
root.utilitiesShortcutActive = false;
}
}
root.visibilities.sidebar = false; target: root.visibilities
root.popouts.hasCurrent = false; }
} else {
// Dashboard hidden, clear shortcut flag
root.dashboardShortcutActive = false;
// root.visibilities.bar = false;
}
}
function onOsdChanged() {
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);
console.log(inOsdArea);
if (!inOsdArea) {
root.osdShortcutActive = true;
}
} else {
// OSD hidden, clear shortcut flag
root.osdShortcutActive = false;
}
}
function onUtilitiesChanged() {
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);
if (!inUtilitiesArea) {
root.utilitiesShortcutActive = true;
}
} else {
// Utilities hidden, clear shortcut flag
root.utilitiesShortcutActive = false;
}
}
}
} }
+89 -54
View File
@@ -1,6 +1,6 @@
import Quickshell import Quickshell
import QtQuick import QtQuick
import QtQuick.Shapes import qs.Components
import qs.Modules as Modules import qs.Modules as Modules
import qs.Modules.Notifications as Notifications import qs.Modules.Notifications as Notifications
import qs.Modules.Notifications.Sidebar as Sidebar import qs.Modules.Notifications.Sidebar as Sidebar
@@ -9,116 +9,151 @@ import qs.Modules.Dashboard as Dashboard
import qs.Modules.Osd as Osd 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.Settings as Settings
import qs.Modules.Drawing as Drawing
import qs.Modules.Dock as Dock
import qs.Config import qs.Config
Item { Item {
id: root id: root
required property ShellScreen screen required property Item bar
required property Item bar 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 notifications: notifications
readonly property alias osd: osd
readonly property alias popouts: popouts
readonly property alias resources: resources
required property ShellScreen screen
readonly property alias settings: settings
readonly property alias sidebar: sidebar
readonly property alias toasts: toasts
readonly property alias utilities: utilities
required property PersistentProperties visibilities required property PersistentProperties visibilities
readonly property alias popouts: popouts anchors.fill: parent
readonly property alias sidebar: sidebar anchors.margins: Config.barConfig.border
readonly property alias notifications: notifications anchors.topMargin: bar.implicitHeight
readonly property alias utilities: utilities
readonly property alias dashboard: dashboard
readonly property alias osd: osd
readonly property alias toasts: toasts
readonly property alias launcher: launcher
anchors.fill: parent Resources.Wrapper {
// anchors.margins: 8 id: resources
anchors.topMargin: Config.barConfig.autoHide && !visibilities.bar ? 0 : bar.implicitHeight
Behavior on anchors.topMargin { anchors.left: parent.left
Modules.Anim {} anchors.top: parent.top
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
anchors.right: parent.right
anchors.rightMargin: sidebar.width
anchors.verticalCenter: parent.verticalCenter
clip: sidebar.width > 0 clip: sidebar.width > 0
screen: root.screen screen: root.screen
visibilities: root.visibilities visibilities: root.visibilities
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: sidebar.width
} }
Modules.Wrapper { Modules.Wrapper {
id: popouts id: popouts
screen: root.screen anchors.top: parent.top
screen: root.screen
anchors.top: parent.top x: {
const off = currentCenter - nonAnimWidth / 2;
x: { const diff = root.width - Math.floor(off + nonAnimWidth);
const off = currentCenter - nonAnimWidth / 2; if (diff < 0)
const diff = root.width - Math.floor(off + nonAnimWidth); return off + diff;
if ( diff < 0 ) return Math.floor(Math.max(off, 0));
return off + diff; }
return Math.floor( Math.max( off, 0 )); }
}
}
Toasts.Toasts { Toasts.Toasts {
id: toasts id: toasts
anchors.bottom: sidebar.visible ? parent.bottom : utilities.top anchors.bottom: sidebar.visible ? parent.bottom : utilities.top
anchors.right: sidebar.left
anchors.margins: Appearance.padding.normal anchors.margins: Appearance.padding.normal
anchors.right: sidebar.left
} }
Notifications.Wrapper { Notifications.Wrapper {
id: notifications id: notifications
visibilities: root.visibilities
panels: root
anchors.top: parent.top
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top
panels: root
visibilities: root.visibilities
} }
Launcher.Wrapper { Launcher.Wrapper {
id: launcher id: launcher
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
panels: root
screen: root.screen screen: root.screen
visibilities: root.visibilities visibilities: root.visibilities
panels: root
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
} }
Utils.Wrapper { Utils.Wrapper {
id: utilities id: utilities
visibilities: root.visibilities
sidebar: sidebar
popouts: popouts
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
popouts: popouts
sidebar: sidebar
visibilities: root.visibilities
} }
Dashboard.Wrapper { Dashboard.Wrapper {
id: dashboard id: dashboard
visibilities: root.visibilities
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
visibilities: root.visibilities
} }
Sidebar.Wrapper { Sidebar.Wrapper {
id: sidebar id: sidebar
visibilities: root.visibilities
panels: root
anchors.top: notifications.bottom
anchors.bottom: utilities.top anchors.bottom: utilities.top
anchors.right: parent.right anchors.right: parent.right
anchors.top: notifications.bottom
panels: root
visibilities: root.visibilities
}
Settings.Wrapper {
id: settings
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
panels: root
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
} }
} }
+212
View File
@@ -0,0 +1,212 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import qs.Daemons
import qs.Components
import qs.Modules
import qs.Modules.Bar
import qs.Config
import qs.Helpers
import qs.Drawers
Variants {
model: Quickshell.screens
Scope {
id: scope
required property var modelData
Exclusions {
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
WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.keyboardFocus: visibilities.dock || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.resources ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
color: "transparent"
contentItem.focus: true
mask: visibilities.isDrawing ? null : region
name: "Bar"
screen: scope.modelData
contentItem.Keys.onEscapePressed: {
if (Config.barConfig.autoHide)
visibilities.bar = false;
visibilities.sidebar = false;
visibilities.dashboard = false;
visibilities.osd = false;
visibilities.settings = false;
visibilities.resources = false;
}
onHasFullscreenChanged: {
visibilities.launcher = false;
visibilities.dashboard = false;
visibilities.osd = false;
visibilities.settings = false;
visibilities.resources = false;
}
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 {
bottom: true
left: true
right: true
top: true
}
Variants {
id: popoutRegions
model: panels.children
Region {
required property Item modelData
height: modelData.height
intersection: Intersection.Subtract
width: modelData.width
x: modelData.x + Config.barConfig.border
y: modelData.y + bar.implicitHeight
}
}
HyprlandFocusGrab {
id: focusGrab
active: visibilities.dock || visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu"))
windows: [win]
onCleared: {
visibilities.launcher = false;
visibilities.sidebar = false;
visibilities.dashboard = false;
visibilities.osd = false;
visibilities.settings = false;
visibilities.resources = false;
visibilities.dock = false;
panels.popouts.hasCurrent = false;
}
}
PersistentProperties {
id: visibilities
property bool bar
property bool dashboard
property bool dock
property bool isDrawing
property bool launcher
property bool notif: NotifServer.popups.length > 0
property bool osd
property bool resources
property bool settings
property bool sidebar
Component.onCompleted: Visibilities.load(scope.modelData, this)
}
Binding {
property: "bar"
target: visibilities
value: visibilities.sidebar || visibilities.dashboard || visibilities.osd || visibilities.notif || visibilities.resources || visibilities.settings || bar.isHovered
when: Config.barConfig.autoHide
}
Item {
anchors.fill: parent
layer.enabled: true
opacity: Appearance.transparency.enabled ? DynamicColors.transparency.base : 1
layer.effect: MultiEffect {
blurMax: 32
shadowColor: Qt.alpha(DynamicColors.palette.m3shadow, 1)
shadowEnabled: true
}
Border {
bar: bar
visibilities: visibilities
}
Backgrounds {
bar: bar
panels: panels
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 {
id: mouseArea
anchors.fill: parent
bar: bar
drawing: drawing
input: input
panels: panels
popouts: panels.popouts
screen: scope.modelData
visibilities: visibilities
z: 1
Panels {
id: panels
bar: bar
drawingItem: drawing
screen: scope.modelData
visibilities: visibilities
}
BarLoader {
id: bar
anchors.left: parent.left
anchors.right: parent.right
popouts: panels.popouts
screen: scope.modelData
visibilities: visibilities
}
}
}
}
}
+26 -23
View File
@@ -2,29 +2,32 @@ import QtQuick
import QtQuick.Effects import QtQuick.Effects
Item { Item {
id: root id: root
property real radius
Rectangle { property real radius
id: shadowRect
anchors.fill: root
radius: root.radius
layer.enabled: true
color: "black"
visible: false
}
MultiEffect { Rectangle {
id: effects id: shadowRect
source: shadowRect
anchors.fill: shadowRect anchors.fill: root
shadowBlur: 2.0 color: "black"
shadowEnabled: true layer.enabled: true
shadowOpacity: 1 radius: root.radius
shadowColor: "black" visible: false
maskSource: shadowRect }
maskEnabled: true
maskInverted: true MultiEffect {
autoPaddingEnabled: true id: effects
}
anchors.fill: shadowRect
autoPaddingEnabled: true
maskEnabled: true
maskInverted: true
maskSource: shadowRect
shadowBlur: 2.0
shadowColor: "black"
shadowEnabled: true
shadowOpacity: 1
source: shadowRect
}
} }
+388
View File
@@ -0,0 +1,388 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Paths
import qs.Components
import qs.Helpers
import qs.Config
ColumnLayout {
id: root
readonly property real centerScale: Math.min(1, screenHeight / 1440)
readonly property int centerWidth: Config.lock.sizes.centerWidth * centerScale
required property var greeter
required property real screenHeight
Layout.fillHeight: true
Layout.fillWidth: false
Layout.preferredWidth: centerWidth
spacing: Appearance.spacing.large * 2
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: Appearance.spacing.small
CustomText {
Layout.alignment: Qt.AlignVCenter
color: DynamicColors.palette.m3secondary
font.bold: true
font.family: Appearance.font.family.clock
font.pointSize: Math.floor(Appearance.font.size.extraLarge * 3 * root.centerScale)
text: Time.hourStr
}
CustomText {
Layout.alignment: Qt.AlignVCenter
color: DynamicColors.palette.m3primary
font.bold: true
font.family: Appearance.font.family.clock
font.pointSize: Math.floor(Appearance.font.size.extraLarge * 3 * root.centerScale)
text: ":"
}
CustomText {
Layout.alignment: Qt.AlignVCenter
color: DynamicColors.palette.m3secondary
font.bold: true
font.family: Appearance.font.family.clock
font.pointSize: Math.floor(Appearance.font.size.extraLarge * 3 * root.centerScale)
text: Time.minuteStr
}
}
CustomText {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: -Appearance.padding.large * 2
color: DynamicColors.palette.m3tertiary
font.bold: true
font.family: Appearance.font.family.mono
font.pointSize: Math.floor(Appearance.font.size.extraLarge * root.centerScale)
text: Time.format("dddd, d MMMM yyyy")
}
CustomClippingRect {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Appearance.spacing.large * 2
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: root.centerWidth / 2
implicitWidth: root.centerWidth / 2
radius: Appearance.rounding.full
MaterialIcon {
anchors.centerIn: parent
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Math.floor(root.centerWidth / 4)
text: "person"
}
CachingImage {
id: pfp
anchors.fill: parent
path: `${Paths.home}/.face`
}
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3onSurfaceVariant
font.family: Appearance.font.family.mono
font.pointSize: Appearance.font.size.normal
font.weight: 600
text: root.greeter.username
visible: text.length > 0
}
CustomRect {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.tPalette.m3surfaceContainer
focus: true
implicitHeight: input.implicitHeight + Appearance.padding.small * 2
implicitWidth: root.centerWidth * 0.8
radius: Appearance.rounding.full
Keys.onPressed: event => {
if (root.greeter.launching)
return;
if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return)
inputField.placeholder.animate = false;
root.greeter.handleKey(event);
}
onActiveFocusChanged: {
if (!activeFocus)
forceActiveFocus();
}
StateLayer {
function onClicked(): void {
parent.forceActiveFocus();
}
cursorShape: Qt.IBeamCursor
hoverEnabled: false
}
RowLayout {
id: input
anchors.fill: parent
anchors.margins: Appearance.padding.small
spacing: Appearance.spacing.normal
Item {
implicitHeight: statusIcon.implicitHeight + Appearance.padding.small * 2
implicitWidth: implicitHeight
MaterialIcon {
id: statusIcon
anchors.centerIn: parent
animate: true
color: root.greeter.errorMessage ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurface
opacity: root.greeter.launching ? 0 : 1
text: {
if (root.greeter.errorMessage)
return "error";
if (root.greeter.awaitingResponse)
return root.greeter.echoResponse ? "person" : "lock";
if (root.greeter.buffer.length > 0)
return "password";
return "login";
}
Behavior on opacity {
Anim {
}
}
}
CircularIndicator {
anchors.fill: parent
running: root.greeter.launching
}
}
InputField {
id: inputField
greeter: root.greeter
}
CustomRect {
color: root.greeter.buffer && !root.greeter.launching ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
implicitHeight: enterIcon.implicitHeight + Appearance.padding.small * 2
implicitWidth: implicitHeight
radius: Appearance.rounding.full
StateLayer {
function onClicked(): void {
root.greeter.submit();
}
color: root.greeter.buffer && !root.greeter.launching ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
}
MaterialIcon {
id: enterIcon
anchors.centerIn: parent
color: root.greeter.buffer && !root.greeter.launching ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
font.weight: 500
text: root.greeter.launching ? "hourglass_top" : "arrow_forward"
}
}
}
}
Item {
Layout.fillWidth: true
Layout.topMargin: -Appearance.spacing.large
implicitHeight: Math.max(message.implicitHeight, stateMessage.implicitHeight)
Behavior on implicitHeight {
Anim {
}
}
CustomText {
id: stateMessage
readonly property string msg: {
if (Hypr.kbLayout !== Hypr.defaultKbLayout) {
if (Hypr.capsLock && Hypr.numLock)
return qsTr("Caps lock and Num lock are ON.\nKeyboard layout: %1").arg(Hypr.kbLayoutFull);
if (Hypr.capsLock)
return qsTr("Caps lock is ON. Kb layout: %1").arg(Hypr.kbLayoutFull);
if (Hypr.numLock)
return qsTr("Num lock is ON. Kb layout: %1").arg(Hypr.kbLayoutFull);
return qsTr("Keyboard layout: %1").arg(Hypr.kbLayoutFull);
}
if (Hypr.capsLock && Hypr.numLock)
return qsTr("Caps lock and Num lock are ON.");
if (Hypr.capsLock)
return qsTr("Caps lock is ON.");
if (Hypr.numLock)
return qsTr("Num lock is ON.");
return "";
}
property bool shouldBeVisible
anchors.left: parent.left
anchors.right: parent.right
animateProp: "opacity"
color: DynamicColors.palette.m3onSurfaceVariant
font.family: Appearance.font.family.mono
horizontalAlignment: Qt.AlignHCenter
lineHeight: 1.2
opacity: shouldBeVisible && !message.msg ? 1 : 0
scale: shouldBeVisible && !message.msg ? 1 : 0.7
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
onMsgChanged: {
if (msg) {
if (opacity > 0) {
animate = true;
text = msg;
animate = false;
} else {
text = msg;
}
shouldBeVisible = true;
} else {
shouldBeVisible = false;
}
}
}
CustomText {
id: message
readonly property bool isError: !!root.greeter.errorMessage
readonly property string msg: {
if (root.greeter.errorMessage)
return root.greeter.errorMessage;
if (root.greeter.launching) {
if (root.greeter.selectedSession && root.greeter.selectedSession.name)
return qsTr("Starting %1...").arg(root.greeter.selectedSession.name);
return qsTr("Starting session...");
}
if (root.greeter.awaitingResponse && root.greeter.promptMessage)
return root.greeter.promptMessage;
return "";
}
anchors.left: parent.left
anchors.right: parent.right
color: isError ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary
font.family: Appearance.font.family.mono
font.pointSize: Appearance.font.size.small
horizontalAlignment: Qt.AlignHCenter
opacity: 0
scale: 0.7
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
onMsgChanged: {
if (msg) {
if (opacity > 0) {
animate = true;
text = msg;
animate = false;
exitAnim.stop();
if (scale < 1)
appearAnim.restart();
else
flashAnim.restart();
} else {
text = msg;
exitAnim.stop();
appearAnim.restart();
}
} else {
appearAnim.stop();
flashAnim.stop();
exitAnim.start();
}
}
Connections {
function onFlashMsg(): void {
exitAnim.stop();
if (message.scale < 1)
appearAnim.restart();
else
flashAnim.restart();
}
target: root.greeter
}
Anim {
id: appearAnim
properties: "scale,opacity"
target: message
to: 1
onFinished: flashAnim.restart()
}
SequentialAnimation {
id: flashAnim
loops: 2
FlashAnim {
to: 0.3
}
FlashAnim {
to: 1
}
}
ParallelAnimation {
id: exitAnim
Anim {
duration: Appearance.anim.durations.large
property: "scale"
target: message
to: 0.7
}
Anim {
duration: Appearance.anim.durations.large
property: "opacity"
target: message
to: 0
}
}
}
}
component FlashAnim: NumberAnimation {
duration: Appearance.anim.durations.small
easing.type: Easing.Linear
property: "opacity"
target: message
}
}
+8
View File
@@ -0,0 +1,8 @@
import QtQuick
import qs.Config
NumberAnimation {
duration: MaterialEasing.standardTime
easing.bezierCurve: MaterialEasing.standard
easing.type: Easing.BezierSpline
}
@@ -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;
}
}
}
+8
View File
@@ -0,0 +1,8 @@
import QtQuick
import qs.Config
ColorAnimation {
duration: MaterialEasing.standardTime
easing.bezierCurve: MaterialEasing.standard
easing.type: Easing.BezierSpline
}
+102
View File
@@ -0,0 +1,102 @@
import qs.Config
import ZShell.Internal
import QtQuick
import QtQuick.Templates
BusyIndicator {
id: root
enum AnimState {
Stopped,
Running,
Completing
}
enum AnimType {
Advance = 0,
Retreat
}
property int animState
property color bgColour: DynamicColors.palette.m3secondaryContainer
property color fgColour: DynamicColors.palette.m3primary
property real implicitSize: Appearance.font.size.normal * 3
property real internalStrokeWidth: strokeWidth
readonly property alias progress: manager.progress
property real strokeWidth: Appearance.padding.small * 0.8
property alias type: manager.indeterminateAnimationType
implicitHeight: implicitSize
implicitWidth: implicitSize
padding: 0
contentItem: CircularProgress {
anchors.fill: parent
bgColour: root.bgColour
fgColour: root.fgColour
padding: root.padding
rotation: manager.rotation
startAngle: manager.startFraction * 360
strokeWidth: root.internalStrokeWidth
value: manager.endFraction - manager.startFraction
}
states: State {
name: "stopped"
when: !root.running
PropertyChanges {
root.internalStrokeWidth: root.strokeWidth / 3
root.opacity: 0
}
}
transitions: Transition {
Anim {
duration: manager.completeEndDuration * Appearance.anim.durations.scale
properties: "opacity,internalStrokeWidth"
}
}
Component.onCompleted: {
if (running) {
running = false;
running = true;
}
}
onRunningChanged: {
if (running) {
manager.completeEndProgress = 0;
animState = CircularIndicator.Running;
} else {
if (animState == CircularIndicator.Running)
animState = CircularIndicator.Completing;
}
}
CircularIndicatorManager {
id: manager
}
NumberAnimation {
duration: manager.duration * Appearance.anim.durations.scale
from: 0
loops: Animation.Infinite
property: "progress"
running: root.animState !== CircularIndicator.Stopped
target: manager
to: 1
}
NumberAnimation {
duration: manager.completeEndDuration * Appearance.anim.durations.scale
from: 0
property: "completeEndProgress"
running: root.animState === CircularIndicator.Completing
target: manager
to: 1
onFinished: {
if (root.animState === CircularIndicator.Completing)
root.animState = CircularIndicator.Stopped;
}
}
}
+66
View File
@@ -0,0 +1,66 @@
import QtQuick
import QtQuick.Shapes
import qs.Config
Shape {
id: root
readonly property real arcRadius: (size - padding - strokeWidth) / 2
property color bgColour: DynamicColors.palette.m3secondaryContainer
property color fgColour: DynamicColors.palette.m3primary
readonly property real gapAngle: ((spacing + strokeWidth) / (arcRadius || 1)) * (180 / Math.PI)
property int padding: 0
readonly property real size: Math.min(width, height)
property int spacing: Appearance.spacing.small
property int startAngle: -90
property int strokeWidth: Appearance.padding.smaller
readonly property real vValue: value || 1 / 360
property real value
asynchronous: true
preferredRendererType: Shape.CurveRenderer
ShapePath {
capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
fillColor: "transparent"
strokeColor: root.bgColour
strokeWidth: root.strokeWidth
Behavior on strokeColor {
CAnim {
duration: Appearance.anim.durations.large
}
}
PathAngleArc {
centerX: root.size / 2
centerY: root.size / 2
radiusX: root.arcRadius
radiusY: root.arcRadius
startAngle: root.startAngle + 360 * root.vValue + root.gapAngle
sweepAngle: Math.max(-root.gapAngle, 360 * (1 - root.vValue) - root.gapAngle * 2)
}
}
ShapePath {
capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
fillColor: "transparent"
strokeColor: root.fgColour
strokeWidth: root.strokeWidth
Behavior on strokeColor {
CAnim {
duration: Appearance.anim.durations.large
}
}
PathAngleArc {
centerX: root.size / 2
centerY: root.size / 2
radiusX: root.arcRadius
radiusY: root.arcRadius
startAngle: root.startAngle
sweepAngle: 360 * root.vValue
}
}
}
+135
View File
@@ -0,0 +1,135 @@
import QtQuick
import QtQuick.Layouts
import qs.Config
ColumnLayout {
id: root
default property alias content: contentColumn.data
property string description: ""
property bool expanded: false
property bool nested: false
property bool showBackground: false
required property string title
signal toggleRequested
Layout.fillWidth: true
spacing: Appearance.spacing.small
Item {
id: sectionHeaderItem
Layout.fillWidth: true
Layout.preferredHeight: Math.max(titleRow.implicitHeight + Appearance.padding.normal * 2, 48)
RowLayout {
id: titleRow
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.normal
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.normal
anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.normal
CustomText {
font.pointSize: Appearance.font.size.larger
font.weight: 500
text: root.title
}
Item {
Layout.fillWidth: true
}
MaterialIcon {
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.normal
rotation: root.expanded ? 180 : 0
text: "expand_more"
Behavior on rotation {
Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.standard
}
}
}
}
StateLayer {
function onClicked(): void {
root.toggleRequested();
root.expanded = !root.expanded;
}
anchors.fill: parent
color: DynamicColors.palette.m3onSurface
radius: Appearance.rounding.normal
showHoverBackground: false
}
}
Item {
id: contentWrapper
Layout.fillWidth: true
Layout.preferredHeight: root.expanded ? (contentColumn.implicitHeight + Appearance.spacing.small * 2) : 0
clip: true
Behavior on Layout.preferredHeight {
Anim {
easing.bezierCurve: Appearance.anim.curves.standard
}
}
CustomRect {
id: backgroundRect
anchors.fill: parent
color: DynamicColors.transparency.enabled ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, root.nested ? 3 : 2) : (root.nested ? DynamicColors.palette.m3surfaceContainerHigh : DynamicColors.palette.m3surfaceContainer)
opacity: root.showBackground && root.expanded ? 1.0 : 0.0
radius: Appearance.rounding.normal
visible: root.showBackground
Behavior on opacity {
Anim {
easing.bezierCurve: Appearance.anim.curves.standard
}
}
}
ColumnLayout {
id: contentColumn
anchors.bottomMargin: Appearance.spacing.small
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.normal
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.normal
opacity: root.expanded ? 1.0 : 0.0
spacing: Appearance.spacing.small
y: Appearance.spacing.small
Behavior on opacity {
Anim {
easing.bezierCurve: Appearance.anim.curves.standard
}
}
CustomText {
id: descriptionText
Layout.bottomMargin: root.description !== "" ? Appearance.spacing.small : 0
Layout.fillWidth: true
Layout.topMargin: root.description !== "" ? Appearance.spacing.smaller : 0
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.small
text: root.description
visible: root.description !== ""
wrapMode: Text.Wrap
}
}
}
}
+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;
}
}
}
+34
View File
@@ -0,0 +1,34 @@
pragma ComponentBehavior: Bound
import ZShell
import Quickshell.Widgets
import QtQuick
IconImage {
id: root
required property color color
asynchronous: true
layer.enabled: true
layer.effect: Coloriser {
colorizationColor: root.color
sourceColor: analyser.dominantColour
}
layer.onEnabledChanged: {
if (layer.enabled && status === Image.Ready)
analyser.requestUpdate();
}
onStatusChanged: {
if (layer.enabled && status === Image.Ready)
analyser.requestUpdate();
}
ImageAnalyser {
id: analyser
sourceItem: root
}
}
+14
View File
@@ -0,0 +1,14 @@
import QtQuick
import QtQuick.Effects
MultiEffect {
property color sourceColor: "black"
brightness: 1 - sourceColor.hslLightness
colorization: 1
Behavior on colorizationColor {
CAnim {
}
}
}
+70
View File
@@ -0,0 +1,70 @@
import QtQuick
import QtQuick.Templates
import qs.Config
Slider {
id: root
property color nonPeakColor: DynamicColors.tPalette.m3primary
required property real peak
property color peakColor: DynamicColors.palette.m3primary
background: Item {
CustomRect {
anchors.bottom: parent.bottom
anchors.bottomMargin: root.implicitHeight / 3
anchors.left: parent.left
anchors.top: parent.top
anchors.topMargin: root.implicitHeight / 3
bottomRightRadius: root.implicitHeight / 15
color: root.nonPeakColor
implicitWidth: root.handle.x - root.implicitHeight
radius: 1000
topRightRadius: root.implicitHeight / 15
CustomRect {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.top: parent.top
bottomRightRadius: root.implicitHeight / 15
color: root.peakColor
implicitWidth: parent.width * root.peak
radius: 1000
topRightRadius: root.implicitHeight / 15
Behavior on implicitWidth {
Anim {
duration: 50
}
}
}
}
CustomRect {
anchors.bottom: parent.bottom
anchors.bottomMargin: root.implicitHeight / 3
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: root.implicitHeight / 3
bottomLeftRadius: root.implicitHeight / 15
color: DynamicColors.tPalette.m3surfaceContainer
implicitWidth: root.implicitWidth - root.handle.x - root.handle.implicitWidth - root.implicitHeight
radius: 1000
topLeftRadius: root.implicitHeight / 15
}
}
handle: CustomRect {
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3primary
implicitHeight: 15
implicitWidth: 5
radius: 1000
x: root.visualPosition * root.availableWidth - implicitWidth / 2
MouseArea {
acceptedButtons: Qt.NoButton
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
}
}
}

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