16 Commits
Author SHA1 Message Date
zach 77ec1c87f8 Merge branch 'main' into feat/settings-revamp
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 16s
Python / test (pull_request) Successful in 29s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m8s
2026-06-29 18:59:24 +02:00
zach f883b3d7fe versioning 2026-06-29 18:57:32 +02:00
zach e685500698 revert certain changes for blobs
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 19s
Python / test (pull_request) Successful in 30s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m14s
2026-06-28 22:49:15 +02:00
zach 04481ae7e1 sidebar menu on top
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 16s
Python / test (pull_request) Successful in 28s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m8s
2026-06-27 00:11:18 +02:00
zach 82a660229b remove network-dev folder
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 17s
Python / test (pull_request) Successful in 28s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m7s
2026-06-26 17:25:25 +02:00
zach ce6e314fde chore: regions now manually set, moved to separate file
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 17s
Python / test (pull_request) Successful in 31s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m8s
2026-06-26 17:23:56 +02:00
zach 0b92cbb3b4 add corner fill
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 9s
Python / lint-format (pull_request) Successful in 16s
Python / test (pull_request) Successful in 27s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m8s
2026-06-25 18:58:21 +02:00
zach 6a15be7b15 sys tray spacing
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 16s
Python / test (pull_request) Successful in 29s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m17s
2026-06-25 12:57:11 +02:00
zach 01c63ebead blobpopup positioning and radius changes
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 19s
Python / test (pull_request) Successful in 29s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m43s
2026-06-24 23:06:29 +02:00
zach ecfa1115cd wonky fix for cropping rect desync
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 14s
Python / lint-format (pull_request) Successful in 18s
Python / test (pull_request) Successful in 28s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m11s
2026-06-24 20:21:36 +02:00
zach d08b2b7ac6 correct index for subpages
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 20s
Python / test (pull_request) Successful in 28s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m7s
2026-06-24 15:42:50 +02:00
zach 4df31d7564 added services page + rename barConfig json property to bar, migration helper function
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 16s
Python / test (pull_request) Successful in 29s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m6s
2026-06-24 15:30:33 +02:00
zach 5c6882d2a8 added panels page
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 9s
Python / lint-format (pull_request) Successful in 28s
Python / test (pull_request) Successful in 28s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m6s
2026-06-23 23:06:30 +02:00
zach 6a80961b2b added audio and apps page in settings
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 18s
Python / test (pull_request) Successful in 28s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m6s
2026-06-23 22:32:56 +02:00
zach 2366901b24 Merge branch 'main' into feat/settings-revamp
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 18s
Python / lint-format (pull_request) Successful in 29s
Python / test (pull_request) Successful in 55s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m24s
2026-06-23 16:24:01 +02:00
zach ea4d5376f6 initial commit for settings revamp
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 15s
Python / lint-format (pull_request) Successful in 29s
Python / test (pull_request) Successful in 54s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m34s
2026-06-23 16:22:56 +02:00
108 changed files with 7323 additions and 723 deletions
+37 -8
View File
@@ -1,6 +1,35 @@
cmake_minimum_required(VERSION 3.19)
project(ZShell)
if(NOT DEFINED VERSION)
execute_process(
COMMAND git describe --tags --abbrev=0
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_LAST_TAG
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
if (GIT_LAST_TAG)
string(REGEX REPLACE "^v" "" GIT_LAST_TAG "${GIT_LAST_TAG}")
string(REPLACE "." ";" VERSION_LIST "${GIT_LAST_TAG}")
list(GET VERSION_LIST 0 VERSION_MAJOR)
list(GET VERSION_LIST 1 VERSION_MINOR)
execute_process(
COMMAND git rev-list v${VERSION_MAJOR}.${VERSION_MINOR}.0..HEAD --count
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE VERSION_PATCH
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
endif()
endif()
set(PROJECT_VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
project(ZShell VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH})
message(STATUS "ZShell version: ${PROJECT_VERSION}")
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -15,11 +44,11 @@ set(INSTALL_QSCONFDIR "etc/xdg/quickshell/zshell" CACHE STRING "Quickshell confi
set(INSTALL_GREETERCONFDIR "etc/xdg/quickshell/zshell-greeter" CACHE STRING "Quickshell greeter install dir")
add_compile_options(
-Wall -Wextra -Wpedantic -Wshadow -Wconversion
-Wold-style-cast -Wnull-dereference -Wdouble-promotion
-Wformat=2 -Wfloat-equal -Woverloaded-virtual
-Wsign-conversion -Wredundant-decls -Wswitch
-Wunreachable-code
-Wall -Wextra -Wpedantic -Wshadow -Wconversion
-Wold-style-cast -Wnull-dereference -Wdouble-promotion
-Wformat=2 -Wfloat-equal -Woverloaded-virtual
-Wsign-conversion -Wredundant-decls -Wswitch
-Wunreachable-code
)
@@ -30,8 +59,8 @@ endif()
if("shell" IN_LIST ENABLE_MODULES)
foreach(dir assets scripts Components Config Modules Daemons Drawers Effects Helpers Paths)
install(DIRECTORY ${dir} DESTINATION "${INSTALL_QSCONFDIR}")
endforeach()
install(DIRECTORY ${dir} DESTINATION "${INSTALL_QSCONFDIR}")
endforeach()
# Disable watching for changes
file(READ shell.qml SHELL_QML)
+18
View File
@@ -4,12 +4,30 @@ import qs.Helpers
Flickable {
id: root
property bool doneFakeFlick
interactive: !Visibilities.getForActive().isDrawing
maximumFlickVelocity: 3000
rebound: Transition {
onRunningChanged: {
if (!running && !root.doneFakeFlick) {
root.doneFakeFlick = true;
root.flick(1, 1);
root.flick(-1, -1);
Qt.callLater(() => root.cancelFlick());
}
}
Anim {
properties: "x,y"
}
}
Timer {
interval: 10
running: root.doneFakeFlick
onTriggered: root.doneFakeFlick = false
}
}
+16 -4
View File
@@ -87,9 +87,15 @@ RowLayout {
id: upButton
color: root.enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 1)
implicitHeight: upIcon.implicitHeight + Appearance.padding.small * 2
implicitHeight: upIcon.implicitHeight + Appearance.padding.extraSmall * 2
implicitWidth: implicitHeight
radius: Appearance.rounding.full
radius: upState.pressed ? Appearance.rounding.small : Appearance.rounding.normal
Behavior on radius {
Anim {
type: Anim.FastEffects
}
}
StateLayer {
id: upState
@@ -120,9 +126,15 @@ RowLayout {
CustomRect {
color: root.enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 1)
implicitHeight: downIcon.implicitHeight + Appearance.padding.small * 2
implicitHeight: downIcon.implicitHeight + Appearance.padding.extraSmall * 2
implicitWidth: implicitHeight
radius: Appearance.rounding.full
radius: downState.pressed ? Appearance.rounding.small : Appearance.rounding.normal
Behavior on radius {
Anim {
type: Anim.FastEffects
}
}
StateLayer {
id: downState
+79
View File
@@ -0,0 +1,79 @@
import QtQuick
import Quickshell
import qs.Config
Image {
id: root
property int fadeInAnim: Anim.DefaultEffects
property int fadeInLargeAnim: Anim.StandardLarge
property int fadeOutAnim: Anim.FastEffects
property bool fadingOut
property bool hadPrevious
property bool preventInit
function maybeStartInAnim(): void {
if (!preventInit && !opacityInAnim.running && status === Image.Ready) {
opacityInAnim.type = hadPrevious ? fadeInAnim : fadeInLargeAnim;
opacityInAnim.start();
}
}
asynchronous: true
fillMode: Image.PreserveAspectCrop
opacity: 0
retainWhileLoading: true
sourceSize: {
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
return Qt.size(width * dpr, height * dpr);
}
Anim on opacity {
id: opacityInAnim
running: false
to: 1
}
Behavior on source {
SequentialAnimation {
ScriptAction {
script: opacityInAnim.stop()
}
PropertyAction {
property: "fadingOut"
target: root
value: true
}
Anim {
property: "opacity"
target: root
to: 0
type: root.fadeOutAnim
}
PropertyAction {
property: "fadingOut"
target: root
value: false
}
PropertyAction {
property: "hadPrevious"
target: root
value: root.source
}
PropertyAction {
}
ScriptAction {
script: root.maybeStartInAnim()
}
}
}
onPreventInitChanged: maybeStartInAnim()
onStatusChanged: maybeStartInAnim()
}
+1
View File
@@ -63,6 +63,7 @@ ButtonBase {
id: label
Layout.alignment: Qt.AlignVCenter
Layout.topMargin: -1
color: root.onColor
font: root.font
}
+309
View File
@@ -0,0 +1,309 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.Config
Item {
id: root
enum Variant {
Outlined,
Filled
}
readonly property color accent: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : (focused ? DynamicColors.palette.m3primary : DynamicColors.palette.m3outline))
readonly property real disabledOpacity: 0.38
readonly property string effectiveTrailingIcon: root.trailingIcon.length > 0 ? root.trailingIcon : (root.password ? (field.echoMode === TextInput.Password ? "visibility" : "visibility_off") : (root.isError ? "error" : ""))
property string errorText: ""
readonly property alias field: field
readonly property bool floating: focused || hasContent || prefixText.length > 0
readonly property bool focused: field.activeFocus
readonly property bool hasContent: field.text.length > 0
readonly property color iconColor: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : (focused ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant))
property int inputMethodHints: Qt.ImhNone
property bool isError: false
property string label: ""
readonly property color labelColor: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : (focused ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant))
property string leadingIcon: ""
property int maxLength: 0
property color notchColor: DynamicColors.palette.m3surface
readonly property bool outlined: variant === M3TextField.Variant.Outlined
property bool password: false
property string placeholder: ""
property string prefixText: ""
property string suffixText: ""
readonly property color supportColor: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurfaceVariant)
property string supportingText: ""
property alias text: field.text
property string trailingIcon: ""
readonly property bool trailingInteractive: root.password && root.trailingIcon.length === 0
property var validator: null
property int variant: M3TextField.Variant.Outlined
signal accepted
function forceFieldFocus(): void {
field.forceActiveFocus();
}
implicitHeight: 56 + (supportRow.visible ? supportRow.implicitHeight + Appearance.spacing.extraSmall : 0)
implicitWidth: 240
CustomRect {
id: filledBg
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
color: root.enabled ? (root.focused ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 1) : DynamicColors.palette.m3surfaceContainerHigh) : DynamicColors.palette.m3onSurface
implicitHeight: 56
opacity: root.enabled ? 1 : 0.04 // M3 filled disabled container @ 4%
topLeftRadius: Appearance.rounding.small
topRightRadius: Appearance.rounding.small
visible: !root.outlined
Behavior on color {
CAnim {
}
}
StateLayer {
anchors.fill: parent
color: root.isError ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurface
enabled: root.enabled
topLeftRadius: Appearance.rounding.small
topRightRadius: Appearance.rounding.small
onClicked: field.forceActiveFocus()
}
CustomRect {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
color: root.accent
implicitHeight: root.focused || root.isError ? 2 : 1
opacity: root.enabled ? 1 : root.disabledOpacity
Behavior on color {
CAnim {
}
}
Behavior on implicitHeight {
Anim {
type: Anim.StandardSmall
}
}
}
}
CustomRect {
id: outline
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
border.color: root.accent
border.width: root.focused || root.isError ? 2 : 1
color: "transparent"
implicitHeight: 56
opacity: root.enabled ? 1 : root.disabledOpacity
radius: Appearance.rounding.small
visible: root.outlined
Behavior on border.color {
CAnim {
}
}
Behavior on border.width {
Anim {
type: Anim.StandardSmall
}
}
StateLayer {
anchors.fill: parent
anchors.margins: outline.border.width
color: root.isError ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurface
enabled: root.enabled
radius: Appearance.rounding.small
onClicked: field.forceActiveFocus()
}
}
Rectangle {
color: root.notchColor
height: outline.border.width
opacity: root.enabled ? 1 : root.disabledOpacity
visible: root.outlined && root.floating && root.label.length > 0
width: labelText.width + Appearance.spacing.extraSmall
x: labelText.x - Appearance.spacing.extraSmall / 2
y: 0
}
CustomText {
id: labelText
color: root.labelColor
font.pointSize: root.floating ? Appearance.font.size.small : Appearance.font.size.medium
opacity: root.enabled ? 1 : root.disabledOpacity
text: root.label
visible: root.label.length > 0
x: root.floating ? (Appearance.padding.larger + Appearance.spacing.extraSmall / 2) : (leading.visible ? leading.x + leading.width + Appearance.spacing.small : Appearance.padding.larger)
y: root.floating ? (root.outlined ? -height / 2 : Appearance.padding.extraSmall) : (56 - height) / 2
Behavior on color {
CAnim {
}
}
Behavior on x {
Anim {
type: Anim.StandardSmall
}
}
Behavior on y {
Anim {
type: Anim.StandardSmall
}
}
}
MaterialIcon {
id: leading
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.larger
anchors.top: parent.top
anchors.topMargin: (56 - height) / 2
color: root.iconColor
font.pointSize: Appearance.font.size.medium
opacity: root.enabled ? 1 : root.disabledOpacity
text: root.leadingIcon
visible: root.leadingIcon.length > 0
Behavior on color {
CAnim {
}
}
}
MaterialIcon {
id: trailing
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.larger
anchors.top: parent.top
anchors.topMargin: (56 - height) / 2
color: root.isError ? DynamicColors.palette.m3error : (root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurface)
font.pointSize: Appearance.font.size.medium
opacity: root.enabled ? 1 : root.disabledOpacity
text: root.effectiveTrailingIcon
visible: root.effectiveTrailingIcon.length > 0
MouseArea {
anchors.fill: parent
anchors.margins: -Appearance.padding.small
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: root.trailingInteractive && root.enabled
onClicked: field.echoMode = field.echoMode === TextInput.Password ? TextInput.Normal : TextInput.Password
}
}
CustomText {
id: prefix
anchors.left: leading.visible ? leading.right : parent.left
anchors.leftMargin: leading.visible ? Appearance.spacing.small : Appearance.padding.larger
anchors.top: parent.top
anchors.topMargin: root.floating && !root.outlined ? Appearance.font.size.small + Appearance.padding.extraSmall : 0
color: root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurface
font.pointSize: Appearance.font.size.medium
height: 56
opacity: root.enabled ? 1 : root.disabledOpacity
text: root.prefixText
verticalAlignment: Text.AlignVCenter
visible: root.prefixText.length > 0 && root.floating
}
CustomText {
id: suffix
anchors.right: trailing.visible ? trailing.left : parent.right
anchors.rightMargin: trailing.visible ? Appearance.spacing.small : Appearance.padding.larger
anchors.top: parent.top
anchors.topMargin: root.floating && !root.outlined ? Appearance.font.size.small + Appearance.padding.extraSmall : 0
color: root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurface
font.pointSize: Appearance.font.size.medium
height: 56
opacity: root.enabled ? 1 : root.disabledOpacity
text: root.suffixText
verticalAlignment: Text.AlignVCenter
visible: root.suffixText.length > 0 && root.floating
}
CustomTextField {
id: field
anchors.left: prefix.visible ? prefix.right : (leading.visible ? leading.right : parent.left)
anchors.leftMargin: prefix.visible ? Appearance.spacing.extraSmall : (leading.visible ? Appearance.spacing.small : Appearance.padding.larger)
anchors.right: suffix.visible ? suffix.left : (trailing.visible ? trailing.left : parent.right)
anchors.rightMargin: suffix.visible ? Appearance.spacing.extraSmall : (trailing.visible ? Appearance.spacing.small : Appearance.padding.larger)
anchors.top: parent.top
color: DynamicColors.palette.m3onSurface
echoMode: root.password ? TextInput.Password : TextInput.Normal
enabled: root.enabled
font.pointSize: Appearance.font.size.medium
height: 56
inputMethodHints: root.inputMethodHints
maximumLength: root.maxLength > 0 ? root.maxLength : 32767
opacity: root.enabled ? 1 : root.disabledOpacity
placeholderText: root.focused ? root.placeholder : ""
topPadding: root.floating && !root.outlined ? Appearance.font.size.small + Appearance.padding.extraSmall : 0
validator: root.validator
verticalAlignment: TextInput.AlignVCenter
onAccepted: root.accepted()
onTextEdited: if (root.isError)
root.isError = false
}
Item {
id: supportRow
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: 56 + Appearance.spacing.extraSmall
implicitHeight: Math.max(supportText.implicitHeight, counter.implicitHeight)
visible: (root.isError && root.errorText.length > 0) || root.supportingText.length > 0 || root.maxLength > 0
CustomText {
id: supportText
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.larger
anchors.right: counter.visible ? counter.left : parent.right
anchors.rightMargin: Appearance.spacing.small
color: root.supportColor
font.pointSize: Appearance.font.size.small
opacity: root.enabled ? 1 : root.disabledOpacity
text: root.isError && root.errorText.length > 0 ? root.errorText : root.supportingText
wrapMode: Text.WordWrap
}
CustomText {
id: counter
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.larger
color: root.supportColor
font.pointSize: Appearance.font.size.small
opacity: root.enabled ? 1 : root.disabledOpacity
text: `${root.text.length}/${root.maxLength}`
visible: root.maxLength > 0
}
}
}
+2 -1
View File
@@ -20,6 +20,7 @@ MouseArea {
return (Math.sqrt(Math.max(d1, d2, d3, d4)) + (shapeMorph ? 24 : 0)) * 1.3;
}
property real endRadiusAtPress
property bool manualHoverOverride
property bool manualPressOverride
property real pressX: width / 2
property real pressY: height / 2
@@ -27,7 +28,7 @@ MouseArea {
readonly property alias rect: base
property bool shapeMorph
property bool showHoverBackground: true
property real stateOpacity: containsMouse ? 0.08 : 0
property real stateOpacity: containsMouse || manualHoverOverride ? 0.08 : 0
property alias topLeftRadius: base.topLeftRadius
property alias topRightRadius: base.topRightRadius
+71
View File
@@ -0,0 +1,71 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.Effects
CustomFlickable {
id: root
property real bottomFadeOpacity: fadeShouldBeActive(false) ? 0 : 1
property real fadeAmount: 0.1
property real topFadeOpacity: fadeShouldBeActive(true) ? 0 : 1
function fadeShouldBeActive(isStart: bool): bool {
// When content is smaller than flickable size, hide fade when rebound starts
if (contentHeight + topMargin + bottomMargin < height && rebound.running && ((isStart ? verticalOvershoot > 0 : verticalOvershoot < 0)))
return false;
if (isStart)
return visibleArea.yPosition > 0;
return visibleArea.yPosition + visibleArea.heightRatio < 1;
}
flickableDirection: Flickable.VerticalFlick
layer.enabled: true
Behavior on bottomFadeOpacity {
Anim {
type: Anim.SlowEffects
}
}
layer.effect: Mask {
maskSource: mask
Rectangle {
id: mask
anchors.fill: parent
layer.enabled: true
visible: false
gradient: Gradient {
orientation: Gradient.Vertical
GradientStop {
color: Qt.rgba(0, 0, 0, root.topFadeOpacity)
position: 0
}
GradientStop {
color: Qt.rgba(0, 0, 0, 1)
position: root.fadeAmount
}
GradientStop {
color: Qt.rgba(0, 0, 0, 1)
position: 1 - root.fadeAmount
}
GradientStop {
color: Qt.rgba(0, 0, 0, root.bottomFadeOpacity)
position: 1
}
}
}
}
Behavior on topFadeOpacity {
Anim {
type: Anim.SlowEffects
}
}
}
+72
View File
@@ -0,0 +1,72 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.Effects
CustomListView {
id: root
property real bottomFadeOpacity: fadeShouldBeActive(false) ? 0 : 1
property real fadeAmount: 0.1
property real topFadeOpacity: fadeShouldBeActive(true) ? 0 : 1
function fadeShouldBeActive(isStart: bool): bool {
// When content is smaller than flickable size, hide fade when rebound starts
if (contentHeight + topMargin + bottomMargin < height && rebound.running && ((isStart ? verticalOvershoot > 0 : verticalOvershoot < 0)))
return false;
if (isStart)
return visibleArea.yPosition > 0;
return visibleArea.yPosition + visibleArea.heightRatio < 1;
}
flickableDirection: Flickable.VerticalFlick
layer.enabled: true
orientation: ListView.Vertical
Behavior on bottomFadeOpacity {
Anim {
type: Anim.SlowEffects
}
}
layer.effect: Mask {
maskSource: mask
Rectangle {
id: mask
anchors.fill: parent
layer.enabled: true
visible: false
gradient: Gradient {
orientation: Gradient.Vertical
GradientStop {
color: Qt.rgba(0, 0, 0, root.topFadeOpacity)
position: 0
}
GradientStop {
color: Qt.rgba(0, 0, 0, 1)
position: root.fadeAmount
}
GradientStop {
color: Qt.rgba(0, 0, 0, 1)
position: 1 - root.fadeAmount
}
GradientStop {
color: Qt.rgba(0, 0, 0, root.bottomFadeOpacity)
position: 1
}
}
}
}
Behavior on topFadeOpacity {
Anim {
type: Anim.SlowEffects
}
}
}
+6
View File
@@ -72,6 +72,12 @@ JsonObject {
property bool upower: true
}
component Tray: JsonObject {
property bool showAudio: true
property bool showBluetooth: false
property bool showMicrophone: true
property bool showNetwork: false
property bool showPower: true
property bool showWifi: false
property int trayIconSize: 24
}
}
+48 -359
View File
@@ -12,7 +12,7 @@ Singleton {
property alias appearance: adapter.appearance
property alias background: adapter.background
property alias barConfig: adapter.barConfig
property alias bar: adapter.bar
property alias clipboard: adapter.clipboard
property alias colors: adapter.colors
property alias dashboard: adapter.dashboard
@@ -20,6 +20,7 @@ Singleton {
property alias general: adapter.general
property alias launcher: adapter.launcher
property alias lock: adapter.lock
property bool migratingLegacyBar: false
property alias notifs: adapter.notifs
property alias osd: adapter.osd
property alias overview: adapter.overview
@@ -29,6 +30,41 @@ Singleton {
property alias sidebar: adapter.sidebar
property alias utilities: adapter.utilities
function applyLegacyBarConfig(legacy): void {
const tray = legacy.tray ?? {};
const popouts = legacy.popouts ?? {};
bar.autoHide = pick(legacy.autoHide, bar.autoHide);
bar.hideWhenNotif = pick(legacy.hideWhenNotif, bar.hideWhenNotif);
bar.rounding = pick(legacy.rounding, bar.rounding);
bar.border = pick(legacy.border, bar.border);
bar.smoothing = pick(legacy.smoothing, bar.smoothing);
bar.height = pick(legacy.height, bar.height);
bar.tray.trayIconSize = pick(tray.trayIconSize, bar.tray.trayIconSize);
bar.popouts.tray = pick(popouts.tray, bar.popouts.tray);
bar.popouts.audio = pick(popouts.audio, bar.popouts.audio);
bar.popouts.activeWindow = pick(popouts.activeWindow, bar.popouts.activeWindow);
bar.popouts.resources = pick(popouts.resources, bar.popouts.resources);
bar.popouts.clock = pick(popouts.clock, bar.popouts.clock);
bar.popouts.network = pick(popouts.network, bar.popouts.network);
bar.popouts.upower = pick(popouts.upower, bar.popouts.upower);
bar.entries = pick(legacy.entries, bar.entries);
}
function migrateLegacyBarConfig(raw): bool {
if (raw.bar !== undefined || raw.barConfig === undefined)
return false;
migratingLegacyBar = true;
applyLegacyBarConfig(raw.barConfig);
migratingLegacyBar = false;
return true;
}
function pick(value, fallback) {
return value === undefined ? fallback : value;
}
function save(): void {
saveTimer.restart();
recentlySaved = true;
@@ -39,348 +75,6 @@ Singleton {
saveTimer.restart();
}
function serializeAppearance(): var {
return {
rounding: {
scale: appearance.rounding.scale
},
spacing: {
scale: appearance.spacing.scale
},
padding: {
scale: appearance.padding.scale
},
deform: {
scale: appearance.deform.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,
alignX: background.alignX,
sourceClipX: background.sourceClipX,
sourceClipY: background.sourceClipY,
sourceClipW: background.sourceClipW,
sourceClipH: background.sourceClipH,
alignY: background.alignY,
zoom: background.zoom
};
}
function serializeBar(): var {
return {
autoHide: barConfig.autoHide,
hideWhenNotif: barConfig.hideWhenNotif,
rounding: barConfig.rounding,
border: barConfig.border,
smoothing: barConfig.smoothing,
height: barConfig.height,
tray: {
trayIconSize: barConfig.tray.trayIconSize
},
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 serializeClipboard(): var {
return {
enabled: clipboard.enabled,
maxEntriesShown: clipboard.maxEntriesShown,
sizes: {
width: clipboard.sizes.width,
previewWidth: clipboard.sizes.previewWidth,
minPreviewWidth: clipboard.sizes.minPreviewWidth,
itemHeight: clipboard.sizes.itemHeight
}
};
}
function serializeColors(): var {
return {
schemeType: colors.schemeType,
presets: {
name: colors.presets.name,
variant: colors.presets.variant,
accent: colors.presets.accent
}
};
}
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(),
screenshot: serializeScreenshot(),
clipboard: serializeClipboard()
};
}
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,
mediaVisualizerSize: dashboard.sizes.mediaVisualizerSize,
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,
showOverFullscreen: general.showOverFullscreen,
desktopIcons: general.desktopIcons,
dateFormat: general.dateFormat,
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
},
battery: {
popupThresholds: general.battery.popupThresholds,
critPerc: general.battery.critPerc
}
};
}
function serializeLauncher(): var {
return {
maxAppsShown: launcher.maxAppsShown,
maxWallpapers: launcher.maxWallpapers,
uwsm: launcher.uwsm,
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,
showNotifContent: lock.showNotifContent,
showNotifIcon: lock.showNotifIcon,
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 serializeScreenshot(): var {
return {
enable_pp: screenshot.enable_pp,
mode: screenshot.mode,
radius: screenshot.radius,
shadow: screenshot.shadow,
rounding: screenshot.rounding,
shadow_blur: screenshot.shadow_blur,
shadow_color: screenshot.shadow_color,
shadow_offset_x: screenshot.shadow_offset_x,
shadow_offset_y: screenshot.shadow_offset_y
};
}
function serializeServices(): var {
return {
weatherLocation: services.weatherLocation,
updates: services.updates,
useFahrenheit: services.useFahrenheit,
minBrightness: services.minBrightness,
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 {
id: timer
}
@@ -392,20 +86,7 @@ Singleton {
onTriggered: {
timer.restart();
try {
let config = {};
try {
config = JSON.parse(fileView.text());
} catch (e) {
config = {};
}
config = root.serializeConfig();
fileView.setText(JSON.stringify(config, null, 4));
} catch (e) {
Toaster.toast(qsTr("Failed to serialize config"), e.message, "settings_alert", Toast.Error);
}
fileView.writeAdapter();
}
}
@@ -425,6 +106,10 @@ Singleton {
path: `${Paths.config}/config.json`
watchChanges: true
onAdapterUpdated: {
if (!root.migratingLegacyBar)
root.save();
}
onFileChanged: {
if (!root.recentlySaved) {
timer.restart();
@@ -441,7 +126,11 @@ Singleton {
ModeScheduler.checkStartup();
Hyprsunset.checkStartup();
try {
JSON.parse(text());
const raw = JSON.parse(text());
const migrated = root.migrateLegacyBarConfig(raw);
if (migrated)
root.save();
const elapsed = timer.elapsedMs();
if (adapter.utilities.toasts.configLoaded && !root.recentlySaved) {
@@ -462,7 +151,7 @@ Singleton {
}
property BackgroundConfig background: BackgroundConfig {
}
property BarConfig barConfig: BarConfig {
property BarConfig bar: BarConfig {
}
property ClipboardConfig clipboard: ClipboardConfig {
}
+1 -1
View File
@@ -1,7 +1,6 @@
import Quickshell.Io
JsonObject {
property int dragThreshold: 50
property bool enabled: true
property int mediaUpdateInterval: 500
property Performance performance: Performance {
@@ -11,6 +10,7 @@ JsonObject {
}
component Performance: JsonObject {
property bool enabled: true
property bool showBattery: true
property bool showCpu: true
property bool showGpu: true
+5
View File
@@ -84,6 +84,11 @@ JsonObject {
dangerous: false
},
]
property int dragThreshold: 50
property bool enableDangerousActions: false
property bool enabled: true
property list<string> favoriteApps: []
property list<string> hiddenApps: []
property int maxAppsShown: 10
property int maxWallpapers: 7
property Sizes sizes: Sizes {
+1
View File
@@ -9,6 +9,7 @@ JsonObject {
property bool expire: true
property int groupPreviewNum: 3
property bool openExpanded: false
property bool showInFullscreen: true
property Sizes sizes: Sizes {
}
+1
View File
@@ -1,6 +1,7 @@
import Quickshell.Io
JsonObject {
property int dragThreshold: 30
property bool enabled: true
property Sizes sizes: Sizes {
}
+1 -6
View File
@@ -25,7 +25,7 @@ Singleton {
property alias server: server
function shouldThrottle(appName: string): bool {
if (props.dnd)
if (props.dnd || [...Visibilities.screens.values()].some(v => v.sidebar))
return false;
const key = (appName || "unknown").trim().toLowerCase();
@@ -49,11 +49,6 @@ Singleton {
if (loaded) {
saveTimer.restart();
}
if (root.list.length > 0) {
HasNotifications.hasNotifications = true;
} else {
HasNotifications.hasNotifications = false;
}
}
Timer {
+1 -1
View File
@@ -44,7 +44,7 @@ Scope {
}
component ExclusionZone: CustomWindow {
exclusiveZone: Config.barConfig.border
exclusiveZone: Config.bar.border
implicitHeight: 1
implicitWidth: 1
name: "Bar-Exclusion"
+12 -10
View File
@@ -21,15 +21,15 @@ Item {
required property PersistentProperties visibilities
function inBottomPanel(panel: Item, x: real, y: real): bool {
return y > root.height - panel.height - Config.barConfig.border && withinPanelWidth(panel, x, y);
return y > root.height - panel.height - Config.bar.border && withinPanelWidth(panel, x, y);
}
function inLeftPanel(panel: Item, x: real, y: real): bool {
return x < panel.x + panel.width + Config.barConfig.border && withinPanelHeight(panel, x, y);
return x < panel.x + panel.width + Config.bar.border && withinPanelHeight(panel, x, y);
}
function inRightPanel(panel: Item, x: real, y: real): bool {
return x > panel.x - Config.barConfig.border && withinPanelHeight(panel, x, y);
return x > panel.x - Config.bar.border && withinPanelHeight(panel, x, y);
}
function inTopPanel(panel: Item, x: real, y: real): bool {
@@ -75,7 +75,7 @@ Item {
const dragX = x - centroid.pressPosition.x;
const dragY = y - centroid.pressPosition.y;
if (centroid.pressPosition.y >= root.screen.height - Config.barConfig.border && centroid.pressPosition.x > root.screen.width / 5 && dragY < -200)
if (centroid.pressPosition.y >= root.screen.height - Config.bar.border && centroid.pressPosition.x > root.screen.width / 5 && dragY < -Config.launcher.dragThreshold)
root.visibilities.launcher = true;
if (root.singleGestureTriggered)
@@ -91,7 +91,7 @@ Item {
}
}
if (centroid.pressPosition.y > root.screen.height - Config.barConfig.border && centroid.pressPosition.x < root.screen.width / 5 && dragY < -50)
if (centroid.pressPosition.y > root.screen.height - Config.bar.border && centroid.pressPosition.x < root.screen.width / 5 && dragY < -50)
root.visibilities.clipboard = true;
if (!Config.dock.hoverToReveal && centroid.pressPosition.y > root.screen.height - root.bar.implicitHeight && centroid.pressPosition.x > root.screen.width / 5 && !root.visibilities.launcher)
@@ -100,17 +100,17 @@ Item {
root.singleGestureTriggered = true;
}
if (centroid.pressPosition.x > root.screen.width - Config.barConfig.border && centroid.pressPosition.y < (root.screen.height / 2) && dragX < -20) {
if (centroid.pressPosition.x > root.screen.width - Config.bar.border && centroid.pressPosition.y < (root.screen.height / 2) && dragX < -Config.sidebar.dragThreshold) {
root.visibilities.sidebar = true;
root.singleGestureTriggered = true;
}
if (centroid.pressPosition.x >= root.screen.width - Config.barConfig.border && centroid.pressPosition.y > (root.screen.height / 2) && dragX < -20) {
if (centroid.pressPosition.x >= root.screen.width - Config.bar.border && centroid.pressPosition.y > (root.screen.height / 2) && dragX < -20) {
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r+1', on_current_monitor = true })`);
root.singleGestureTriggered = true;
}
if (centroid.pressPosition.x <= Config.barConfig.border && dragX > 20) {
if (centroid.pressPosition.x <= Config.bar.border && dragX > 20) {
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r-1', on_current_monitor = true })`);
root.singleGestureTriggered = true;
}
@@ -174,7 +174,7 @@ Item {
root.popouts.hasCurrent = false;
}
if (Config.barConfig.autoHide)
if (Config.bar.autoHide)
root.bar.isHovered = false;
}
}
@@ -193,7 +193,7 @@ Item {
return;
}
if (!root.visibilities.bar && Config.barConfig.autoHide && y < root.bar.implicitHeight)
if (!root.visibilities.bar && Config.bar.autoHide && y < root.bar.implicitHeight)
root.bar.isHovered = true;
if (root.panels.sidebar.width === 0) {
@@ -298,6 +298,8 @@ Item {
root.visibilities.sidebar = false;
root.panels.popouts.hasCurrent = false;
root.visibilities.launcher = false;
} else {
Config.save();
}
}
+4 -4
View File
@@ -10,7 +10,7 @@ import qs.Modules.Osd as Osd
import qs.Components.Toast as Toasts
import qs.Modules.Launcher as Launcher
import qs.Modules.Resources as Resources
import qs.Modules.Settings as Settings
import qs.Modules.SettingsNew as Settings
import qs.Modules.Drawing as Drawing
import qs.Modules.Dock as Dock
import qs.Modules.Clipboard as Clipboard
@@ -184,11 +184,11 @@ Item {
Item {
id: settingsWrapper
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
clip: true
implicitHeight: settings.implicitHeight * (1 - settings.offsetScale)
implicitHeight: settings.implicitHeight
implicitWidth: settings.implicitWidth
x: (root.width - settings.implicitWidth) / 2
y: (settings.implicitHeight + (root.height - root.bar.implicitHeight - settings.implicitHeight) / 2) * (1 - settings.offsetScale) - settings.implicitHeight - 5
Settings.Wrapper {
id: settings
+89
View File
@@ -0,0 +1,89 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import qs.Modules.Bar as Bar
Region {
id: root
required property Bar.BarLoader bar
readonly property real borderThickness: win.borderThickness
readonly property alias menuPopoutRegion: menuPopoutRegion
required property Panels panels
required property var win
height: win.height - bar.implicitHeight - win.borderThickness - win.dragMaskPadding * 2
intersection: Intersection.Xor
width: win.width - win.borderThickness * 2 - win.dragMaskPadding * 2
x: win.borderThickness + win.dragMaskPadding
y: bar.implicitHeight + win.dragMaskPadding
R {
panel: root.panels.dashboardWrapper
}
R {
panel: root.panels.launcher
}
R {
id: sidebarRegion
panel: root.panels.sidebar
width: panel.width * (1 - root.panels.sidebar.offsetScale) + root.borderThickness
x: root.win.width - width
}
R {
panel: root.panels.osdWrapper
width: panel.width * (1 - root.panels.osd.offsetScale) + root.borderThickness
x: root.win.width - width
}
R {
panel: root.panels.notifications
}
R {
height: panel.height * (1 - root.panels.utilities.offsetScale) + root.borderThickness
panel: root.panels.utilities
y: root.win.height - height
}
R {
panel: root.panels.popoutsWrapper
}
R {
panel: root.panels.resourcesWrapper
}
R {
panel: root.panels.settingsWrapper
}
R {
panel: root.panels.dock
}
R {
panel: root.panels.clipboard
}
Region {
id: menuPopoutRegion
intersection: Intersection.Subtract
}
component R: Region {
required property Item panel
height: panel.height
intersection: Intersection.Subtract
width: panel.width
x: panel.x + root.borderThickness
y: panel.y + root.bar.implicitHeight
}
}
+25 -45
View File
@@ -18,9 +18,9 @@ CustomWindow {
id: root
readonly property alias bar: bar
readonly property real borderLayoutThickness: hasFullscreen ? 0 : Config.barConfig.border
readonly property real borderRounding: Config.barConfig.rounding * (1 - fsTransitionProg)
readonly property real borderThickness: Config.barConfig.border * (1 - fsTransitionProg)
readonly property real borderLayoutThickness: hasFullscreen ? 0 : Config.bar.border
readonly property real borderRounding: Config.bar.rounding * (1 - fsTransitionProg)
readonly property real borderThickness: Config.bar.border * (1 - fsTransitionProg)
readonly property int dragMaskPadding: {
if (focusGrab.active)
return 0;
@@ -44,7 +44,7 @@ CustomWindow {
readonly property bool hasFullscreenOnNormalWs: monitor?.activeWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen > 1) ?? false
readonly property bool hasSpecialWorkspace: (monitor?.lastIpcObject.specialWorkspace?.name.length ?? 0) > 0
readonly property alias interactionWrapper: interactions
readonly property alias menuRegion: menuPopoutRegion
readonly property alias menuRegion: regions.menuPopoutRegion
readonly property HyprlandMonitor monitor: Hypr.monitorFor(screen)
property var root: Quickshell.shellDir
readonly property real sdfBorderOffset: 2 * fsTransitionProg
@@ -56,7 +56,7 @@ CustomWindow {
WlrLayershell.layer: (fsTransitionProg > 0 && Config.general.showOverFullscreen) || (hasSpecialWorkspace && hasFullscreenOnNormalWs) ? WlrLayer.Overlay : WlrLayer.Top
color: "transparent"
contentItem.focus: true
mask: visibilities.isDrawing ? null : (hasFullscreen ? emptyRegion : region)
mask: visibilities.isDrawing ? null : (hasFullscreen ? emptyRegion : regions)
name: "Bar"
Behavior on fsTransitionProg {
@@ -69,7 +69,7 @@ CustomWindow {
}
contentItem.Keys.onEscapePressed: {
if (Config.barConfig.autoHide)
if (Config.bar.autoHide)
visibilities.bar = false;
visibilities.launcher = false;
visibilities.sidebar = false;
@@ -93,12 +93,6 @@ CustomWindow {
panels.popouts.hasCurrent = false;
}
Region {
id: menuPopoutRegion
intersection: Intersection.Subtract
}
Region {
id: emptyRegion
@@ -106,17 +100,21 @@ CustomWindow {
width: panels.notifications.width
x: panels.notifications.x + root.borderThickness
y: panels.notifications.y + bar.implicitHeight
Region {
height: panels.osd.height
width: panels.osdWrapper.width * (1 - panels.osd.offsetScale) + root.borderThickness
x: root.width - width
y: panels.osdWrapper.y + bar.implicitHeight
}
}
Region {
id: region
Regions {
id: regions
height: root.height - bar.implicitHeight - root.borderThickness - root.dragMaskPadding * 2
intersection: Intersection.Xor
regions: [...popoutRegions.instances, menuPopoutRegion]
width: root.width - root.borderThickness * 2 - root.dragMaskPadding * 2
x: root.borderThickness + root.dragMaskPadding
y: bar.implicitHeight + root.dragMaskPadding
bar: bar
panels: panels
win: root
}
anchors {
@@ -126,22 +124,6 @@ CustomWindow {
top: true
}
Variants {
id: popoutRegions
model: panels.children
Region {
required property Item modelData
height: modelData.height
intersection: Intersection.Subtract
width: modelData.width
x: modelData.x + root.borderThickness
y: modelData.y + bar.implicitHeight
}
}
HyprlandFocusGrab {
id: focusGrab
@@ -190,8 +172,8 @@ CustomWindow {
Binding {
property: "bar"
target: visibilities
value: visibilities.sidebar || visibilities.dashboard || visibilities.osd || (!Config.barConfig.hideWhenNotif && visibilities.notif) || visibilities.resources || visibilities.settings || bar.isHovered
when: Config.barConfig.autoHide
value: visibilities.sidebar || visibilities.dashboard || visibilities.osd || (!Config.bar.hideWhenNotif && visibilities.notif) || visibilities.resources || visibilities.settings || bar.isHovered
when: Config.bar.autoHide
}
Item {
@@ -211,7 +193,7 @@ CustomWindow {
id: blobGroup
color: root.surfaceColor
smoothing: Config.barConfig.smoothing
smoothing: Config.bar.smoothing
}
BlobInvertedRect {
@@ -323,15 +305,13 @@ CustomWindow {
PanelBg {
id: settingsBg
property real extraHeight: 0.2
property real extraHeight: 0
deformAmount: 0.03
implicitHeight: panels.settings.height * (1 + extraHeight)
implicitWidth: panels.settings.width
panel: panels.settings
radius: Appearance.rounding.large
topLeftRadius: Appearance.rounding.large + Appearance.padding.smaller
topRightRadius: Appearance.rounding.large + Appearance.padding.smaller
panel: panels.settingsWrapper
radius: Appearance.rounding.large + Appearance.padding.normal
x: panels.settingsWrapper.x + panels.settings.x + root.borderThickness
y: panels.settingsWrapper.y + panels.settings.y + bar.implicitHeight - panels.settings.height * extraHeight
}
@@ -441,7 +421,7 @@ CustomWindow {
resources.transform: Matrix4x4 {
matrix: resourcesBg.deformMatrix
}
settings.transform: Matrix4x4 {
settingsWrapper.transform: Matrix4x4 {
matrix: settingsBg.deformMatrix
}
sidebar.transform: Matrix4x4 {
+7
View File
@@ -0,0 +1,7 @@
import QtQuick.Effects
MultiEffect {
maskEnabled: true
maskSpreadAtMin: 1
maskThresholdMin: 0.5
}
-16
View File
@@ -1,16 +0,0 @@
pragma Singleton
import Quickshell
import Quickshell.Io
Singleton {
id: root
property alias hasNotifications: adapter.hasNotifications
JsonObject {
id: adapter
property bool hasNotifications: false
}
}
+26
View File
@@ -0,0 +1,26 @@
pragma Singleton
import Quickshell
Singleton {
property var _regexCache: ({})
function testRegexList(filterList: list<string>, target: string): bool {
const regexChecker = /^\^.*\$$/;
for (const filter of filterList) {
if (regexChecker.test(filter)) {
let re = _regexCache[filter];
if (!re) {
re = new RegExp(filter);
_regexCache[filter] = re;
}
if (re.test(target))
return true;
} else {
if (filter === target)
return true;
}
}
return false;
}
}
+7
View File
@@ -21,6 +21,13 @@ Searcher {
property bool recentlyChanged
property bool showPreview: false
function getCategoryFor(w: FileSystemEntry): string {
let category = w.parentDir.slice(Paths.wallsdir.length + 1);
if (category.includes("/"))
category = category.slice(0, category.indexOf("/"));
return category;
}
function getCrop(screen: string): var {
return root.crops[screen];
}
+5 -5
View File
@@ -57,15 +57,15 @@ RowLayout {
const item = ch.item;
const itemWidth = item.implicitWidth;
if (id === "audio" && Config.barConfig.popouts.audio) {
if (id === "audio" && Config.bar.popouts.audio) {
popouts.currentName = "audio";
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true;
} else if (id === "network" && Config.barConfig.popouts.network) {
} else if (id === "network" && Config.bar.popouts.network) {
popouts.currentName = "network";
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true;
} else if (id === "upower" && Config.barConfig.popouts.upower) {
} else if (id === "upower" && Config.bar.popouts.upower) {
popouts.currentName = "upower";
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true;
@@ -81,8 +81,8 @@ RowLayout {
Repeater {
id: repeater
// model: Config.barConfig.entries.filted(n => n.index > 50).sort(n => n.index)
model: Config.barConfig.entries
// model: Config.bar.entries.filted(n => n.index > 50).sort(n => n.index)
model: Config.bar.entries
DelegateChooser {
role: "id"
+6 -6
View File
@@ -15,15 +15,15 @@ import qs.Modules.Network
Item {
id: root
readonly property int contentHeight: Config.barConfig.height + padding * 2
readonly property int exclusiveZone: Config.barConfig.autoHide ? Config.barConfig.border : contentHeight
readonly property int contentHeight: Config.bar.height + padding * 2
readonly property int exclusiveZone: Config.bar.autoHide ? Config.bar.border : contentHeight
required property bool fullscreen
property bool isHovered
readonly property int padding: Math.max(Appearance.padding.smaller, Config.barConfig.border)
readonly property int padding: Math.max(Appearance.padding.smaller, Config.bar.border)
required property Wrapper popouts
required property ClipWrapper popoutsWrapper
required property ShellScreen screen
readonly property bool shouldBeVisible: !fullscreen && (!Config.barConfig.autoHide || visibilities.bar || isHovered)
readonly property bool shouldBeVisible: !fullscreen && (!Config.bar.autoHide || visibilities.bar || isHovered)
readonly property int vPadding: 6
required property PersistentProperties visibilities
@@ -31,8 +31,8 @@ Item {
content.item?.checkPopout(x);
}
implicitHeight: fullscreen ? 0 : Config.barConfig.border
visible: height > Config.barConfig.border
implicitHeight: fullscreen ? 0 : Config.bar.border
visible: height > Config.bar.border
states: State {
name: "visible"
+4 -4
View File
@@ -38,11 +38,11 @@ Item {
Rectangle {
anchors.fill: parent
anchors.margins: Config.barConfig.border + 1
anchors.margins: Config.bar.border + 1
anchors.topMargin: root.bar.implicitHeight + 1
radius: Config.barConfig.border > 0 ? Config.barConfig.rounding : 0
topLeftRadius: Config.barConfig.rounding
topRightRadius: Config.barConfig.rounding
radius: Config.bar.border > 0 ? Config.bar.rounding : 0
topLeftRadius: Config.bar.rounding
topRightRadius: Config.bar.rounding
}
}
}
+1 -1
View File
@@ -14,7 +14,7 @@ CustomRect {
required property PersistentProperties visibilities
color: visibilities.dashboard ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
implicitHeight: Config.bar.height + Appearance.padding.smallest * 2
implicitWidth: timeText.contentWidth + Appearance.padding.normal * 2
radius: Appearance.rounding.full
+1 -1
View File
@@ -9,7 +9,7 @@ CustomRect {
property bool tempEnabled: Hyprsunset.enabled
color: root.tempEnabled ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
implicitHeight: Config.bar.height + Appearance.padding.smallest * 2
implicitWidth: implicitHeight
radius: Appearance.rounding.full
+15
View File
@@ -65,5 +65,20 @@ Item {
width: root.width - icon.width - Appearance.rounding.normal * 2
}
}
Loader {
id: favoriteIcon
active: root.modelData && Strings.testRegexList(Config.launcher.favoriteApps, root.modelData.id)
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
asynchronous: true
sourceComponent: MaterialIcon {
color: DynamicColors.palette.m3primary
fill: 1
text: "favorite"
}
}
}
}
+1 -1
View File
@@ -19,7 +19,7 @@ Searcher {
Variants {
id: variants
model: Config.launcher.actions.filter(a => (a.enabled ?? true))
model: Config.launcher.actions.filter(a => (a.enabled ?? true) && (Config.launcher.enableDangerousActions || !(a.dangerous ?? false)))
Action {
}
+2 -1
View File
@@ -74,7 +74,8 @@ Searcher {
AppDb {
id: appDb
entries: DesktopEntries.applications.values
entries: DesktopEntries.applications.values.filter(a => !Strings.testRegexList(Config.launcher.hiddenApps, a.id))
favoriteApps: Config.launcher.favoriteApps
path: `${Paths.state}/apps.sqlite`
}
}
+1 -1
View File
@@ -22,7 +22,7 @@ PathView {
let outerMargins = 0;
if ((visibilities.utilities || visibilities.sidebar) && panels.utilities.implicitWidth > outerMargins)
outerMargins = panels.utilities.implicitWidth;
const maxWidth = screen.width - Config.barConfig.rounding * 4 - (barMargins + outerMargins) * 2;
const maxWidth = screen.width - Config.bar.rounding * 4 - (barMargins + outerMargins) * 2;
if (maxWidth <= 0)
return 0;
+1 -1
View File
@@ -12,7 +12,7 @@ CustomRect {
readonly property int textWidth: Math.min(metrics.width, 200)
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
implicitHeight: Config.bar.height + Appearance.padding.smallest * 2
implicitWidth: layout.implicitWidth + Appearance.padding.normal * 2
radius: Appearance.rounding.full
+3 -4
View File
@@ -1,8 +1,7 @@
import Quickshell
import Quickshell.Hyprland
import QtQuick
import qs.Config
import qs.Helpers
import qs.Daemons
import qs.Components
CustomRect {
@@ -12,7 +11,7 @@ CustomRect {
required property PersistentProperties visibilities
color: visibilities.sidebar ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
implicitHeight: Config.bar.height + Appearance.padding.smallest * 2
implicitWidth: implicitHeight
radius: Appearance.rounding.full
@@ -24,7 +23,7 @@ CustomRect {
fill: root.visibilities.sidebar ? 1 : 0
font.family: "Material Symbols Rounded"
font.pointSize: Appearance.font.size.larger
text: HasNotifications.hasNotifications ? "\uf4fe" : "\ue7f4"
text: NotifServer.list.length ? "\uf4fe" : "\ue7f4"
Behavior on color {
CAnim {
+13 -4
View File
@@ -1,11 +1,12 @@
pragma ComponentBehavior: Bound
import qs.Components
import qs.Config
import qs.Daemons
import Quickshell
import Quickshell.Widgets
import QtQuick
import qs.Components
import qs.Config
import qs.Daemons
import qs.Helpers
Item {
id: root
@@ -72,7 +73,15 @@ Item {
}
}
model: ScriptModel {
values: NotifServer.popups.filter(n => !n.closed)
values: {
const popups = NotifServer.popups.filter(n => !n.closed);
if (!Config.notifs.showInFullscreen) {
if (Hypr.monitorFor(root.panels.screen).activeWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen > 1) ?? false)
return [];
}
return popups;
}
}
move: Transition {
Anim {
@@ -7,7 +7,7 @@ ShapePath {
id: root
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real rounding: Config.barConfig.rounding
readonly property real rounding: Config.bar.rounding
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property var sidebar
required property Wrapper wrapper
@@ -73,6 +73,7 @@ CustomRect {
CustomSplitButton {
active: menuItems.find(m => root.props.recordingMode === m.icon + m.text) ?? menuItems[0]
enabled: !Recorder.running
menuOnTop: true
menuItems: [
MenuItem {
+1 -1
View File
@@ -16,7 +16,7 @@ CustomRect {
clip: true
color: visibilities.resources ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
implicitHeight: Config.bar.height + Appearance.padding.smallest * 2
implicitWidth: rowLayout.implicitWidth + Appearance.padding.larger * 2
radius: Appearance.rounding.full
+14 -14
View File
@@ -11,7 +11,7 @@ SettingsPage {
SettingSwitch {
name: "Auto hide"
object: Config.barConfig
object: Config.bar
setting: "autoHide"
}
@@ -21,7 +21,7 @@ SettingsPage {
SettingSpinBox {
min: 1
name: "Height"
object: Config.barConfig
object: Config.bar
setting: "height"
}
@@ -31,7 +31,7 @@ SettingsPage {
SettingSpinBox {
min: 0
name: "Rounding"
object: Config.barConfig
object: Config.bar
setting: "rounding"
}
@@ -41,7 +41,7 @@ SettingsPage {
SettingSpinBox {
min: 0
name: "Border"
object: Config.barConfig
object: Config.bar
setting: "border"
}
@@ -51,7 +51,7 @@ SettingsPage {
SettingSpinBox {
min: 0
name: "Smoothing"
object: Config.barConfig
object: Config.bar
setting: "smoothing"
}
}
@@ -66,7 +66,7 @@ SettingsPage {
SettingSpinBox {
min: 16
name: "Tray icon size"
object: Config.barConfig.tray
object: Config.bar.tray
setting: "trayIconSize"
}
}
@@ -80,7 +80,7 @@ SettingsPage {
SettingSwitch {
name: "Tray"
object: Config.barConfig.popouts
object: Config.bar.popouts
setting: "tray"
}
@@ -89,7 +89,7 @@ SettingsPage {
SettingSwitch {
name: "Audio"
object: Config.barConfig.popouts
object: Config.bar.popouts
setting: "audio"
}
@@ -98,7 +98,7 @@ SettingsPage {
SettingSwitch {
name: "Active window"
object: Config.barConfig.popouts
object: Config.bar.popouts
setting: "activeWindow"
}
@@ -107,7 +107,7 @@ SettingsPage {
SettingSwitch {
name: "Resources"
object: Config.barConfig.popouts
object: Config.bar.popouts
setting: "resources"
}
@@ -116,7 +116,7 @@ SettingsPage {
SettingSwitch {
name: "Clock"
object: Config.barConfig.popouts
object: Config.bar.popouts
setting: "clock"
}
@@ -125,7 +125,7 @@ SettingsPage {
SettingSwitch {
name: "Network"
object: Config.barConfig.popouts
object: Config.bar.popouts
setting: "network"
}
@@ -134,7 +134,7 @@ SettingsPage {
SettingSwitch {
name: "Power"
object: Config.barConfig.popouts
object: Config.bar.popouts
setting: "upower"
}
}
@@ -148,7 +148,7 @@ SettingsPage {
SettingBarEntryList {
name: "Bar entries"
object: Config.barConfig
object: Config.bar
setting: "entries"
}
}
@@ -0,0 +1,96 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.Pipewire
import qs.Components
import qs.Config
ItemList {
id: root
property int currentId: -1
property string iconName: "speaker"
property var nodes: []
signal selected(node: PwNode)
last: true
showList: true
delegate: Item {
id: device
readonly property bool active: device.modelData?.id === root.currentId
required property int index
required property PwNode modelData
anchors.left: root.list.contentItem.left
anchors.right: root.list.contentItem.right
implicitHeight: deviceLayout.implicitHeight + deviceLayout.anchors.margins * 2
StateLayer {
bottomLeftRadius: device.index === root?.list.count - 1 ? Appearance.rounding.large : radius
bottomRightRadius: device.index === root?.list.count - 1 ? Appearance.rounding.large : radius
radius: Appearance.rounding.extraSmall
onClicked: root.selected(device.modelData)
}
RowLayout {
id: deviceLayout
anchors.fill: parent
anchors.leftMargin: Appearance.padding.largeIncreased
anchors.margins: Appearance.padding.large
anchors.rightMargin: Appearance.padding.largeIncreased
spacing: Appearance.spacing.normal
CustomRect {
color: device.active ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondaryContainer
implicitHeight: devIcon.implicitHeight + Appearance.padding.normal * 2
implicitWidth: implicitHeight
radius: Appearance.rounding.full
MaterialIcon {
id: devIcon
anchors.centerIn: parent
color: device.active ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSecondaryContainer
fill: device.active ? 1 : 0
font.pointSize: Appearance.font.size.large
text: root.iconName
Behavior on fill {
Anim {
}
}
}
}
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.smaller
text: device.modelData?.description || device.modelData?.name || qsTr("Unknown")
}
MaterialIcon {
color: DynamicColors.palette.m3primary
font.pointSize: Appearance.font.size.large
opacity: device.active ? 1 : 0
text: "check"
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
}
}
model: ScriptModel {
values: [...root.nodes].sort((a, b) => (a.description || a.name || "").localeCompare(b.description || b.name || ""))
}
}
+128
View File
@@ -0,0 +1,128 @@
import QtQuick
import ZShell.Blobs
import qs.Components
import qs.Config
Item {
id: root
property real animDriver
property alias color: blobGroup.color
default required property Item content
property bool hoverOverride
readonly property alias hovered: btn.containsMouse
property alias icon: icon.text
property bool open
property int padding
property bool pressOverride
property int topMovement: Appearance.padding.large
implicitHeight: btn.implicitHeight * 0.9
implicitWidth: btn.implicitWidth * 0.9
Binding {
property: "opacity"
target: root.content
value: root.animDriver
}
BlobGroup {
id: blobGroup
color: DynamicColors.palette.m3surfaceContainerHighest
cornerFill: false
smoothing: Appearance.rounding.medium
Behavior on color {
CAnim {
}
}
}
BlobRect {
id: btnRect
anchors.fill: parent
anchors.margins: (!(btn.pressed || root.pressOverride) && (btn.containsMouse || root.hoverOverride) ? -Appearance.padding.extraSmall : 0) + (root.open ? -Appearance.padding.extraSmall : 0)
group: blobGroup
radius: root.open ? Appearance.rounding.large : Appearance.rounding.medium
Behavior on anchors.margins {
Anim {
}
}
Behavior on radius {
Anim {
type: Anim.DefaultEffects
}
}
}
BlobRect {
id: rect
anchors.right: parent.right
anchors.top: parent.top
deformScale: 0.00001
group: blobGroup
implicitHeight: parent.height
implicitWidth: parent.width
radius: Appearance.rounding.small + Appearance.padding.small
states: State {
name: "open"
when: root.open
PropertyChanges {
rect.anchors.rightMargin: root.width - Appearance.spacing.small
rect.anchors.topMargin: -root.topMovement
rect.implicitHeight: root.content.implicitHeight + root.padding * 2
rect.implicitWidth: root.content.implicitWidth + root.padding * 2
root.animDriver: 1
}
}
transitions: Transition {
Anim {
properties: "rightMargin,implicitWidth"
}
Anim {
duration: Appearance.anim.durations.expressiveFastEffects
easing.bezierCurve: Appearance.anim.curves.expressiveFastEffects
properties: "topMargin,implicitHeight"
}
Anim {
property: "animDriver"
type: Anim.DefaultEffects
}
}
MouseArea { // MouseArea to catch inputs
anchors.fill: parent
children: [root.content]
clip: true
}
}
MouseArea {
id: btn
anchors.centerIn: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
implicitHeight: icon.implicitHeight + Appearance.padding.extraSmall * 2
implicitWidth: implicitHeight
onClicked: root.open = !root.open
MaterialIcon {
id: icon
anchors.centerIn: parent
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.medium
text: "view_apps"
}
}
}
@@ -0,0 +1,14 @@
import QtQuick
import qs.Components
import qs.Config
CustomRect {
property bool first
property bool last
bottomLeftRadius: last ? Appearance.rounding.large : Appearance.rounding.extraSmall
bottomRightRadius: last ? Appearance.rounding.large : Appearance.rounding.extraSmall
color: DynamicColors.tPalette.m3surfaceContainer
topLeftRadius: first ? Appearance.rounding.large : Appearance.rounding.extraSmall
topRightRadius: first ? Appearance.rounding.large : Appearance.rounding.extraSmall
}
@@ -0,0 +1,659 @@
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Components
import qs.Helpers
Item {
id: root
readonly property string endTime: {
var d = new Date(0, 0, 0, 0, 0, 0, 0);
d.setMinutes(object[settings[2]]);
return Qt.formatTime(d, "hh:mm AP");
}
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
required property string name
required property var object
required property list<string> settings
property bool shouldBeActive: true
readonly property string startTime: {
var d = new Date(0, 0, 0, 0, 0, 0, 0);
d.setMinutes(object[settings[1]]);
return Qt.formatTime(d, "hh:mm AP");
}
function commitChoice(choice: int, setting: string): void {
root.object[setting] = choice;
Config.save();
Hyprsunset.checkStartup();
}
function convertHour(timeValue: int): int {
return Math.floor(timeValue / 60);
}
function convertMinute(timeValue: int): int {
return timeValue % 60;
}
function convertToMinutes(hour: int, minute: int): int {
return hour * 60 + minute;
}
Layout.fillWidth: true
implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Rectangle {
anchors.fill: parent
anchors.margins: -Appearance.padding.smaller
color: DynamicColors.palette.m3primaryContainer
opacity: root.highlighted ? 0.5 : 0
radius: Appearance.rounding.small
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.normal
}
}
}
RowLayout {
id: row
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
ColumnLayout {
Layout.fillHeight: true
Layout.fillWidth: true
CustomText {
id: text
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
CustomText {
Layout.alignment: Qt.AlignLeft
Layout.preferredWidth: Math.min(contentWidth, optionLayout.x - Appearance.spacing.normal)
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.normal
text: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(root.startTime).arg(root.endTime)
wrapMode: Text.WordWrap
}
}
ColumnLayout {
id: optionLayout
Layout.fillHeight: true
Layout.fillWidth: true
RowLayout {
CustomText {
Layout.preferredWidth: spacer.x + spacer.width
text: qsTr("Start")
}
CustomText {
Layout.preferredWidth: endMinuteRect.width + endHourRect.width
text: qsTr("End")
}
CustomText {
Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter
text: qsTr("Enabled: ")
}
CustomSwitch {
id: enabledSwitch
Layout.alignment: Qt.AlignRight | Qt.AlignHCenter
checked: root.object[root.settings[0]]
onToggled: {
root.object[root.settings[0]] = checked;
Config.save();
}
}
}
RowLayout {
Layout.fillHeight: true
CustomRect {
id: startHourRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: startHourField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Appearance.rounding.small
CustomRect {
anchors.fill: parent
border.color: startHourField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest
border.width: startHourField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {
}
}
}
CustomTextField {
id: startHourField
function setConfigText(setting: string): string {
var val = root.convertHour(root.object[setting]);
if (val === 0) {
return "00";
}
return String(val);
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
color: focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface
cursorHeight: height - Appearance.padding.normal * 2
font.family: "Roboto"
font.letterSpacing: -0.25
font.pixelSize: 56
font.weight: 400
horizontalAlignment: TextInput.AlignHCenter
text: setConfigText(root.settings[1])
verticalAlignment: TextInput.AlignVCenter
Keys.onPressed: event => {
if (event.key === Qt.Key_Backspace) {
event.accepted = true;
if (startHourField.text.length >= 2) {
startHourField.text = "0" + startHourField.text[0];
} else if (startHourField.text.length === 1) {
startHourField.text = "0";
}
return;
} else if (event.key === Qt.Key_Escape) {
event.accepted = true;
startHourField.text = setConfigText(root.settings[1]);
startHourField.focus = false;
} else if (event.key === Qt.Key_Return) {
startHourField.focus = false;
return;
} else if (event.key === Qt.Key_Tab) {
startMinuteField.focus = true;
} else if (event.key === Qt.Key_Backtab) {
endMinuteField.focus = true;
}
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
event.accepted = true;
var digit = event.text;
var textLen = startHourField.text.length;
if (textLen >= 2 && startHourField.text[0] !== '0') {
return;
}
var val = 0;
if (textLen === 0) {
val = parseInt(digit);
} else if (textLen === 1) {
val = parseInt(startHourField.text + digit);
} else {
val = parseInt(startHourField.text[1] + digit);
}
val = Math.max(0, Math.min(23, val));
if (textLen >= 2 && val < 10) {
startHourField.text = "0" + val;
} else {
startHourField.text = val.toString();
}
}
event.accepted = true;
}
onCursorPositionChanged: cursorPosition = 2
onEditingFinished: {
root.commitChoice(root.convertToMinutes(parseInt(startHourField.text), parseInt(startMinuteField.text)), root.settings[1]);
}
onTextEdited: {
if (startHourField.text === "")
return;
var val = parseInt(startHourField.text);
if (isNaN(val))
return;
val = Math.max(0, Math.min(23, val));
var newText = val.toString();
if (newText !== startHourField.text)
startHourField.text = newText;
}
}
}
CustomText {
id: startSeparator
font.pointSize: Appearance.font.size.extraLarge
text: ":"
}
CustomRect {
id: startMinuteRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: startMinuteField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Appearance.rounding.small
CustomRect {
anchors.fill: parent
border.color: startMinuteField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest
border.width: startMinuteField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {
}
}
}
CustomTextField {
id: startMinuteField
function setConfigText(setting: string): string {
var val = root.convertMinute(root.object[setting]);
if (val === 0) {
return "00";
}
return String(val);
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
color: focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface
cursorHeight: height - Appearance.padding.normal * 2
font.family: "Roboto"
font.letterSpacing: -0.25
font.pixelSize: 56
font.weight: 400
horizontalAlignment: TextInput.AlignHCenter
text: setConfigText(root.settings[1])
verticalAlignment: TextInput.AlignVCenter
Keys.onPressed: event => {
if (event.key === Qt.Key_Backspace) {
event.accepted = true;
if (startMinuteField.text.length >= 2) {
startMinuteField.text = "0" + startMinuteField.text[0];
} else if (startMinuteField.text.length === 1) {
startMinuteField.text = "0";
}
return;
} else if (event.key === Qt.Key_Escape) {
event.accepted = true;
startMinuteField.text = setConfigText(root.settings[1]);
startMinuteField.focus = false;
} else if (event.key === Qt.Key_Return) {
startMinuteField.focus = false;
return;
} else if (event.key === Qt.Key_Tab) {
endHourField.focus = true;
} else if (event.key === Qt.Key_Backtab) {
startHourField.focus = true;
}
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
event.accepted = true;
var digit = event.text;
var textLen = startMinuteField.text.length;
if (textLen >= 2 && startMinuteField.text[0] !== '0') {
return;
}
var val = 0;
if (textLen === 0) {
val = parseInt(digit);
} else if (textLen === 1) {
val = parseInt(startMinuteField.text + digit);
} else {
val = parseInt(startMinuteField.text[1] + digit);
}
val = Math.max(0, Math.min(59, val));
if (textLen >= 2 && val < 10) {
startMinuteField.text = "0" + val;
} else {
startMinuteField.text = val.toString();
}
}
event.accepted = true;
}
onCursorPositionChanged: cursorPosition = 2
onEditingFinished: {
root.commitChoice(root.convertToMinutes(parseInt(startHourField.text), parseInt(startMinuteField.text)), root.settings[1]);
}
onTextEdited: {
if (startMinuteField.text === "")
return;
var val = parseInt(startMinuteField.text);
if (isNaN(val))
return;
val = Math.max(0, Math.min(23, val));
var newText = val.toString();
if (newText !== startMinuteField.text)
startMinuteField.text = newText;
}
}
}
Item {
id: spacer
Layout.preferredWidth: Appearance.spacing.large
}
CustomRect {
id: endHourRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: endHourField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Appearance.rounding.small
CustomRect {
anchors.fill: parent
border.color: endHourField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest
border.width: endHourField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {
}
}
}
CustomTextField {
id: endHourField
function setConfigText(setting: string): string {
var val = root.convertHour(root.object[setting]);
if (val === 0) {
return "00";
}
return String(val);
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
color: focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface
cursorHeight: height - Appearance.padding.normal * 2
font.family: "Roboto"
font.letterSpacing: -0.25
font.pixelSize: 56
font.weight: 400
horizontalAlignment: TextInput.AlignHCenter
text: setConfigText(root.settings[2])
verticalAlignment: TextInput.AlignVCenter
Keys.onPressed: event => {
if (event.key === Qt.Key_Backspace) {
event.accepted = true;
if (endHourField.text.length >= 2) {
endHourField.text = "0" + endHourField.text[0];
} else if (endHourField.text.length === 1) {
endHourField.text = "0";
}
return;
} else if (event.key === Qt.Key_Escape) {
event.accepted = true;
endHourField.text = setConfigText(root.settings[2]);
endHourField.focus = false;
} else if (event.key === Qt.Key_Return) {
endHourField.focus = false;
return;
} else if (event.key === Qt.Key_Tab) {
endMinuteField.focus = true;
} else if (event.key === Qt.Key_Backtab) {
startMinuteField.focus = true;
}
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
event.accepted = true;
var digit = event.text;
var textLen = endHourField.text.length;
if (textLen >= 2 && endHourField.text[0] !== '0') {
return;
}
var val = 0;
if (textLen === 0) {
val = parseInt(digit);
} else if (textLen === 1) {
val = parseInt(endHourField.text + digit);
} else {
val = parseInt(endHourField.text[1] + digit);
}
val = Math.max(0, Math.min(23, val));
if (textLen >= 2 && val < 10) {
endHourField.text = "0" + val;
} else {
endHourField.text = val.toString();
}
}
event.accepted = true;
}
onCursorPositionChanged: cursorPosition = 2
onEditingFinished: {
root.commitChoice(root.convertToMinutes(parseInt(endHourField.text), parseInt(endMinuteField.text)), root.settings[2]);
}
onTextEdited: {
if (endHourField.text === "")
return;
var val = parseInt(endHourField.text);
if (isNaN(val))
return;
val = Math.max(0, Math.min(23, val));
var newText = val.toString();
if (newText !== endHourField.text)
endHourField.text = newText;
}
}
}
CustomText {
id: endSeparator
font.pointSize: Appearance.font.size.extraLarge
text: ":"
}
CustomRect {
id: endMinuteRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: endMinuteField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Appearance.rounding.small
CustomRect {
anchors.fill: parent
border.color: endMinuteField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest
border.width: endMinuteField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {
}
}
}
CustomTextField {
id: endMinuteField
function setConfigText(setting: string): string {
var val = root.convertMinute(root.object[setting]);
if (val === 0) {
return "00";
}
return String(val);
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
color: focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface
cursorHeight: height - Appearance.padding.normal * 2
font.family: "Roboto"
font.letterSpacing: -0.25
font.pixelSize: 56
font.weight: 400
horizontalAlignment: TextInput.AlignHCenter
text: setConfigText(root.settings[2])
verticalAlignment: TextInput.AlignVCenter
Keys.onPressed: event => {
if (event.key === Qt.Key_Backspace) {
event.accepted = true;
if (endMinuteField.text.length >= 2) {
endMinuteField.text = "0" + endMinuteField.text[0];
} else if (endMinuteField.text.length === 1) {
endMinuteField.text = "0";
}
return;
} else if (event.key === Qt.Key_Escape) {
event.accepted = true;
endMinuteField.text = setConfigText(root.settings[2]);
endMinuteField.focus = false;
} else if (event.key === Qt.Key_Return) {
endMinuteField.focus = false;
return;
} else if (event.key === Qt.Key_Tab) {
startHourField.focus = true;
} else if (event.key === Qt.Key_Backtab) {
endHourField.focus = true;
}
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
event.accepted = true;
var digit = event.text;
var textLen = endMinuteField.text.length;
if (textLen >= 2 && endMinuteField.text[0] !== '0') {
return;
}
var val = 0;
if (textLen === 0) {
val = parseInt(digit);
} else if (textLen === 1) {
val = parseInt(endMinuteField.text + digit);
} else {
val = parseInt(endMinuteField.text[1] + digit);
}
val = Math.max(0, Math.min(59, val));
if (textLen >= 2 && val < 10) {
endMinuteField.text = "0" + val;
} else {
endMinuteField.text = val.toString();
}
}
event.accepted = true;
}
onCursorPositionChanged: cursorPosition = 2
onEditingFinished: {
root.commitChoice(root.convertToMinutes(parseInt(endHourField.text), parseInt(endMinuteField.text)), root.settings[2]);
}
onTextEdited: {
if (endMinuteField.text === "")
return;
var val = parseInt(endMinuteField.text);
if (isNaN(val))
return;
val = Math.max(0, Math.min(23, val));
var newText = val.toString();
if (newText !== endMinuteField.text)
endMinuteField.text = newText;
}
}
}
}
RowLayout {
Layout.fillWidth: true
CustomText {
id: startHour
Layout.preferredWidth: startSeparator.x + startSeparator.width
text: qsTr("Hour")
}
CustomText {
id: startMinute
Layout.preferredWidth: spacer.x + spacer.width - x
text: qsTr("Minute")
}
CustomText {
Layout.preferredWidth: endSeparator.x + endSeparator.width - x
text: qsTr("Hour")
}
CustomText {
text: qsTr("Minute")
}
}
}
}
}
+116
View File
@@ -0,0 +1,116 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
ConnectedRect {
id: root
property alias delegate: list.delegate
property int extraHeight
readonly property alias list: list
property alias model: list.model
property string placeholderIcon
property string placeholderText
property bool showList
Layout.fillWidth: true
clip: true
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: (showList && list.count > 0 ? list.contentHeight : placeholder.implicitHeight + Appearance.padding.extraLarge * 2) + extraHeight
Behavior on implicitHeight {
Anim {
}
}
Loader {
id: placeholder
active: opacity > 0
anchors.centerIn: parent
opacity: root.showList && list.count > 0 ? 0 : 1
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
sourceComponent: ColumnLayout {
spacing: Appearance.spacing.extraSmall
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
animate: true
color: DynamicColors.palette.m3outline
font.pointSize: Appearance.font.size.large
text: root.placeholderIcon
}
CustomText {
Layout.alignment: Qt.AlignHCenter
animate: true
color: DynamicColors.palette.m3outline
font.pointSize: Appearance.font.size.large
text: root.placeholderText
}
}
}
ListView {
id: list
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
interactive: false
opacity: root.showList ? 1 : 0
spacing: 0
add: Transition {
Anim {
from: 0
property: "opacity"
to: 1
type: Anim.DefaultEffects
}
}
displaced: Transition {
Anim {
property: "opacity"
to: 1
type: Anim.DefaultEffects
}
Anim {
property: "y"
}
}
move: Transition {
Anim {
property: "opacity"
to: 1
type: Anim.DefaultEffects
}
Anim {
property: "y"
}
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
remove: Transition {
Anim {
property: "opacity"
to: 0
type: Anim.DefaultEffects
}
}
}
}
+70
View File
@@ -0,0 +1,70 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
ConnectedRect {
id: root
property alias icon: icon.text
property alias status: status.text
property alias text: label.text
signal clicked
Layout.fillWidth: true
implicitHeight: navLayout.implicitHeight + navLayout.anchors.margins * 2
StateLayer {
onClicked: root.clicked()
}
RowLayout {
id: navLayout
anchors.fill: parent
anchors.leftMargin: Appearance.padding.largeIncreased
anchors.margins: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.largeIncreased
spacing: Appearance.spacing.normal
MaterialIcon {
id: icon
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.large
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
id: label
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
}
CustomText {
id: status
Layout.fillWidth: true
animate: true
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
visible: text
}
}
MaterialIcon {
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.medium
text: "chevron_right"
}
}
}
+68
View File
@@ -0,0 +1,68 @@
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Components
import qs.Modules.SettingsNew
ConnectedRect {
id: root
property alias checked: switchButton.checked
property int horizontalPadding: Appearance.padding.largeIncreased
required property Component popup
property alias subtext: subtext.text
property alias text: text.text
property int verticalPadding: Appearance.padding.normal
signal clicked(checked: bool)
Layout.fillWidth: true
implicitHeight: layout.implicitHeight + verticalPadding * 2
Column {
id: layout
anchors.left: parent.left
anchors.leftMargin: root.horizontalPadding
anchors.right: icon.left
anchors.rightMargin: Appearance.padding.normal
anchors.verticalCenter: parent.verticalCenter
CustomText {
id: text
font.pointSize: Appearance.font.size.smaller
}
CustomText {
id: subtext
color: DynamicColors.palette.m3outline
font.pointSize: Appearance.font.size.small
wrapMode: Text.WordWrap
}
}
MaterialIcon {
id: icon
anchors.right: switchButton.left
anchors.rightMargin: Appearance.spacing.normal
anchors.verticalCenter: parent.verticalCenter
text: "open_in_new"
}
StateLayer {
onClicked: PopupManager.requestOpen(root.popup)
}
CustomSwitch {
id: switchButton
anchors.right: parent.right
anchors.rightMargin: root.horizontalPadding
anchors.verticalCenter: parent.verticalCenter
onClicked: root.clicked(checked)
}
}
+70
View File
@@ -0,0 +1,70 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Modules.SettingsNew
import qs.Components
import qs.Config
ColumnLayout {
id: root
readonly property int cappedWidth: Math.min(800, width)
default property Item contentChild
readonly property alias flickable: flickable
property bool isSubPage
required property SettingsState sState
required property string title
spacing: Appearance.spacing.large
MouseArea { // Prevent clicks from reaching flickable
Layout.bottomMargin: -flickable.topMargin // Extra height to block clicks on flickable top margin
implicitHeight: header.implicitHeight - Layout.bottomMargin
implicitWidth: header.implicitWidth
z: 1
RowLayout {
id: header
spacing: Appearance.spacing.large
Loader {
active: root.isSubPage
asynchronous: true
visible: active
sourceComponent: IconButton {
icon: "arrow_back"
inactiveColor: DynamicColors.tPalette.m3surfaceContainerHigh
inactiveOnColor: DynamicColors.palette.m3onSurfaceVariant
isRound: true
type: IconButton.Tonal
onClicked: root.sState.closeSubPage()
}
}
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.large
text: root.title
}
}
}
VerticalFadeFlickable {
id: flickable
Layout.fillHeight: true
Layout.fillWidth: true
Layout.topMargin: -topMargin
bottomMargin: Appearance.padding.extraLarge
contentHeight: root.contentChild?.implicitHeight ?? 0
contentItem.children: [root.contentChild]
fadeAmount: 0.1
topMargin: Appearance.padding.large
}
}
+123
View File
@@ -0,0 +1,123 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Components
import qs.Config
import qs.Drawers
ConnectedRect {
id: root
default required property Item content
property alias icon: icon.text
property bool keepPopupAsChild
readonly property alias popup: popup
property alias status: status.text
property alias text: label.text
Layout.fillWidth: true
implicitHeight: navLayout.implicitHeight + navLayout.anchors.margins * 2
StateLayer {
id: stateLayer
manualHoverOverride: popup.hovered && !popup.open
onClicked: popup.open = true
}
RowLayout {
id: navLayout
anchors.fill: parent
anchors.leftMargin: Appearance.padding.largeIncreased
anchors.margins: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.largeIncreased
spacing: Appearance.spacing.normal
MaterialIcon {
id: icon
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.larger
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
id: label
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
}
CustomText {
id: status
Layout.fillWidth: true
animate: true
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
visible: text
}
}
Item {
id: triggerArea
implicitHeight: popup.implicitHeight
implicitWidth: popup.implicitWidth
TransformWatcher {
id: tWatcher
a: area.parent
b: triggerArea
}
MouseArea {
id: area
anchors.fill: parent
cursorShape: popup.open ? Qt.ArrowCursor : undefined
enabled: popup.open
hoverEnabled: true
parent: {
if (root.keepPopupAsChild)
return triggerArea;
const win = QsWindow.window;
const contentWin = win as Windows; // If inside the drawer content window, put it inside the interaction wrapper so hover works
return contentWin ? contentWin.interactionWrapper : (win as QsWindow).contentItem;
}
preventStealing: true
propagateComposedEvents: false
z: popup.animDriver > 0 ? 1 : 0
onClicked: popup.open = false
BlobPopup {
id: popup
color: open || hovered || stateLayer.containsMouse ? DynamicColors.palette.m3secondaryContainer : DynamicColors.palette.m3surfaceContainerHighest
content: root.content
hoverOverride: stateLayer.containsMouse
padding: Appearance.padding.small
pressOverride: stateLayer.pressed
x: {
tWatcher.transform;
return triggerArea.mapToItem(area.parent, 0, 0).x;
}
y: {
tWatcher.transform;
return triggerArea.mapToItem(area.parent, 0, 0).y;
}
}
}
}
}
}
@@ -0,0 +1,16 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
CustomText {
property bool first
Layout.bottomMargin: Appearance.spacing.extraSmall
Layout.fillWidth: true
Layout.leftMargin: Appearance.padding.small
Layout.topMargin: first ? 0 : Appearance.spacing.large - ((parent as ColumnLayout).spacing ?? 0)
color: DynamicColors.palette.m3onSurfaceVariant
elide: Text.ElideRight
font.pointSize: Appearance.font.size.medium
}
+64
View File
@@ -0,0 +1,64 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
ConnectedRect {
id: root
property alias active: splitButton.active
property alias fallbackIcon: splitButton.fallbackIcon
property alias fallbackText: splitButton.fallbackText
property alias menuItems: splitButton.menuItems
property alias menuOnTop: splitButton.menuOnTop
property string subtext
property alias text: label.text
signal selected(item: MenuItem)
Layout.fillWidth: true
clip: false
implicitHeight: rowLayout.implicitHeight + rowLayout.anchors.margins * 2
z: splitButton.expanded ? 1 : 0
RowLayout {
id: rowLayout
anchors.fill: parent
anchors.leftMargin: Appearance.padding.largeIncreased
anchors.margins: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.largeIncreased
spacing: Appearance.spacing.small
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
id: label
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.smaller
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: root.subtext
visible: root.subtext
}
}
CustomSplitButton {
id: splitButton
type: CustomSplitButton.Tonal
menu.onItemSelected: item => root.selected(item)
stateLayer.onClicked: splitButton.expanded = !splitButton.expanded
}
}
}
+81
View File
@@ -0,0 +1,81 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
ConnectedRect {
id: root
property alias icon: slider.insetIcon
property alias label: label.text
property real value
property alias valueLabel: valueLabel.text
signal moved(value: real)
Layout.fillWidth: true
implicitHeight: rowLayout.implicitHeight + rowLayout.anchors.margins + rowLayout.anchors.topMargin
RowLayout {
id: rowLayout
anchors.fill: parent
anchors.margins: Appearance.padding.largeIncreased
anchors.topMargin: Appearance.padding.large
spacing: Appearance.spacing.small
ColumnLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.small
RowLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.small
CustomText {
id: label
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
}
CustomText {
id: valueLabel
color: DynamicColors.palette.m3outline
font.pointSize: Appearance.font.size.small
}
}
CustomMouseArea {
function onWheel(event: WheelEvent): void {
const step = Config.services.audioIncrement;
if (event.angleDelta.y > 0)
root.moved(Math.min(1, root.value + step));
else if (event.angleDelta.y < 0)
root.moved(Math.max(0, root.value - step));
}
Layout.fillWidth: true
implicitHeight: Appearance.padding.larger * 3
CustomSlider {
id: slider
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
enabled: root.enabled
implicitHeight: parent.implicitHeight
radius: Appearance.rounding.small
value: root.value
onInteraction: v => root.moved(v)
}
}
}
}
}
+63
View File
@@ -0,0 +1,63 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
ConnectedRect {
id: root
property real from: 0
property real stepSize: 1
property string subtext
property alias text: label.text
property real to: 99
property real value
signal moved(value: real)
Layout.fillWidth: true
implicitHeight: rowLayout.implicitHeight + rowLayout.anchors.margins * 2
RowLayout {
id: rowLayout
anchors.fill: parent
anchors.leftMargin: Appearance.padding.largeIncreased
anchors.margins: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.largeIncreased
spacing: Appearance.spacing.small
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
id: label
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.smaller
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: root.subtext
visible: root.subtext
}
}
CustomSpinBox {
max: root.to
min: root.from
step: root.stepSize
value: root.value
onValueModified: v => root.moved(v)
}
}
}
+125
View File
@@ -0,0 +1,125 @@
import QtQuick
import QtQuick.Controls
import qs.Components
import qs.Modules.SettingsNew
import qs.Config
StackView {
id: root
readonly property int animMovement: Appearance.padding.extraLarge * 2
default property list<Component> pages
required property SettingsState sState
function openSubPage(idx: int, immediate: bool): void {
const page = pages[idx];
if (page) {
push(page, {
sState
}, immediate ? StackView.Immediate : StackView.PushTransition);
} else {
console.warn(logCat, "Attempted to open invalid sub-page index", idx);
sState.closeSubPage();
}
}
clip: busy
popEnter: Transition {
SequentialAnimation {
PropertyAction {
property: "opacity"
value: 0
}
PauseAnimation {
duration: Appearance.anim.durations.expressiveEffects
}
ParallelAnimation {
Anim {
property: "opacity"
to: 1
type: Anim.SlowEffects
}
Anim {
from: -root.animMovement
property: "x"
to: 0
type: Anim.SlowEffects
}
}
}
}
popExit: Transition {
Anim {
property: "opacity"
to: 0
type: Anim.DefaultEffects
}
}
pushEnter: Transition {
SequentialAnimation {
PropertyAction {
property: "opacity"
value: 0
}
PauseAnimation {
duration: Appearance.anim.durations.expressiveEffects
}
ParallelAnimation {
Anim {
property: "opacity"
to: 1
type: Anim.SlowEffects
}
Anim {
from: root.animMovement
property: "x"
to: 0
type: Anim.SlowEffects
}
}
}
}
pushExit: Transition {
Anim {
property: "opacity"
to: 0
type: Anim.DefaultEffects
}
}
Component.onCompleted: {
openSubPage(0, true);
for (const page of sState.subPageIdxStack)
openSubPage(page, true);
}
LoggingCategory {
id: logCat
defaultLogLevel: LoggingCategory.Info
name: "ZShell.settings"
}
Connections {
function onSubPageClosed(): void {
if (root.depth < root.sState.subPageIdxStack.length) {
console.log(logCat, "Attempted to close page while depth < stack depth. Ignoring.");
return;
}
root.pop();
}
function onSubPageOpened(idx: int): void {
root.openSubPage(idx, false);
}
target: root.sState
}
}
+585
View File
@@ -0,0 +1,585 @@
import QtQuick
import QtQuick.Layouts
import ZShell.Components
import qs.Config
import qs.Components
import qs.Helpers
CustomClippingRect {
id: root
required property var object
required property list<string> settings
property bool shouldBeActive: true
signal applySettings(startTime: int, endTime: int)
signal close
function convertHour(timeValue: int): int {
return Math.floor(timeValue / 60);
}
function convertMinute(timeValue: int): int {
return timeValue % 60;
}
function convertToMinutes(hour: int, minute: int): int {
return hour * 60 + minute;
}
color: DynamicColors.palette.m3surfaceContainer
implicitHeight: column.implicitHeight + column.anchors.margins * 2 + buttonRow.implicitHeight + buttonRow.anchors.topMargin
implicitWidth: column.implicitWidth + column.anchors.margins * 2
radius: Appearance.rounding.large
ColumnLayout {
id: column
anchors.left: parent.left
anchors.margins: Appearance.padding.largeIncreased
anchors.right: parent.right
anchors.top: parent.top
spacing: Appearance.spacing.normal
CustomText {
text: qsTr("Select time")
}
RowLayout {
CustomRect {
id: startHourRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: startHourField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Appearance.rounding.small
CustomRect {
anchors.fill: parent
border.color: startHourField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest
border.width: startHourField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {
}
}
}
CustomTextField {
id: startHourField
function setConfigText(setting: string): string {
var val = root.convertHour(root.object[setting]);
if (val === 0) {
return "00";
}
return String(val);
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
color: focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface
cursorHeight: height - Appearance.padding.normal * 2
font.family: "Roboto"
font.letterSpacing: -0.25
font.pixelSize: 56
font.weight: 400
horizontalAlignment: TextInput.AlignHCenter
text: setConfigText(root.settings[1])
verticalAlignment: TextInput.AlignVCenter
Keys.onPressed: event => {
if (event.key === Qt.Key_Backspace) {
event.accepted = true;
if (startHourField.text.length >= 2) {
startHourField.text = "0" + startHourField.text[0];
} else if (startHourField.text.length === 1) {
startHourField.text = "0";
}
return;
} else if (event.key === Qt.Key_Escape) {
event.accepted = true;
startHourField.text = setConfigText(root.settings[1]);
startHourField.focus = false;
} else if (event.key === Qt.Key_Return) {
startHourField.focus = false;
return;
} else if (event.key === Qt.Key_Tab) {
startMinuteField.focus = true;
} else if (event.key === Qt.Key_Backtab) {
endMinuteField.focus = true;
}
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
event.accepted = true;
var digit = event.text;
var textLen = startHourField.text.length;
if (textLen >= 2 && startHourField.text[0] !== '0') {
return;
}
var val = 0;
if (textLen === 0) {
val = parseInt(digit);
} else if (textLen === 1) {
val = parseInt(startHourField.text + digit);
} else {
val = parseInt(startHourField.text[1] + digit);
}
val = Math.max(0, Math.min(23, val));
if (textLen >= 2 && val < 10) {
startHourField.text = "0" + val;
} else {
startHourField.text = val.toString();
}
}
event.accepted = true;
}
onCursorPositionChanged: cursorPosition = 2
onTextEdited: {
if (startHourField.text === "")
return;
var val = parseInt(startHourField.text);
if (isNaN(val))
return;
val = Math.max(0, Math.min(23, val));
var newText = val.toString();
if (newText !== startHourField.text)
startHourField.text = newText;
}
}
}
CustomText {
id: startSeparator
font.pointSize: Appearance.font.size.extraLarge
text: ":"
}
CustomRect {
id: startMinuteRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: startMinuteField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Appearance.rounding.small
CustomRect {
anchors.fill: parent
border.color: startMinuteField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest
border.width: startMinuteField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {
}
}
}
CustomTextField {
id: startMinuteField
function setConfigText(setting: string): string {
var val = root.convertMinute(root.object[setting]);
if (val === 0) {
return "00";
}
return String(val);
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
color: focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface
cursorHeight: height - Appearance.padding.normal * 2
font.family: "Roboto"
font.letterSpacing: -0.25
font.pixelSize: 56
font.weight: 400
horizontalAlignment: TextInput.AlignHCenter
text: setConfigText(root.settings[1])
verticalAlignment: TextInput.AlignVCenter
Keys.onPressed: event => {
if (event.key === Qt.Key_Backspace) {
event.accepted = true;
if (startMinuteField.text.length >= 2) {
startMinuteField.text = "0" + startMinuteField.text[0];
} else if (startMinuteField.text.length === 1) {
startMinuteField.text = "0";
}
return;
} else if (event.key === Qt.Key_Escape) {
event.accepted = true;
startMinuteField.text = setConfigText(root.settings[1]);
startMinuteField.focus = false;
} else if (event.key === Qt.Key_Return) {
startMinuteField.focus = false;
return;
} else if (event.key === Qt.Key_Tab) {
endHourField.focus = true;
} else if (event.key === Qt.Key_Backtab) {
startHourField.focus = true;
}
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
event.accepted = true;
var digit = event.text;
var textLen = startMinuteField.text.length;
if (textLen >= 2 && startMinuteField.text[0] !== '0') {
return;
}
var val = 0;
if (textLen === 0) {
val = parseInt(digit);
} else if (textLen === 1) {
val = parseInt(startMinuteField.text + digit);
} else {
val = parseInt(startMinuteField.text[1] + digit);
}
val = Math.max(0, Math.min(59, val));
if (textLen >= 2 && val < 10) {
startMinuteField.text = "0" + val;
} else {
startMinuteField.text = val.toString();
}
}
event.accepted = true;
}
onCursorPositionChanged: cursorPosition = 2
onTextEdited: {
if (startMinuteField.text === "")
return;
var val = parseInt(startMinuteField.text);
if (isNaN(val))
return;
val = Math.max(0, Math.min(23, val));
var newText = val.toString();
if (newText !== startMinuteField.text)
startMinuteField.text = newText;
}
}
}
Item {
id: spacer
Layout.preferredWidth: Appearance.spacing.large
}
CustomRect {
id: endHourRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: endHourField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Appearance.rounding.small
CustomRect {
anchors.fill: parent
border.color: endHourField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest
border.width: endHourField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {
}
}
}
CustomTextField {
id: endHourField
function setConfigText(setting: string): string {
var val = root.convertHour(root.object[setting]);
if (val === 0) {
return "00";
}
return String(val);
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
color: focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface
cursorHeight: height - Appearance.padding.normal * 2
font.family: "Roboto"
font.letterSpacing: -0.25
font.pixelSize: 56
font.weight: 400
horizontalAlignment: TextInput.AlignHCenter
text: setConfigText(root.settings[2])
verticalAlignment: TextInput.AlignVCenter
Keys.onPressed: event => {
if (event.key === Qt.Key_Backspace) {
event.accepted = true;
if (endHourField.text.length >= 2) {
endHourField.text = "0" + endHourField.text[0];
} else if (endHourField.text.length === 1) {
endHourField.text = "0";
}
return;
} else if (event.key === Qt.Key_Escape) {
event.accepted = true;
endHourField.text = setConfigText(root.settings[2]);
endHourField.focus = false;
} else if (event.key === Qt.Key_Return) {
endHourField.focus = false;
return;
} else if (event.key === Qt.Key_Tab) {
endMinuteField.focus = true;
} else if (event.key === Qt.Key_Backtab) {
startMinuteField.focus = true;
}
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
event.accepted = true;
var digit = event.text;
var textLen = endHourField.text.length;
if (textLen >= 2 && endHourField.text[0] !== '0') {
return;
}
var val = 0;
if (textLen === 0) {
val = parseInt(digit);
} else if (textLen === 1) {
val = parseInt(endHourField.text + digit);
} else {
val = parseInt(endHourField.text[1] + digit);
}
val = Math.max(0, Math.min(23, val));
if (textLen >= 2 && val < 10) {
endHourField.text = "0" + val;
} else {
endHourField.text = val.toString();
}
}
event.accepted = true;
}
onCursorPositionChanged: cursorPosition = 2
onTextEdited: {
if (endHourField.text === "")
return;
var val = parseInt(endHourField.text);
if (isNaN(val))
return;
val = Math.max(0, Math.min(23, val));
var newText = val.toString();
if (newText !== endHourField.text)
endHourField.text = newText;
}
}
}
CustomText {
id: endSeparator
font.pointSize: Appearance.font.size.extraLarge
text: ":"
}
CustomRect {
id: endMinuteRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: endMinuteField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Appearance.rounding.small
CustomRect {
anchors.fill: parent
border.color: endMinuteField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest
border.width: endMinuteField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {
}
}
}
CustomTextField {
id: endMinuteField
function setConfigText(setting: string): string {
var val = root.convertMinute(root.object[setting]);
if (val === 0) {
return "00";
}
return String(val);
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
color: focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface
cursorHeight: height - Appearance.padding.normal * 2
font.family: "Roboto"
font.letterSpacing: -0.25
font.pixelSize: 56
font.weight: 400
horizontalAlignment: TextInput.AlignHCenter
text: setConfigText(root.settings[2])
verticalAlignment: TextInput.AlignVCenter
Keys.onPressed: event => {
if (event.key === Qt.Key_Backspace) {
event.accepted = true;
if (endMinuteField.text.length >= 2) {
endMinuteField.text = "0" + endMinuteField.text[0];
} else if (endMinuteField.text.length === 1) {
endMinuteField.text = "0";
}
return;
} else if (event.key === Qt.Key_Escape) {
event.accepted = true;
endMinuteField.text = setConfigText(root.settings[2]);
endMinuteField.focus = false;
} else if (event.key === Qt.Key_Return) {
endMinuteField.focus = false;
return;
} else if (event.key === Qt.Key_Tab) {
startHourField.focus = true;
} else if (event.key === Qt.Key_Backtab) {
endHourField.focus = true;
}
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
event.accepted = true;
var digit = event.text;
var textLen = endMinuteField.text.length;
if (textLen >= 2 && endMinuteField.text[0] !== '0') {
return;
}
var val = 0;
if (textLen === 0) {
val = parseInt(digit);
} else if (textLen === 1) {
val = parseInt(endMinuteField.text + digit);
} else {
val = parseInt(endMinuteField.text[1] + digit);
}
val = Math.max(0, Math.min(59, val));
if (textLen >= 2 && val < 10) {
endMinuteField.text = "0" + val;
} else {
endMinuteField.text = val.toString();
}
}
event.accepted = true;
}
onCursorPositionChanged: cursorPosition = 2
onTextEdited: {
if (endMinuteField.text === "")
return;
var val = parseInt(endMinuteField.text);
if (isNaN(val))
return;
val = Math.max(0, Math.min(23, val));
var newText = val.toString();
if (newText !== endMinuteField.text)
endMinuteField.text = newText;
}
}
}
}
RowLayout {
CustomText {
Layout.preferredWidth: spacer.x + spacer.width
text: qsTr("Start")
}
CustomText {
Layout.preferredWidth: endMinuteRect.width + endHourRect.width
text: qsTr("End")
}
}
}
RowLayout {
id: buttonRow
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: Appearance.padding.largeIncreased
anchors.right: parent.right
anchors.top: column.bottom
anchors.topMargin: Appearance.spacing.normal
Item {
id: buttonSpacer
Layout.fillWidth: true
}
ButtonRow {
spacing: Appearance.spacing.normal
IconTextButton {
font.pointSize: Appearance.font.size.normal
icon: "close"
inactiveColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2)
inactiveOnColor: DynamicColors.palette.m3onSurfaceVariant
isRound: true
isToggle: false
shapeMorph: true
shapeMorphExpansion: pressed ? 12 : 0
text: "Cancel"
onClicked: root.close()
}
IconTextButton {
font.pointSize: Appearance.font.size.normal
icon: "check"
isRound: true
isToggle: false
shapeMorph: true
shapeMorphExpansion: pressed ? 12 : 0
text: "Apply"
onClicked: {
const start = root.convertToMinutes(parseInt(startHourField.text)) + parseInt(startMinuteField.text);
const end = root.convertToMinutes(parseInt(endHourField.text)) + parseInt(endMinuteField.text);
root.applySettings(start, end);
}
}
}
}
}
+82
View File
@@ -0,0 +1,82 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
CustomSwitch {
id: root
readonly property alias bg: bg
property alias first: bg.first
property alias last: bg.last
property string subtext
Layout.fillWidth: true
cLayer: 2
horizontalPadding: Appearance.padding.largeIncreased
implicitHeight: Math.max(implicitContentHeight, implicitIndicatorHeight) + verticalPadding * 2
implicitWidth: implicitContentWidth + implicitIndicatorWidth + horizontalPadding * 2
indicator.anchors.right: right
indicator.anchors.rightMargin: root.horizontalPadding
indicator.anchors.verticalCenter: verticalCenter
verticalPadding: Appearance.padding.normal
background: ConnectedRect {
id: bg
StateLayer {
id: stateLayer
manualPressOverride: root.pressed
}
}
contentItem: Item {
anchors.left: parent.left
anchors.leftMargin: root.horizontalPadding
anchors.right: root.indicator.left
anchors.rightMargin: Appearance.spacing.normal
implicitHeight: column.implicitHeight
implicitWidth: column.implicitWidth
Column {
id: column
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 0
CustomText {
id: label
anchors.left: parent.left
anchors.right: parent.right
elide: Text.ElideRight
font: {
const f = Qt.font(label.font);
f.pointSize = Appearance.font.size.smaller;
return f;
}
text: root.text
}
CustomText {
id: subtext
anchors.left: parent.left
anchors.right: parent.right
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font: {
const f = Qt.font(subtext.font);
f.pointSize = Appearance.font.size.small;
return f;
}
text: root.subtext
visible: root.subtext
}
}
}
onPressed: stateLayer.press(stateLayer.mouseX, stateLayer.mouseY)
}
+62
View File
@@ -0,0 +1,62 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Components
import qs.Config
Item {
id: root
property alias imgHeight: imgWrapper.implicitHeight
property alias radius: imgWrapper.radius
property alias source: img.source
signal clicked
Layout.fillWidth: true
implicitHeight: layout.implicitHeight
ColumnLayout {
id: layout
anchors.fill: parent
spacing: Appearance.spacing.small
CustomClippingRect {
id: imgWrapper
Layout.fillWidth: true
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: width
radius: Appearance.rounding.large
Image {
id: img
anchors.fill: parent
asynchronous: true
fillMode: Image.PreserveAspectCrop
opacity: status === Image.Ready ? 1 : 0
retainWhileLoading: true
sourceSize: {
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
return Qt.size(width * dpr, height * dpr);
}
Behavior on opacity {
Anim {
type: Anim.SlowEffects
}
}
}
}
}
StateLayer {
anchors.bottomMargin: layout.implicitHeight - imgWrapper.implicitHeight
onClicked: root.clicked()
}
}
@@ -0,0 +1,342 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import ZShell.Components
import qs.Config
import qs.Components
import qs.Helpers
Item {
id: wrapper
property int buttonRowHeight: Appearance.padding.large * 3
property bool changesMade: false
readonly property var currentScreen: screens.length > selectedScreenIndex ? screens[selectedScreenIndex] : null
property var screens: []
property int selectedScreenIndex: 0
property bool shouldBeActive: true
function applyCrop(): void {
if (!cropRectLoader.item || !currentScreen)
return;
const cropRect = cropRectLoader.item;
const cropXPercent = (cropRect.x - cropRect.imageX) / scaledImg.paintedWidth;
const cropYPercent = (cropRect.y - cropRect.imageY) / scaledImg.paintedHeight;
const cropWidthPercent = cropRect.width / scaledImg.paintedWidth;
const cropHeightPercent = cropRect.height / scaledImg.paintedHeight;
Wallpapers.setCrop(currentScreen.name, Qt.rect(cropXPercent, cropYPercent, cropWidthPercent, cropHeightPercent), cropRect.zoom);
}
function refreshScreens(): void {
screens = [...Quickshell.screens].sort((a, b) => a.x - b.x);
selectedScreenIndex = 0;
}
function selectScreen(index: int): void {
if (index < 0 || index >= screens.length || index === selectedScreenIndex)
return;
selectedScreenIndex = index;
if (cropRectLoader.item)
Qt.callLater(syncCropToScreen);
}
function syncCropToScreen(): void {
if (cropRectLoader.item)
cropRectLoader.item.restoreFromData();
}
function zoomClipRect(zoom: real): void {
if (!cropRectLoader.item)
return;
const cropRect = cropRectLoader.item;
const centerX = cropRect.x + cropRect.width * 0.5;
const centerY = cropRect.y + cropRect.height * 0.5;
cropRect.zoom = zoom;
cropRect.x = centerX - cropRect.width * 0.5;
cropRect.y = centerY - cropRect.height * 0.5;
cropRect.clampToBounds();
}
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: shouldBeActive ? 430 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Component.onCompleted: {
refreshScreens();
Qt.callLater(syncCropToScreen);
}
onSelectedScreenIndexChanged: {
Qt.callLater(syncCropToScreen);
}
IconButton {
anchors.margins: Appearance.padding.normal
anchors.right: parent.right
anchors.top: parent.top
icon: "check"
opacity: wrapper.changesMade ? 1 : 0
scale: wrapper.changesMade ? 1 : 0
z: 2
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
onClicked: {
wrapper.applyCrop();
wrapper.changesMade = false;
}
}
ButtonRow {
id: screenSelector
anchors.bottom: scaledImg.top
anchors.bottomMargin: Appearance.spacing.normal
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.extraLarge * 2
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.extraLarge * 2
anchors.top: parent.top
spacing: Appearance.spacing.small
Repeater {
model: wrapper.screens
delegate: TextButton {
required property int index
readonly property bool isCurrent: wrapper.selectedScreenIndex === index
required property var modelData
fillWidth: true
inactiveColor: isCurrent ? DynamicColors.palette.m3primary : Qt.alpha(DynamicColors.palette.m3surfaceContainer, 0.7)
inactiveOnColor: isCurrent ? DynamicColors.palette.m3onPrimary : Qt.alpha(DynamicColors.palette.m3onSurface, 0.7)
isRound: true
shapeMorph: true
text: modelData.name
onClicked: wrapper.selectScreen(index)
}
}
}
RowLayout {
id: sliderLayout
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: 30
CustomSlider {
id: zoomSlider
Layout.fillWidth: true
Layout.leftMargin: Appearance.padding.normal
Layout.preferredHeight: Appearance.padding.larger * 3
Layout.rightMargin: Appearance.padding.normal
from: 1.0
implicitHeight: Appearance.padding.larger * 3
insetIcon: "crop"
to: 5.0
value: cropRectLoader.item ? cropRectLoader.item.zoom : 1.0
onInteraction: value => {
wrapper.zoomClipRect(1 + (value * 4));
wrapper.changesMade = true;
}
}
}
Image {
id: scaledImg
property var displayData
property real monitorScale: 1.0
anchors.bottom: sliderLayout.top
anchors.bottomMargin: Appearance.spacing.normal
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: wrapper.buttonRowHeight + Appearance.spacing.normal
// anchors.top: screenSelector.bottom
asynchronous: true
fillMode: Image.PreserveAspectFit
retainWhileLoading: true
source: Wallpapers.current
sourceSize.height: parent.height
sourceSize.width: parent.width
onPaintedWidthChanged: {
if (paintedWidth > 0 && cropRectLoader.item) {
if (!wrapper.screens.length)
wrapper.refreshScreens();
wrapper.syncCropToScreen();
}
}
onSourceChanged: {
if (cropRectLoader.item) {
if (!wrapper.screens.length)
wrapper.refreshScreens();
wrapper.syncCropToScreen();
}
}
onStatusChanged: {
if (scaledImg.status == Image.Ready && cropRectLoader.item) {
if (!wrapper.screens.length)
wrapper.refreshScreens();
wrapper.syncCropToScreen();
}
}
Loader {
id: cropRectLoader
active: scaledImg.status === Image.Ready
sourceComponent: Component {
CustomRect {
id: cropRect
property real aspectRatio: wrapper.currentScreen ? wrapper.currentScreen.width / wrapper.currentScreen.height : 1
readonly property real baseHeight: baseWidth / aspectRatio
readonly property real baseWidth: {
let fittedHeight = scaledImg.paintedHeight;
let fittedWidth = fittedHeight * aspectRatio;
if (fittedWidth > scaledImg.paintedWidth) {
fittedWidth = scaledImg.paintedWidth;
fittedHeight = fittedWidth / aspectRatio;
}
return fittedWidth;
}
readonly property real imageX: (scaledImg.width - scaledImg.paintedWidth) / 2
readonly property real imageY: (scaledImg.height - scaledImg.paintedHeight) / 2
property real imgAspectRatio: scaledImg.paintedWidth / scaledImg.paintedHeight
property real zoom: 1.0
function centerInImage() {
x = imageX + (scaledImg.paintedWidth - width) / 2;
y = imageY + (scaledImg.paintedHeight - height) / 2;
}
function clampToBounds() {
x = Math.max(imageX, Math.min(x, imageX + scaledImg.paintedWidth - width));
y = Math.max(imageY, Math.min(y, imageY + scaledImg.paintedHeight - height));
}
function restoreFromData() {
let data = Wallpapers.getCrop(wrapper.currentScreen.name);
console.log(data.x, data.y);
if (data && (Math.abs(data.x) > 0.001 || Math.abs(data.y) > 0.001 || Math.abs(data.width - 1.0) > 0.001 || Math.abs(data.height - 1.0) > 0.001)) {
zoom = data.zoom > 0 ? data.zoom : 1.0;
x = imageX + (data.x * scaledImg.paintedWidth);
y = imageY + (data.y * scaledImg.paintedHeight);
clampToBounds();
} else {
zoom = 1.0;
centerInImage();
}
}
border.color: DynamicColors.palette.m3primary
border.width: 2
height: baseHeight / zoom
opacity: 1
width: baseWidth / zoom
Behavior on opacity {
Anim {
}
}
Component.onCompleted: {
restoreFromData();
}
onHeightChanged: clampToBounds()
onWidthChanged: clampToBounds()
}
}
}
MouseArea {
id: mouse
function updateCrop(mouseX, mouseY) {
if (!cropRectLoader.item)
return;
const cropRect = cropRectLoader.item;
let nx = mouseX - cropRect.width * 0.5;
let ny = mouseY - cropRect.height * 0.5;
nx = Math.max(cropRect.imageX, Math.min(nx, cropRect.imageX + scaledImg.paintedWidth - cropRect.width));
ny = Math.max(cropRect.imageY, Math.min(ny, cropRect.imageY + scaledImg.paintedHeight - cropRect.height));
cropRect.x = nx;
cropRect.y = ny;
}
anchors.fill: parent
hoverEnabled: true
preventStealing: true
onPositionChanged: mouse => {
if (pressed) {
updateCrop(mouse.x, mouse.y);
wrapper.changesMade = true;
}
}
onPressed: mouse => {
updateCrop(mouse.x, mouse.y);
wrapper.changesMade = true;
}
onReleased: {
wrapper.changesMade = true;
}
}
}
}
+72
View File
@@ -0,0 +1,72 @@
pragma ComponentBehavior: Bound
import QtQuick
import ZShell.Blobs
import qs.Components
import qs.Config
CustomClippingRect {
id: root
property color blobColor: DynamicColors.tPalette.m3surfaceContainerLow
readonly property real ratio: 16.0 / 9.0
readonly property SettingsState sState: SettingsState {
id: sState
onClose: root.close()
}
signal close
implicitHeight: sState.screen.height * 0.7
implicitWidth: implicitHeight * ratio
radius: Appearance.rounding.large + Appearance.padding.normal
Behavior on blobColor {
CAnim {
}
}
BlobGroup {
id: blobGroup
color: root.blobColor
smoothing: Appearance.rounding.normal
}
BlobInvertedRect {
anchors.fill: parent
borderBottom: Appearance.padding.normal
borderLeft: navPane.width + navPane.anchors.margins * 2
borderRight: Appearance.padding.normal
borderTop: Appearance.padding.normal
group: blobGroup
opacity: root.blobColor.a
radius: Appearance.rounding.large
}
NavPane {
id: navPane
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: Appearance.padding.large
anchors.top: parent.top
sState: root.sState
width: Math.min(600, Math.round(root.width / 3))
}
Pages {
anchors.bottom: parent.bottom
anchors.left: navPane.right
anchors.leftMargin: navPane.anchors.margins + anchors.margins
anchors.margins: Appearance.padding.extraLarge
anchors.right: parent.right
anchors.top: parent.top
sState: root.sState
}
PopupOverlay {
anchors.fill: parent
}
}
+25
View File
@@ -0,0 +1,25 @@
import QtQuick
import QtQuick.Layouts
import qs.Modules.SettingsNew.NavPane
import qs.Config
ColumnLayout {
id: root
required property SettingsState sState
spacing: Appearance.spacing.large
SearchBar {
Layout.fillWidth: true
sState: root.sState
}
NavLocations {
Layout.bottomMargin: -bottomMargin
Layout.fillHeight: true
Layout.fillWidth: true
Layout.topMargin: -topMargin
sState: root.sState
}
}
@@ -0,0 +1,126 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Modules.SettingsNew
VerticalFadeFlickable {
id: root
required property SettingsState sState
bottomMargin: Appearance.padding.large
contentHeight: content.implicitHeight
fadeAmount: 0.1
topMargin: Appearance.padding.large
ColumnLayout {
id: content
anchors.left: parent.left
anchors.right: parent.right
spacing: Appearance.spacing.extraSmall
Repeater {
id: list
model: PageRegistry.pages
CustomRect {
id: item
required property int index
readonly property bool isCategoryEnd: index === list.model.length - 1 || PageRegistry.pages[index + 1].category !== modelData.category
readonly property bool isCategoryStart: index === 0 || PageRegistry.pages[index - 1].category !== modelData.category
readonly property bool isCurrentPage: index === root.sState.currentPageIdx
required property var modelData
Layout.fillWidth: true
Layout.topMargin: index !== 0 && isCategoryStart ? Appearance.spacing.small : 0
bottomLeftRadius: stateLayer.pressed ? Appearance.rounding.medium : isCurrentPage ? Appearance.rounding.large : isCategoryEnd ? Appearance.rounding.normal : Appearance.rounding.extraSmall
bottomRightRadius: stateLayer.pressed ? Appearance.rounding.medium : isCurrentPage ? Appearance.rounding.large : isCategoryEnd ? Appearance.rounding.normal : Appearance.rounding.extraSmall
color: isCurrentPage ? DynamicColors.palette.m3secondaryContainer : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
implicitHeight: {
const h = layout.implicitHeight + layout.anchors.margins * 2;
return h % 2 === 0 ? h : h + 1;
}
topLeftRadius: stateLayer.pressed ? Appearance.rounding.medium : isCurrentPage ? Appearance.rounding.large : isCategoryStart ? Appearance.rounding.normal : Appearance.rounding.extraSmall
topRightRadius: stateLayer.pressed ? Appearance.rounding.medium : isCurrentPage ? Appearance.rounding.large : isCategoryStart ? Appearance.rounding.normal : Appearance.rounding.extraSmall
RadiusBehavior on bottomLeftRadius {
}
RadiusBehavior on bottomRightRadius {
}
RadiusBehavior on topLeftRadius {
}
RadiusBehavior on topRightRadius {
}
StateLayer {
id: stateLayer
anchors.fill: parent
bottomLeftRadius: parent.bottomLeftRadius
bottomRightRadius: parent.bottomRightRadius
topLeftRadius: parent.topLeftRadius
topRightRadius: parent.topRightRadius
onClicked: root.sState.currentPageIdx = item.index
}
RowLayout {
id: layout
anchors.fill: parent
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.small
CustomRect {
Layout.bottomMargin: -1
Layout.fillHeight: true
Layout.topMargin: -1
color: item.isCurrentPage ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondaryContainer
implicitWidth: height
radius: Appearance.rounding.full
MaterialIcon {
anchors.centerIn: parent
anchors.verticalCenterOffset: 1
color: item.isCurrentPage ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSecondaryContainer
fill: item.modelData.noFill ? 0 : 1
font.pointSize: Appearance.font.size.large
grade: 25
text: item.modelData.icon
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
text: item.modelData.name
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurfaceVariant
elide: Text.ElideRight
text: item.modelData.description
}
}
}
}
}
}
component RadiusBehavior: Behavior {
Anim {
type: Anim.DefaultEffects
}
}
}
+75
View File
@@ -0,0 +1,75 @@
import QtQuick
import QtQuick.Layouts
import qs.Modules.SettingsNew
import qs.Components
import qs.Config
CustomRect {
id: root
required property SettingsState sState
border.color: DynamicColors.palette.m3outlineVariant
color: DynamicColors.tPalette.m3surfaceContainerLowest
implicitHeight: searchLayout.implicitHeight + Appearance.padding.normal * 2
radius: Appearance.rounding.full
Behavior on border.color {
CAnim {
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.IBeamCursor
onClicked: searchField.focus = true
}
RowLayout {
id: searchLayout
anchors.left: parent.left
anchors.margins: Appearance.padding.large
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.small
MaterialIcon {
color: DynamicColors.palette.m3onSurfaceVariant
text: "search"
}
CustomTextField {
id: searchField
Layout.fillHeight: true
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurfaceVariant
placeholderText: qsTr("Search settings")
placeholderTextColor: DynamicColors.palette.m3onSurfaceVariant
Binding {
property: "searchOpen"
target: root.sState
value: searchField.text.length > 0
}
}
IconButton {
icon: "close"
isRound: true
opacity: searchField.text.length > 0 ? 1 : 0
padding: Appearance.padding.extraSmall
type: IconButton.Text
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
onClicked: searchField.clear()
}
}
}
+187
View File
@@ -0,0 +1,187 @@
pragma Singleton
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Modules.SettingsNew.Common
import qs.Modules.SettingsNew.Pages
import qs.Modules.SettingsNew.Pages.Wallpaper
import qs.Modules.SettingsNew.Pages.Audio
import qs.Modules.SettingsNew.Pages.Apps
import qs.Modules.SettingsNew.Pages.Panels
import qs.Modules.SettingsNew.Pages.Panels.Bar
import qs.Modules.SettingsNew.Pages.Services
QtObject {
id: root
readonly property list<Component> pageComps: [
// Appearance
Component {
// Wallpaper & style
StackPage {
Component {
Wallpaper {
}
}
Component {
WallpaperSelect {
}
}
}
},
Component {
// Screenshot
StackPage {
Component {
Screenshot {
}
}
}
},
// Connectivity
Component {
PlaceholderComp {
}
},
Component {
PlaceholderComp {
}
},
Component {
// Audio
StackPage {
Component {
AudioPage {
}
}
Component {
AppVolumes {
}
}
}
},
// Shell
Component {
StackPage {
Component {
PanelsPage {
}
}
Component {
BarPanel {
}
}
Component {
DashboardPanel {
}
}
Component {
ResourcesPanel {
}
}
Component {
LauncherPanel {
}
}
Component {
SidebarPanel {
}
}
// Bar sub pages
Component {
BarTray {
}
}
Component {
BarStatusIcons {
}
}
Component {
BarClock {
}
}
}
},
// Apps
Component {
StackPage {
Component {
AppsPage {
}
}
Component {
AllApps {
}
}
Component {
AppInfo {
}
}
}
},
// Services
Component {
StackPage {
Component {
ServicesPage {
}
}
Component {
NotificationsPage {
}
}
}
}
]
readonly property Component placeholderComp: Component {
PlaceholderComp {
}
}
component PlaceholderComp: Item {
property SettingsState sState // To avoid the warning from non-existent property
ColumnLayout {
anchors.centerIn: parent
spacing: Appearance.padding.extraSmall
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3outlineVariant
font.pointSize: Appearance.font.size.extraLarge
text: "handyman"
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3outlineVariant
text: qsTr("Page under construction")
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3outlineVariant
text: qsTr("This page will be available in a future update.")
}
}
}
}
+64
View File
@@ -0,0 +1,64 @@
pragma Singleton
import QtQuick
import Quickshell
Singleton {
id: root
readonly property list<var> pages: [
// Appearance
{
name: qsTr("Appearance"),
icon: "colors",
description: qsTr("Colors, animations, font, wallpaper"),
category: "appearance"
},
{
name: qsTr("Screenshot"),
icon: "screenshot_region",
description: qsTr("Set screenshot effects"),
category: "appearance"
},
// Connectivity
{
name: qsTr("Network"),
icon: "wifi",
description: qsTr("Wi-Fi, ethernet"),
category: "connectivity"
},
{
name: qsTr("Connected devices"),
icon: "devices_other",
description: qsTr("Bluetooth, pairing"),
category: "connectivity"
},
{
name: qsTr("Audio"),
icon: "volume_up",
description: qsTr("App volumes, sound devices"),
category: "connectivity"
},
// Shell
{
name: qsTr("Panels"),
icon: "dock_to_bottom",
description: qsTr("Bar, dashboard, launcher, sidebar"),
category: "shell"
},
{
name: qsTr("Apps"),
icon: "apps",
description: qsTr("Default apps, favorites, hidden apps"),
category: "shell"
},
{
name: qsTr("Services"),
icon: "build",
description: qsTr("Poll intervals, audio and brightness increments"),
category: "shell"
},
]
}
+100
View File
@@ -0,0 +1,100 @@
import QtQuick
import qs.Components
import qs.Config
Item {
id: root
property int animOff
property Item currentItem
property int lastPageIdx
required property SettingsState sState
function loadPage(idx: int): void {
if (currentItem)
currentItem.destroy();
const comp = PageCompRegistry.pageComps[idx] ?? PageCompRegistry.placeholderComp;
const incubator = comp.incubateObject(container, {
sState
});
const attach = () => {
incubator.object.anchors.fill = container;
currentItem = incubator.object;
};
if (incubator.status === Component.Ready)
attach();
else
incubator.onStatusChanged = status => {
if (status === Component.Ready)
attach();
};
}
Item {
id: container
anchors.fill: parent
layer.enabled: opacity < 1
objectName: "PageContainer"
Component.onCompleted: root.loadPage(root.sState.currentPageIdx)
}
Connections {
function onCurrentPageIdxChanged(): void {
switchAnim.complete();
root.animOff = Appearance.padding.normal * (root.sState.currentPageIdx > root.lastPageIdx ? 1 : -1);
switchAnim.start();
root.lastPageIdx = root.sState.currentPageIdx;
}
target: root.sState
}
SequentialAnimation {
id: switchAnim
Anim {
property: "opacity"
target: container
to: 0
type: Anim.DefaultEffects
}
ScriptAction {
script: root.loadPage(root.sState.currentPageIdx)
}
PropertyAction {
property: "topMargin"
target: container.anchors
value: root.animOff
}
PropertyAction {
property: "bottomMargin"
target: container.anchors
value: -root.animOff
}
ParallelAnimation {
Anim {
from: 0
property: "opacity"
target: container
to: 1
type: Anim.SlowEffects
}
Anim {
properties: "topMargin,bottomMargin"
target: container.anchors
to: 0
type: Anim.SlowEffects
}
}
}
}
+100
View File
@@ -0,0 +1,100 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import qs.Components
import qs.Config
import qs.Helpers
import qs.Modules.SettingsNew.Common
PageBase {
id: root
isSubPage: true
title: qsTr("All apps")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
Repeater {
id: list
model: [...DesktopEntries.applications.values].sort((a, b) => a.name.localeCompare(b.name))
ConnectedRect {
id: appItem
required property int index
required property DesktopEntry modelData
Layout.fillWidth: true
first: index === 0
implicitHeight: appRow.implicitHeight + appRow.anchors.margins * 2
last: index === list.count - 1
StateLayer {
onClicked: {
root.sState.selectedApp = appItem.modelData;
root.sState.openSubPage(2);
}
}
RowLayout {
id: appRow
anchors.fill: parent
anchors.leftMargin: Appearance.padding.largeIncreased
anchors.margins: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.largeIncreased
spacing: Appearance.spacing.small
IconImage {
asynchronous: true
implicitSize: Math.round(Appearance.font.size.large * 1.8)
source: Quickshell.iconPath(appItem.modelData.icon, "image-missing")
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: appItem.modelData.name
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: (appItem.modelData.comment || appItem.modelData.genericName) ?? ""
visible: text
}
}
MaterialIcon {
color: DynamicColors.palette.m3primary
fill: 1
font.pointSize: Appearance.font.size.small
text: "favorite"
visible: Strings.testRegexList(Config.launcher.favoriteApps, appItem.modelData.id)
}
MaterialIcon {
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.medium
text: "chevron_right"
}
}
}
}
}
}
+172
View File
@@ -0,0 +1,172 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import qs.Components
import qs.Config
import qs.Helpers
import qs.Modules.SettingsNew.Common
PageBase {
id: root
readonly property DesktopEntry app: sState.selectedApp
readonly property bool favoriteByRegex: app && matchedByRegex(Config.launcher.favoriteApps, app.id)
readonly property bool hiddenByRegex: app && matchedByRegex(Config.launcher.hiddenApps, app.id)
function isRegexEntry(s: string): bool {
return /^\^.*\$$/.test(s);
}
function matchedByRegex(filterList: list<string>, id: string): bool {
return filterList.some(f => isRegexEntry(f) && new RegExp(f).test(id));
}
isSubPage: true
title: qsTr("App info")
onAppChanged: {
// Auto close when app lost
if (!app)
sState.closeSubPage();
}
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
// Header
RowLayout {
Layout.bottomMargin: Appearance.spacing.large
Layout.fillWidth: true
Layout.leftMargin: Appearance.padding.small
spacing: Appearance.spacing.large
IconImage {
asynchronous: true
implicitSize: Math.round(Appearance.font.size.large * 3)
source: Quickshell.iconPath(root.app?.icon, "image-missing")
}
ColumnLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.extraSmall / 2
CustomText {
Layout.fillWidth: true
font.pointSize: Appearance.font.size.medium
text: root.app?.name ?? ""
wrapMode: Text.WordWrap
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3outline
font.pointSize: Appearance.font.size.small
text: (root.app?.comment || root.app?.genericName) ?? ""
visible: text
wrapMode: Text.WordWrap
}
}
}
// Launcher
SectionHeader {
first: true
text: qsTr("Launcher")
}
ToggleRow {
checked: root.app && Strings.testRegexList(Config.launcher.favoriteApps, root.app.id)
enabled: !root.favoriteByRegex
first: true
subtext: root.favoriteByRegex ? qsTr("Matched by a regex in favoriteApps — edit the config file to change") : qsTr("Pin to the bottom of the launcher")
text: qsTr("Favorite")
onToggled: {
const apps = Config.launcher.favoriteApps;
Config.launcher.favoriteApps = checked ? [...apps, root.app.id] : apps.filter(a => a !== root.app.id);
}
}
ToggleRow {
checked: root.app && Strings.testRegexList(Config.launcher.hiddenApps, root.app.id)
enabled: !root.hiddenByRegex
last: true
subtext: root.hiddenByRegex ? qsTr("Matched by a regex in hiddenApps — edit the config file to change") : qsTr("Hide from the launcher")
text: qsTr("Hidden")
onToggled: {
const apps = Config.launcher.hiddenApps;
Config.launcher.hiddenApps = checked ? [...apps, root.app.id] : apps.filter(a => a !== root.app.id);
}
}
// Details
SectionHeader {
text: qsTr("Details")
}
WrapInfoRow {
id: appId
first: true
label: qsTr("App ID")
labelComp.Layout.preferredWidth: Math.max(labelComp.implicitWidth, command.labelComp.implicitWidth)
value: root.app?.id ?? ""
}
WrapInfoRow {
id: command
label: qsTr("Command")
labelComp.Layout.preferredWidth: Math.max(labelComp.implicitWidth, appId.labelComp.implicitWidth)
last: true
value: (root.app?.command ?? []).join(" ")
}
}
component WrapInfoRow: ConnectedRect {
id: row
property alias label: label.text
readonly property alias labelComp: label
property alias value: value.text
Layout.fillWidth: true
implicitHeight: rowLayout.implicitHeight + rowLayout.anchors.margins * 2
RowLayout {
id: rowLayout
anchors.fill: parent
anchors.leftMargin: Appearance.padding.largeIncreased
anchors.margins: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.largeIncreased
spacing: Appearance.spacing.small
CustomText {
id: label
Layout.alignment: Qt.AlignTop
font.pointSize: Appearance.font.size.small
}
Item {
Layout.fillWidth: true
}
CustomText {
id: value
Layout.fillWidth: true
Layout.maximumWidth: implicitWidth + 1 // Whyyyyyyyyy
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.small
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
}
}
}
}
+177
View File
@@ -0,0 +1,177 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import ZShell
import qs.Helpers
import qs.Components
import qs.Config
import qs.Modules.SettingsNew.Common
PageBase {
id: root
title: qsTr("Apps")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
// Default applications
SectionHeader {
first: true
text: qsTr("Default applications")
}
DefaultRow {
first: true
icon: "terminal"
status: Config.general.apps.terminal.join(" ")
text: qsTr("Terminal")
onSelected: app => Config.general.apps.terminal = app.command
}
DefaultRow {
icon: "volume_up"
status: Config.general.apps.audio.join(" ")
text: qsTr("Audio")
onSelected: app => Config.general.apps.audio = app.command
}
DefaultRow {
icon: "play_circle"
status: Config.general.apps.playback.join(" ")
text: qsTr("Media playback")
onSelected: app => Config.general.apps.playback = app.command
}
DefaultRow {
icon: "folder"
last: true
status: Config.general.apps.explorer.join(" ")
text: qsTr("File manager")
onSelected: app => Config.general.apps.explorer = app.command
}
// Library
SectionHeader {
text: qsTr("Library")
}
NavRow {
first: true
icon: "apps"
last: true
status: qsTr("Browse installed apps, set favorites and hidden")
text: qsTr("All apps")
onClicked: root.sState.openSubPage(1)
}
}
component DefaultRow: PopupRow {
id: row
readonly property int popupHeight: root.flickable.height - y + root.flickable.contentY - Appearance.padding.large - Appearance.padding.extraLarge
signal selected(app: DesktopEntry)
keepPopupAsChild: {
if (root.sState.animatingContainer || root.opacity < 1)
return true;
let p = root.parent;
while (p && p.objectName !== "PageContainer")
p = p.parent;
return p?.opacity < 1;
}
popup.topMovement: Appearance.padding.large
Loader {
active: row.popup.animDriver > 0
anchors.centerIn: parent
sourceComponent: VerticalFadeListView {
id: list
fadeAmount: 0.05
implicitHeight: ZUtils.clamp(row.popupHeight, 200, 800)
implicitWidth: 300
model: {
const apps = [...DesktopEntries.applications.values];
const favorited = new Set(apps.filter(a => Strings.testRegexList(Config.launcher.favoriteApps, a.id)));
return apps.sort((a, b) => (favorited.has(b) - favorited.has(a)) || a.name.localeCompare(b.name));
}
delegate: StateLayer {
id: appItem
required property int index
required property DesktopEntry modelData
anchors.fill: undefined
anchors.left: list.contentItem.left
anchors.right: list.contentItem.right
implicitHeight: itemLayout.implicitHeight + itemLayout.anchors.margins * 2
radius: Appearance.rounding.small
onClicked: {
row.popup.open = false;
row.selected(modelData);
}
RowLayout {
id: itemLayout
anchors.fill: parent
anchors.margins: Appearance.padding.normal
spacing: Appearance.spacing.small
IconImage {
asynchronous: true
implicitSize: Math.round(Appearance.font.size.large * 1.8)
source: Quickshell.iconPath(appItem.modelData.icon, "image-missing")
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: appItem.modelData.name
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: (appItem.modelData.comment || appItem.modelData.genericName) ?? ""
visible: text
}
}
MaterialIcon {
color: DynamicColors.palette.m3primary
fill: 1
font.pointSize: Appearance.font.size.small
text: "favorite"
visible: Strings.testRegexList(Config.launcher.favoriteApps, appItem.modelData.id)
}
}
}
}
}
}
}
@@ -0,0 +1,69 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.Pipewire
import qs.Modules.SettingsNew.Common
import qs.Helpers
import qs.Components
import qs.Config
import qs.Daemons
PageBase {
id: root
isSubPage: true
title: qsTr("App volumes")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
CustomText {
Layout.bottomMargin: Appearance.spacing.small
Layout.fillWidth: true
Layout.leftMargin: Appearance.padding.small
color: DynamicColors.palette.m3outline
font.pointSize: Appearance.font.size.small
text: qsTr("Adjust the volume of individual apps currently playing audio.")
wrapMode: Text.WordWrap
}
ItemList {
id: streamList
color: list.count === 0 ? DynamicColors.tPalette.m3surfaceContainer : "transparent"
first: true
last: true
list.spacing: Appearance.spacing.extraSmall / 2
placeholderIcon: "music_off"
placeholderText: qsTr("No apps playing audio")
showList: true
delegate: SliderRow {
id: stream
required property int index
required property PwNode modelData
anchors.left: streamList.list.contentItem.left
anchors.right: streamList.list.contentItem.right
enabled: !stream.modelData?.audio?.muted
first: index === 0
icon: Icons.getVolumeIcon(stream.modelData?.audio?.volume ?? 0, stream.modelData?.audio?.muted ?? false)
label: Audio.getStreamName(stream.modelData)
last: index === streamList.list.count - 1
value: stream.modelData?.audio?.volume ?? 0
valueLabel: Math.round(value * 100) + "%"
onMoved: v => Audio.setStreamVolume(stream.modelData, v)
}
model: ScriptModel {
values: [...Audio.streams]
}
}
}
}
+136
View File
@@ -0,0 +1,136 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Daemons
import qs.Config
import qs.Components
import qs.Modules.SettingsNew.Common
import qs.Helpers
PageBase {
id: root
title: qsTr("Audio")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
// Output
SliderRow {
enabled: !Audio.muted
first: true
icon: Icons.getVolumeIcon(Audio.volume, Audio.muted)
label: qsTr("Output")
value: Audio.volume
valueLabel: Math.round(value * 100) + "%"
onMoved: v => Audio.setVolume(v)
}
ToggleRow {
checked: Audio.muted
text: qsTr("Muted")
onToggled: Audio.setStreamMuted(Audio.sink, checked)
}
AudioDeviceList {
currentId: Audio.sink?.id ?? -1
iconName: "speaker"
nodes: Audio.sinks
placeholderIcon: "speaker"
placeholderText: qsTr("No output devices")
onSelected: node => Audio.setAudioSink(node)
}
// Input
SliderRow {
Layout.topMargin: Appearance.spacing.large - parent.spacing
enabled: !Audio.sourceMuted
first: true
icon: Icons.getMicVolumeIcon(Audio.sourceVolume, Audio.sourceMuted)
label: qsTr("Input")
value: Audio.sourceVolume
valueLabel: Math.round(value * 100) + "%"
onMoved: v => Audio.setSourceVolume(v)
}
ToggleRow {
checked: Audio.sourceMuted
text: qsTr("Muted")
onToggled: Audio.setStreamMuted(Audio.source, checked)
}
AudioDeviceList {
currentId: Audio.source?.id ?? -1
iconName: "mic"
nodes: Audio.sources
placeholderIcon: "mic_off"
placeholderText: qsTr("No input devices")
onSelected: node => Audio.setAudioSource(node)
}
// Per-app volumes
ConnectedRect {
Layout.fillWidth: true
Layout.topMargin: Appearance.spacing.large - parent.spacing
first: true
implicitHeight: appLayout.implicitHeight + appLayout.anchors.margins * 2
last: true
StateLayer {
onClicked: root.sState.openSubPage(1)
}
RowLayout {
id: appLayout
anchors.fill: parent
anchors.leftMargin: Appearance.padding.largeIncreased
anchors.margins: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.largeIncreased
spacing: Appearance.spacing.small
MaterialIcon {
font.pointSize: Appearance.font.size.medium
text: "tune"
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: qsTr("App volumes")
}
CustomText {
Layout.fillWidth: true
animate: true
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: Audio.streams.length === 0 ? qsTr("No apps playing audio") : Audio.streams.length === 1 ? qsTr("1 app playing audio") : qsTr("%1 apps playing audio").arg(Audio.streams.length)
}
}
MaterialIcon {
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.medium
text: "chevron_right"
}
}
}
}
}
@@ -0,0 +1,61 @@
pragma ComponentBehavior: Bound
import QtQuick.Layouts
import qs.Config
import qs.Components
import qs.Modules.SettingsNew.Common
PageBase {
id: root
readonly property list<MenuItem> clockFormats: [
MenuItem {
text: qsTr("Long format")
value: "dddd d MMM - hh:mm"
},
MenuItem {
text: qsTr("Short format")
value: "ddd d MMM - hh:mm"
},
MenuItem {
text: qsTr("Time only")
value: "hh:mm"
},
MenuItem {
text: qsTr("Long format seconds")
value: "dddd d MMM - hh:mm:ss"
},
MenuItem {
text: qsTr("Short format seconds")
value: "ddd d MMM - hh:mm:ss"
},
MenuItem {
text: qsTr("Time only seconds")
value: "hh:mm:ss"
}
]
isSubPage: true
title: qsTr("Clock")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
SelectRow {
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
active: root.clockFormats.find(item => item.value === Config.general.dateFormat)
first: true
last: true
menuItems: root.clockFormats
subtext: qsTr("Change how time is displayed in the widget")
text: qsTr("Time format")
onSelected: item => {
Config.general.dateFormat = item.value;
}
}
}
}
@@ -0,0 +1,93 @@
pragma ComponentBehavior: Bound
import QtQuick.Layouts
import qs.Config
import qs.Modules.SettingsNew.Common
PageBase {
id: root
isSubPage: true
title: qsTr("System icons")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
// Visible icons
SectionHeader {
first: true
text: qsTr("Visible icons")
}
ToggleRow {
checked: Config.bar.tray.showAudio
first: true
text: qsTr("Speakers")
onToggled: Config.bar.tray.showAudio = checked
}
ToggleRow {
checked: Config.bar.tray.showMicrophone
text: qsTr("Microphone")
onToggled: Config.bar.tray.showMicrophone = checked
}
//////// FOR LATER:
// ToggleRow {
// checked: Config.bar.tray.showNetwork
// text: qsTr("Network")
//
// onToggled: Config.bar.tray.showNetwork = checked
// }
//
// ToggleRow {
// checked: Config.bar.tray.showWifi
// text: qsTr("Wi-Fi")
//
// onToggled: Config.bar.tray.showWifi = checked
// }
//
// ToggleRow {
// checked: Config.bar.tray.showBluetooth
// text: qsTr("Bluetooth")
//
// onToggled: Config.bar.tray.showBluetooth = checked
// }
ToggleRow {
checked: Config.bar.tray.showPower
last: true
text: qsTr("Battery")
onToggled: Config.bar.tray.showPower = checked
}
// Behaviour
SectionHeader {
text: qsTr("Behavior")
}
ToggleRow {
checked: Config.bar.popouts.audio
first: true
subtext: qsTr("Show a details popout when hovering the audio icons")
text: qsTr("Audio popout on hover")
onToggled: Config.bar.popouts.audio = checked
}
ToggleRow {
checked: Config.bar.popouts.upower
last: true
subtext: qsTr("Show a details popout when hovering the power icon")
text: qsTr("Power popout on hover")
onToggled: Config.bar.popouts.upower = checked
}
}
}
@@ -0,0 +1,31 @@
pragma ComponentBehavior: Bound
import QtQuick.Layouts
import qs.Config
import qs.Modules.SettingsNew.Common
PageBase {
id: root
isSubPage: true
title: qsTr("Tray")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
SpinRow {
first: true
from: 12
last: true
stepSize: 1
text: qsTr("Icon size")
to: 24
value: Config.bar.tray.trayIconSize
onMoved: value => Config.bar.tray.trayIconSize = value
}
}
}
@@ -0,0 +1,66 @@
pragma ComponentBehavior: Bound
import QtQuick.Layouts
import qs.Config
import qs.Modules.SettingsNew.Common
PageBase {
id: root
isSubPage: true
title: qsTr("Taskbar")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
// Behavior
SectionHeader {
first: true
text: qsTr("Behavior")
}
ToggleRow {
checked: Config.bar.autoHide
first: true
last: true
subtext: qsTr("Hide the bar, reveal on hover")
text: qsTr("Auto hide")
onToggled: Config.bar.autoHide = checked
}
// Components
SectionHeader {
text: qsTr("Components")
}
NavRow {
first: true
icon: "widgets"
status: qsTr("System tray icons")
text: qsTr("Tray")
onClicked: root.sState.openSubPage(6)
}
NavRow {
icon: "signal_cellular_alt"
status: qsTr("Visible indicators")
text: qsTr("Status icons")
onClicked: root.sState.openSubPage(7)
}
NavRow {
icon: "schedule"
last: true
status: qsTr("Date, icon, background")
text: qsTr("Clock")
onClicked: root.sState.openSubPage(8)
}
}
}
@@ -0,0 +1,36 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Components
import qs.Modules.SettingsNew.Common
PageBase {
id: root
isSubPage: true
title: qsTr("Dashboard")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
// General
SectionHeader {
first: true
text: qsTr("General")
}
ToggleRow {
checked: Config.dashboard.enabled
first: true
last: true
text: qsTr("Enabled")
onToggled: Config.dashboard.enabled = checked
}
}
}
@@ -0,0 +1,131 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Components
import qs.Modules.SettingsNew.Common
PageBase {
id: root
isSubPage: true
title: qsTr("Launcher")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
// General
SectionHeader {
first: true
text: qsTr("General")
}
ToggleRow {
checked: Config.launcher.enabled
first: true
last: true
text: qsTr("Enabled")
onToggled: Config.launcher.enabled = checked
}
// Display
SectionHeader {
text: qsTr("Display")
}
SpinRow {
first: true
from: 1
stepSize: 1
text: qsTr("Max items shown")
to: 20
value: Config.launcher.maxAppsShown
onMoved: v => Config.launcher.maxShown = v
}
SpinRow {
from: 1
stepSize: 1
text: qsTr("Max wallpapers")
to: 30
value: Config.launcher.maxWallpapers
onMoved: v => Config.launcher.maxWallpapers = v
}
SpinRow {
from: 0
last: true
stepSize: 5
subtext: qsTr("Pixels dragged before the launcher opens")
text: qsTr("Drag threshold")
to: 200
value: Config.launcher.dragThreshold
onMoved: v => Config.launcher.dragThreshold = v
}
// Behavior
SectionHeader {
text: qsTr("Behavior")
}
ToggleRow {
checked: Config.launcher.enableDangerousActions
first: true
last: true
subtext: qsTr("Allow actions that shut down or log out")
text: qsTr("Enable dangerous actions")
onToggled: Config.launcher.enableDangerousActions = checked
}
// Fuzzy search
SectionHeader {
text: qsTr("Fuzzy search")
}
ToggleRow {
checked: Config.launcher.useFuzzy.apps
first: true
text: qsTr("Apps")
onToggled: Config.launcher.useFuzzy.apps = checked
}
ToggleRow {
checked: Config.launcher.useFuzzy.actions
text: qsTr("Actions")
onToggled: Config.launcher.useFuzzy.actions = checked
}
ToggleRow {
checked: Config.launcher.useFuzzy.schemes
text: qsTr("Schemes")
onToggled: Config.launcher.useFuzzy.schemes = checked
}
ToggleRow {
checked: Config.launcher.useFuzzy.variants
text: qsTr("Variants")
onToggled: Config.launcher.useFuzzy.variants = checked
}
ToggleRow {
checked: Config.launcher.useFuzzy.wallpapers
last: true
text: qsTr("Wallpapers")
onToggled: Config.launcher.useFuzzy.wallpapers = checked
}
}
}
@@ -0,0 +1,85 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Components
import qs.Modules.SettingsNew.Common
PageBase {
id: root
isSubPage: true
title: qsTr("Resources")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
// General
SectionHeader {
first: true
text: qsTr("General")
}
ToggleRow {
checked: Config.dashboard.performance.enabled
first: true
last: true
text: qsTr("Enabled")
onToggled: Config.dashboard.performance.enabled = checked
}
// Performance widgets
SectionHeader {
text: qsTr("Performance widgets")
}
ToggleRow {
checked: Config.dashboard.performance.showBattery
first: true
text: qsTr("Battery")
onToggled: Config.dashboard.performance.showBattery = checked
}
ToggleRow {
checked: Config.dashboard.performance.showGpu
text: qsTr("GPU")
onToggled: Config.dashboard.performance.showGpu = checked
}
ToggleRow {
checked: Config.dashboard.performance.showCpu
text: qsTr("CPU")
onToggled: Config.dashboard.performance.showCpu = checked
}
ToggleRow {
checked: Config.dashboard.performance.showMemory
text: qsTr("Memory")
onToggled: Config.dashboard.performance.showMemory = checked
}
ToggleRow {
checked: Config.dashboard.performance.showStorage
text: qsTr("Storage")
onToggled: Config.dashboard.performance.showStorage = checked
}
ToggleRow {
checked: Config.dashboard.performance.showNetwork
last: true
text: qsTr("Network")
onToggled: Config.dashboard.performance.showNetwork = checked
}
}
}
@@ -0,0 +1,46 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Components
import qs.Modules.SettingsNew.Common
PageBase {
id: root
isSubPage: true
title: qsTr("Sidebar")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
SectionHeader {
first: true
text: qsTr("General")
}
ToggleRow {
checked: Config.sidebar.enabled
first: true
text: qsTr("Enabled")
onToggled: Config.sidebar.enabled = checked
}
SpinRow {
from: 0
last: true
stepSize: 5
subtext: qsTr("Pixels dragged before the sidebar opens")
text: qsTr("Drag threshold")
to: 200
value: Config.sidebar.dragThreshold
onMoved: v => Config.sidebar.dragThreshold = v
}
}
}
+58
View File
@@ -0,0 +1,58 @@
import QtQuick.Layouts
import qs.Config
import qs.Modules.SettingsNew.Common
PageBase {
id: root
title: qsTr("Panels")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
NavRow {
first: true
icon: "dock_to_bottom"
status: !Config.bar.autoHide ? qsTr("Always visible") : qsTr("Reveal on hover")
text: qsTr("Bar")
onClicked: root.sState.openSubPage(1)
}
NavRow {
icon: "dashboard"
status: Config.dashboard.enabled ? qsTr("Enabled") : qsTr("Disabled")
text: qsTr("Dashboard")
onClicked: root.sState.openSubPage(2)
}
NavRow {
icon: "insert_chart"
status: Config.dashboard.performance.enabled ? qsTr("Enabled") : qsTr("Disabled")
text: qsTr("Resources")
onClicked: root.sState.openSubPage(3)
}
NavRow {
icon: "apps"
status: Config.launcher.enabled ? qsTr("Enabled") : qsTr("Disabled")
text: qsTr("Launcher")
onClicked: root.sState.openSubPage(4)
}
NavRow {
icon: "dock_to_right"
last: true
status: Config.sidebar.enabled ? qsTr("Enabled") : qsTr("Disabled")
text: qsTr("Sidebar")
onClicked: root.sState.openSubPage(5)
}
}
}
+138
View File
@@ -0,0 +1,138 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import ZShell.Components
import qs.Helpers
import qs.Components
import qs.Config
import qs.Modules.SettingsNew
import qs.Modules.SettingsNew.Common
PageBase {
id: root
title: qsTr("Screenshot effects")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.large
width: root.cappedWidth
ToggleRow {
checked: Config.screenshot.enable_pp
first: true
text: qsTr("Enable effects")
onToggled: Config.screenshot.enable_pp = checked
}
SelectRow {
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
active: Config.screenshot.mode === "manual" ? menuItems[0] : menuItems[1]
enabled: Config.screenshot.enable_pp
last: true
subtext: qsTr("Automatic or manual effect values")
text: qsTr("Effects mode")
menuItems: [
MenuItem {
icon: "build"
text: qsTr("Manual")
value: "manual"
},
MenuItem {
icon: "rotate_auto"
text: qsTr("Auto")
value: "auto"
}
]
onSelected: item => {
Config.screenshot.mode = item.value;
Config.save();
}
}
ToggleRow {
checked: Config.screenshot.rounding
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual"
first: true
text: qsTr("Enable rounded corners")
onToggled: Config.screenshot.rounding = checked
}
SpinRow {
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.rounding
from: 0
last: true
stepSize: 1
text: qsTr("Corner radius")
to: 50
value: Config.screenshot.radius
onMoved: value => {
const newVal = Math.floor(value);
Config.screenshot.radius = newVal;
}
}
ToggleRow {
checked: Config.screenshot.shadow
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual"
first: true
text: qsTr("Enable shadow")
onToggled: Config.screenshot.shadow = checked
}
SpinRow {
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow
from: 0
stepSize: 1
text: qsTr("Shadow blur amount")
to: 100
value: Config.screenshot.shadow_blur
onMoved: value => {
const newVal = Math.floor(value);
Config.screenshot.shadow_blur = newVal;
}
}
SpinRow {
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow
from: -100
stepSize: 10
text: qsTr("Shadow horizontal offset")
to: 100
value: Config.screenshot.shadow_offset_x
onMoved: value => {
const newVal = Math.floor(value);
Config.screenshot.shadow_offset_x = newVal;
}
}
SpinRow {
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow
from: -100
last: true
stepSize: 10
text: qsTr("Shadow vertical offset")
to: 100
value: Config.screenshot.shadow_offset_y
onMoved: value => {
const newVal = Math.floor(value);
Config.screenshot.shadow_offset_y = newVal;
}
}
}
}
@@ -0,0 +1,162 @@
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Components
import qs.Modules.SettingsNew.Common
PageBase {
id: root
isSubPage: true
title: qsTr("Notifications")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
// Notifications
SectionHeader {
first: true
text: qsTr("Notifications")
}
ToggleRow {
checked: Config.notifs.showInFullscreen
first: true
subtext: qsTr("Whether notifications appear over fullscreen apps")
text: qsTr("Show in fullscreen")
onToggled: Config.notifs.showInFullscreen = checked
}
ToggleRow {
checked: Config.notifs.expire
subtext: qsTr("Dismiss notifications after their timeout")
text: qsTr("Expire automatically")
onToggled: Config.notifs.expire = checked
}
ToggleRow {
checked: Config.notifs.openExpanded
subtext: qsTr("Show notifications expanded by default")
text: qsTr("Open expanded")
onToggled: Config.notifs.openExpanded = checked
}
SpinRow {
from: 1000
stepSize: 500
subtext: qsTr("Time before a notification dismisses (ms)")
text: qsTr("Default timeout")
to: 60000
value: Config.notifs.defaultExpireTimeout
onMoved: v => Config.notifs.defaultExpireTimeout = Math.round(v)
}
SpinRow {
from: 1
last: true
stepSize: 1
subtext: qsTr("Notifications shown per group before collapsing")
text: qsTr("Group preview count")
to: 10
value: Config.notifs.groupPreviewNum
onMoved: v => Config.notifs.groupPreviewNum = Math.round(v)
}
// Toasts
SectionHeader {
text: qsTr("Toasts")
}
SpinRow {
first: true
from: 1
stepSize: 1
subtext: qsTr("Maximum number of toasts shown at once")
text: qsTr("Visible toasts")
to: 10
value: Config.utilities.maxToasts
onMoved: v => Config.utilities.maxToasts = Math.round(v)
}
ToggleRow {
checked: Config.utilities.toasts.chargingChanged
text: qsTr("Charging changes")
onToggled: Config.utilities.toasts.chargingChanged = checked
}
ToggleRow {
checked: Config.utilities.toasts.gameModeChanged
text: qsTr("Game mode changes")
onToggled: Config.utilities.toasts.gameModeChanged = checked
}
// ToggleRow {
// checked: Config.utilities.toasts.dndChanged
// text: qsTr("Do not disturb changes")
//
// onToggled: Config.utilities.toasts.dndChanged = checked
// }
ToggleRow {
checked: Config.utilities.toasts.audioOutputChanged
text: qsTr("Audio output changes")
onToggled: Config.utilities.toasts.audioOutputChanged = checked
}
ToggleRow {
checked: Config.utilities.toasts.audioInputChanged
last: true
text: qsTr("Audio input changes")
onToggled: Config.utilities.toasts.audioInputChanged = checked
}
// ToggleRow {
// checked: Config.utilities.toasts.capsLockChanged
// text: qsTr("Caps lock changes")
//
// onToggled: Config.utilities.toasts.capsLockChanged = checked
// }
//
// ToggleRow {
// checked: Config.utilities.toasts.numLockChanged
// text: qsTr("Num lock changes")
//
// onToggled: Config.utilities.toasts.numLockChanged = checked
// }
//
// ToggleRow {
// checked: Config.utilities.toasts.kbLayoutChanged
// text: qsTr("Keyboard layout changes")
//
// onToggled: Config.utilities.toasts.kbLayoutChanged = checked
// }
//
// ToggleRow {
// checked: Config.utilities.toasts.vpnChanged
// text: qsTr("VPN changes")
//
// onToggled: Config.utilities.toasts.vpnChanged = checked
// }
//
// ToggleRow {
// checked: Config.utilities.toasts.nowPlaying
// last: true
// text: qsTr("Now playing")
//
// onToggled: Config.utilities.toasts.nowPlaying = checked
// }
}
}
+214
View File
@@ -0,0 +1,214 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import ZShell.Services
import qs.Components
import qs.Helpers
import qs.Config
import qs.Modules.SettingsNew.Common
PageBase {
id: root
// GPU options + the config string each maps to (see Gpu::parseType)
readonly property list<MenuItem> gpuItems: [
MenuItem {
text: qsTr("Auto")
},
MenuItem {
text: "NVIDIA"
},
MenuItem {
text: qsTr("Generic")
},
MenuItem {
text: qsTr("None")
}
]
readonly property list<string> gpuValues: ["", "NVIDIA", "GENERIC", "None"]
function gpuKeyToIndex(key: string): int {
const u = (key ?? "").trim().toUpperCase();
if (u === "")
return 0; // Auto
if (u === "NVIDIA")
return 1;
if (u === "GENERIC")
return 2;
return 3; // None
}
title: qsTr("Services")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
// Detected running players, used as default-player options
Variants {
id: playerVariants
model: [...new Set(Players.list.map(p => Players.getIdentity(p)).filter(id => id))]
MenuItem {
required property string modelData
activeIcon: "music_note"
icon: modelData === Config.services.defaultPlayer ? "check" : ""
text: modelData
}
}
// Notifications
SectionHeader {
first: true
text: qsTr("Notifications")
}
NavRow {
first: true
icon: "notifications"
last: true
status: qsTr("Notifications, toasts, timeouts")
text: qsTr("Notifications")
onClicked: root.sState.openSubPage(1)
}
// Polling
SectionHeader {
text: qsTr("Polling")
}
SpinRow {
first: true
from: 100
stepSize: 50
subtext: qsTr("How often the media position updates (ms)")
text: qsTr("Media refresh")
to: 2000
value: Config.dashboard.mediaUpdateInterval
onMoved: v => Config.dashboard.mediaUpdateInterval = v
}
SpinRow {
from: 0.5
last: true
stepSize: 0.5
subtext: qsTr("CPU, memory and GPU update interval (seconds)")
text: qsTr("System stats refresh")
to: 10
value: Config.dashboard.resourceUpdateInterval / 1000
onMoved: v => Config.dashboard.resourceUpdateInterval = Math.round(v * 1000)
}
// Media & lyrics
SectionHeader {
text: qsTr("Media")
}
SelectRow {
active: menuItems.find(i => i.text === Config.services.defaultPlayer) ?? null
fallbackIcon: "music_note"
fallbackText: Config.services.defaultPlayer || qsTr("Auto")
first: true
last: true
menuItems: playerVariants.instances
subtext: qsTr("Preferred media player when several are open")
text: qsTr("Default player")
onSelected: item => Config.services.defaultPlayer = item.text
}
// Input increments
SectionHeader {
text: qsTr("Input increments")
}
SpinRow {
first: true
from: 1
stepSize: 1
subtext: qsTr("Amount the volume changes per input (%)")
text: qsTr("Volume step")
to: 50
value: Math.round(Config.services.audioIncrement * 100)
onMoved: v => Config.services.audioIncrement = v / 100
}
SpinRow {
from: 1
stepSize: 1
subtext: qsTr("Amount the brightness changes per input (%)")
text: qsTr("Brightness step")
to: 50
value: Math.round(Config.services.brightnessIncrement * 100)
onMoved: v => Config.services.brightnessIncrement = v / 100
}
SpinRow {
from: 1
stepSize: 1
subtext: qsTr("Lowest allowed brightness (%)")
text: qsTr("Brightness minimum")
to: 20
value: Math.round(Config.services.minBrightness * 100)
onMoved: v => Config.services.minBrightness = v / 100
}
SpinRow {
from: 50
last: true
stepSize: 5
subtext: qsTr("Upper limit for output volume (%)")
text: qsTr("Max volume")
to: 200
value: Math.round(Config.services.maxVolume * 100)
onMoved: v => Config.services.maxVolume = v / 100
}
// Service tuning
SectionHeader {
text: qsTr("Service tuning")
}
SpinRow {
first: true
from: 10
stepSize: 2
subtext: qsTr("Resolution of the audio visualizer")
text: qsTr("Visualizer resolution")
to: 120
value: Config.services.visualizerBars
onMoved: v => Config.services.visualiserBars = v
}
ToggleRow {
checked: Config.general.color.smart
subtext: qsTr("Derive theme mode from the wallpaper")
text: qsTr("Smart color scheme")
onToggled: Config.general.color.smart = checked
}
SelectRow {
active: root.gpuItems[root.gpuKeyToIndex(Config.services.gpuType)]
last: true
menuItems: root.gpuItems
menuOnTop: true
subtext: Gpu.name ? qsTr("Monitoring: %1").arg(Gpu.name) : qsTr("Override for GPU type")
text: qsTr("GPU")
onSelected: item => Config.services.gpuType = root.gpuValues[root.gpuItems.indexOf(item)]
}
}
}
+156
View File
@@ -0,0 +1,156 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import ZShell.Components
import qs.Helpers
import qs.Components
import qs.Config
import qs.Modules.SettingsNew
import qs.Modules.SettingsNew.Common
PageBase {
id: root
title: qsTr("Wallpaper & style")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.large
width: root.cappedWidth
Item {
id: wallWrapper
Layout.alignment: Qt.AlignHCenter
implicitHeight: cropper.height
implicitWidth: root.cappedWidth
WallpaperCropper {
id: cropper
}
}
IconTextButton {
Layout.alignment: Qt.AlignHCenter
enabled: Config.background.enabled
horizontalPadding: Appearance.padding.extraLarge
icon: "wallpaper"
isRound: true
shapeMorph: true
text: qsTr("Wallpapers")
type: IconTextButton.Tonal
verticalPadding: Appearance.padding.normal
onClicked: root.sState.openSubPage(1) // Wallpaper page
}
ToggleRow {
checked: Config.background.enabled
first: true
text: qsTr("Display wallpaper")
onToggled: Config.background.enabled = checked
}
ToggleRow {
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
checked: DynamicColors.transparency.enabled
subtext: qsTr("Base %1, layers %2").arg(DynamicColors.transparency.base).arg(DynamicColors.transparency.layers)
text: qsTr("Transparency")
onToggled: Config.appearance.transparency.enabled = checked
}
ToggleRow {
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
checked: !DynamicColors.light
last: true
text: qsTr("Dark theme")
onToggled: DynamicColors.setMode(checked ? "dark" : "light")
}
OverlayRow {
id: darkMode
readonly property string endTime: {
var d = new Date(0, 0, 0, 0, 0, 0, 0);
d.setMinutes(Config.general.color.scheduleDarkEnd);
return Qt.formatTime(d, "hh:mm AP");
}
readonly property string startTime: {
var d = new Date(0, 0, 0, 0, 0, 0, 0);
d.setMinutes(Config.general.color.scheduleDarkStart);
return Qt.formatTime(d, "hh:mm AP");
}
checked: Config.general.color.scheduleDark
first: true
subtext: qsTr("Dark mode will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime)
text: qsTr("Schedule dark mode")
popup: Component {
TimeInput {
object: Config.general.color
settings: ["scheduleDark", "scheduleDarkStart", "scheduleDarkEnd"]
onApplySettings: (start, end) => {
Config.general.color.scheduleDarkStart = start;
Config.general.color.scheduleDarkEnd = end;
Config.save();
ModeScheduler.checkStartup();
PopupManager.requestClose();
}
onClose: PopupManager.requestClose()
}
}
onClicked: value => {
Config.general.color.scheduleDark = value;
}
}
OverlayRow {
id: hyprsunset
readonly property string endTime: {
var d = new Date(0, 0, 0, 0, 0, 0, 0);
d.setMinutes(Config.general.color.scheduleHyprsunsetEnd);
return Qt.formatTime(d, "hh:mm AP");
}
readonly property string startTime: {
var d = new Date(0, 0, 0, 0, 0, 0, 0);
d.setMinutes(Config.general.color.scheduleHyprsunsetStart);
return Qt.formatTime(d, "hh:mm AP");
}
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
checked: Config.general.color.scheduleHyprsunset
last: true
subtext: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime)
text: qsTr("Schedule hyprsunset")
popup: Component {
TimeInput {
object: Config.general.color
settings: ["scheduleHyprsunset", "scheduleHyprsunsetStart", "scheduleHyprsunsetEnd"]
onApplySettings: (start, end) => {
Config.general.color.scheduleHyprsunsetStart = start;
Config.general.color.scheduleHyprsunsetEnd = end;
Config.save();
Hyprsunset.checkStartup();
PopupManager.requestClose();
}
onClose: PopupManager.requestClose()
}
}
onClicked: value => {
Config.general.color.scheduleHyprsunset = value;
}
}
}
}
@@ -0,0 +1,119 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import ZShell.Models
import qs.Paths
import qs.Helpers
import qs.Components
import qs.Config
import qs.Modules.SettingsNew.Common
PageBase {
id: root
isSubPage: true
title: qsTr("Wallpapers")
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
spacing: Appearance.spacing.small
width: root.cappedWidth
CustomText {
Layout.topMargin: Appearance.spacing.large
font.pointSize: Appearance.font.size.large
text: qsTr("Wallpapers")
}
GridLayout {
Layout.fillWidth: true
columnSpacing: Appearance.spacing.extraSmall
columns: 4
rowSpacing: Appearance.spacing.extraSmall
visible: localWalls.count > 0
Repeater {
id: localWalls
model: {
const walls = Wallpapers.list;
var baseDir = Paths.wallsdir;
const categories = {};
const list = [];
for (const w of walls) {
var parentDir = w.parentDir;
if (!parentDir.endsWith("/"))
parentDir = parentDir + "/";
if (!baseDir.endsWith("/"))
baseDir = baseDir + "/";
if (parentDir !== baseDir) {
const category = Wallpapers.getCategoryFor(w);
if (category && (!(category in categories) || categories[category].name.localeCompare(w.name) > 0))
categories[category] = w;
} else {
list.push(w);
}
}
list.push(...Object.values(categories));
list.sort((a, b) => ((a.parentDir === baseDir) - (b.parentDir === baseDir)) || a.name.localeCompare(b.name));
while (list.length < 4)
list.push(null);
return list;
}
WallItem {
required property FileSystemEntry modelData
enabled: modelData
// Empty placeholders for sizing
opacity: modelData ? 1 : 0
source: String(modelData?.path ?? "")
onClicked: {
Wallpapers.setWallpaper(modelData.path);
root.sState.closeSubPage();
}
}
}
}
Loader {
Layout.fillWidth: true
active: localWalls.count === 0
asynchronous: true
visible: active
sourceComponent: CustomRect {
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: noWallsLayout.implicitHeight + Appearance.padding.extraLarge * 3
radius: Appearance.rounding.large
ColumnLayout {
id: noWallsLayout
anchors.centerIn: parent
spacing: Appearance.spacing.extraSmall
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3outline
text: "hide_image"
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3outline
text: qsTr("No local wallpapers found")
}
}
}
}
}
}
+20
View File
@@ -0,0 +1,20 @@
pragma Singleton
import Quickshell
import QtQuick
Singleton {
id: root
property bool closed: true
property Component currentPopup: null
function requestClose(): void {
closed = true;
}
function requestOpen(component: Component): void {
currentPopup = component;
closed = false;
}
}
+46
View File
@@ -0,0 +1,46 @@
import QtQuick
import qs.Config
import qs.Components
CustomRect {
id: root
readonly property bool closing: PopupManager.closed
readonly property var currentPopup: PopupManager.currentPopup
property bool shouldBeVisible: loader.status === Loader.Ready
color: Qt.alpha(DynamicColors.palette.m3shadow, 0.3)
opacity: shouldBeVisible && !closing ? 1 : 0
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
onVisibleChanged: if (!visible)
PopupManager.currentPopup = null
CustomMouseArea {
anchors.fill: parent
hoverEnabled: true
preventStealing: true
propagateComposedEvents: false
onClicked: {
const insideItemWidth = mouseX < loader.x + loader.item.width && mouseX > loader.x;
const insideItemHeight = mouseY < loader.y + loader.item.height && mouseY > loader.y;
if (insideItemHeight && insideItemWidth)
return;
PopupManager.requestClose();
}
}
Loader {
id: loader
anchors.centerIn: parent
sourceComponent: root.currentPopup
}
}
+33
View File
@@ -0,0 +1,33 @@
import QtQuick
import Quickshell
import Quickshell.Bluetooth
QtObject {
id: root
property bool animatingContainer
property int currentPageIdx
property bool isWindow
property ShellScreen screen
property bool searchOpen
property DesktopEntry selectedApp
property BluetoothDevice selectedBtDevice
property string selectedWallpaperCategory
property list<int> subPageIdxStack
signal close
signal subPageClosed
signal subPageOpened(idx: int)
function closeSubPage(): void {
subPageClosed();
subPageIdxStack.pop();
}
function openSubPage(idx: int): void {
subPageIdxStack.push(idx);
subPageOpened(idx);
}
onCurrentPageIdxChanged: subPageIdxStack.length = 0
}
+47
View File
@@ -0,0 +1,47 @@
import Quickshell
import QtQuick
import qs.Components
import qs.Config
import qs.Helpers
Item {
id: root
property real offsetScale: shouldBeActive ? 0 : 1
required property var panels
required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.settings
required property PersistentProperties visibilities
implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth
opacity: 1 - offsetScale
visible: offsetScale < 1
Behavior on offsetScale {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
CustomClippingRect {
anchors.fill: parent
Loader {
id: content
active: root.shouldBeActive || root.visible
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
sourceComponent: Content {
sState.animatingContainer: content.opacity < 1
sState.currentPageIdx: ["wallpaper"][0]
sState.screen: root.screen
onClose: console.log("shouldclose")
}
}
}
}
+4 -4
View File
@@ -45,19 +45,19 @@ Item {
CustomRect {
anchors.fill: parent
anchors.margins: 3
color: Config.general.color.scheduleDark && root.current ? DynamicColors.palette.m3primary : "transparent"
color: icon.layer.enabled && root.current ? DynamicColors.palette.m3primary : "transparent"
radius: Appearance.rounding.full
StateLayer {
acceptedButtons: Qt.LeftButton | Qt.RightButton
anchors.fill: parent
color: Config.general.color.scheduleDark && root.current ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
color: icon.layer.enabled && root.current ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
onClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
root.item.activate();
console.log(icon.source + "\n" + root.item.id);
} else if (mouse.button === Qt.RightButton && Config.barConfig.popouts.tray) {
} else if (mouse.button === Qt.RightButton && Config.bar.popouts.tray) {
root.popouts.currentName = `traymenu${root.ind}`;
root.popouts.currentCenter = Qt.binding(() => root.mapToItem(root.loader, root.implicitWidth / 2, 0).x);
root.popouts.hasCurrent = true;
@@ -77,7 +77,7 @@ Item {
anchors.centerIn: parent
antialiasing: true
color: root.current ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
implicitSize: Config.barConfig.tray.trayIconSize * root.dpr
implicitSize: Config.bar.tray.trayIconSize * root.dpr
layer.enabled: Config.general.color.smart || Config.general.color.scheduleDark
scale: 1 / root.dpr
source: root.resolveIcon(root.item.id, root.item.icon)
+17 -8
View File
@@ -47,12 +47,12 @@ RowLayout {
let modRowPos = sysTrayMod.mapToItem(sysModRow, modPos.x, modPos.y);
let child = closestRowChild(sysModRow, modRowPos.x);
if (child) {
if (child.objectName === "audioWidget" && Config.barConfig.popouts.audio)
if (child.objectName === "audioWidget" && Config.bar.popouts.audio)
return {
id: "audio",
item: child
};
if (child.objectName === "upowerWidget" && Config.barConfig.popouts.upower)
if (child.objectName === "upowerWidget" && Config.bar.popouts.upower)
return {
id: "upower",
item: child
@@ -75,7 +75,7 @@ RowLayout {
return null;
}
height: Config.barConfig.height + Appearance.padding.smallest * 2
height: Config.bar.height + Appearance.padding.smallest * 2
spacing: Appearance.padding.small
width: sysTray.implicitWidth + sysTrayMod.implicitWidth + Appearance.padding.small
@@ -123,22 +123,31 @@ RowLayout {
Layout.fillHeight: true
bottomLeftRadius: Appearance.rounding.smallest / 2
color: DynamicColors.tPalette.m3surfaceContainer
implicitWidth: sysModRow.width + Appearance.padding.smaller * 2
implicitWidth: sysModRow.implicitWidth + Appearance.padding.smaller + Appearance.padding.normal
radius: Appearance.rounding.full
topLeftRadius: Appearance.rounding.smallest / 2
Row {
RowLayout {
id: sysModRow
anchors.centerIn: parent
spacing: Appearance.padding.small
anchors.fill: parent
anchors.leftMargin: Appearance.padding.smaller
anchors.rightMargin: Appearance.padding.normal
AudioWidget {
Layout.fillHeight: true
Layout.rightMargin: hasContent ? Appearance.spacing.extraSmall / 2 : 0
objectName: "audioWidget"
Behavior on Layout.rightMargin {
Anim {
type: Anim.FastEffects
}
}
}
UPowerWidget {
height: parent.height
Layout.fillHeight: true
objectName: "upowerWidget"
}
}
+43
View File
@@ -11,25 +11,68 @@ RowLayout {
id: root
property color barColor: DynamicColors.palette.m3primary
readonly property bool hasContent: mic.visible || speaker.visible
property color textColor: DynamicColors.palette.m3onSurface
width: hasContent ? implicitWidth : 0
MaterialIcon {
id: mic
readonly property bool shouldBeVisible: Config.bar.tray.showMicrophone
Layout.alignment: Qt.AlignVCenter
Layout.maximumWidth: shouldBeVisible ? implicitWidth : 0
animate: true
color: (Audio.sourceMuted ?? false) ? DynamicColors.palette.m3error : root.textColor
fill: 1
font.pointSize: Appearance.font.size.larger
opacity: shouldBeVisible ? 1 : 0
scale: shouldBeVisible ? 1 : 0
text: Audio.sourceMuted ? "mic_off" : "mic"
visible: opacity > 0
Behavior on Layout.maximumWidth {
Anim {
}
}
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
}
MaterialIcon {
id: speaker
readonly property bool shouldBeVisible: Config.bar.tray.showAudio
Layout.alignment: Qt.AlignVCenter
Layout.maximumWidth: shouldBeVisible ? implicitWidth : 0
animate: true
color: Audio.muted ? DynamicColors.palette.m3error : root.textColor
fill: 1
font.pointSize: Appearance.font.size.larger
opacity: shouldBeVisible ? 1 : 0
scale: shouldBeVisible ? 1 : 0
text: Audio.muted ? "volume_off" : "volume_up"
visible: opacity > 0
Behavior on Layout.maximumWidth {
Anim {
}
}
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
}
}
+24
View File
@@ -8,8 +8,32 @@ import qs.Helpers
Item {
id: root
readonly property bool shouldBeActive: Config.bar.tray.showPower
Layout.preferredHeight: shouldBeActive ? implicitHeight : 0
Layout.preferredWidth: shouldBeActive ? implicitWidth : 0
implicitHeight: Battery.isLaptop ? batteryIconLoader.item.implicitHeight : upowerIconLoader.item.implicitHeight
implicitWidth: Battery.isLaptop ? batteryIconLoader.item.implicitWidth : upowerIconLoader.item.implicitWidth
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0
visible: opacity > 0
Behavior on Layout.preferredHeight {
Anim {
}
}
Behavior on Layout.preferredWidth {
Anim {
}
}
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Loader {
id: batteryIconLoader
+1 -1
View File
@@ -12,7 +12,7 @@ CustomRect {
property color textColor: DynamicColors.palette.m3onSurface
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
implicitHeight: Config.bar.height + Appearance.padding.smallest * 2
implicitWidth: contentRow.implicitWidth + Appearance.spacing.small * 2
radius: Appearance.rounding.full
+1 -1
View File
@@ -21,7 +21,7 @@ Item {
readonly property int workspacesShown: workspaces.length
height: implicitHeight
implicitHeight: Config.barConfig.height + Appearance.padding.smaller * 2
implicitHeight: Config.bar.height + Appearance.padding.smaller * 2
implicitWidth: (root.workspaceButtonWidth * root.workspacesShown) + root.activeWorkspaceMargin * 2
Behavior on implicitWidth {
+1 -1
View File
@@ -19,7 +19,7 @@ Singleton {
readonly property string recsdir: Quickshell.env("ZSHELL_RECORDINGS_DIR") || `${videos}/Recordings`
readonly property string state: `${Quickshell.env("XDG_STATE_HOME") || `${home}/.local/state`}/zshell`
readonly property string videos: Quickshell.env("XDG_VIDEOS_DIR") || `${home}/Videos`
readonly property string wallsdir: Quickshell.env("ZSHELL_WALLPAPERS_DIR") || absolutePath(Config.wallpaperPath)
readonly property string wallsdir: Quickshell.env("ZSHELL_WALLPAPERS_DIR") || absolutePath(Config.general.wallpaperPath)
function absolutePath(path: string): string {
return toLocalFile(path.replace("~", home));
+8
View File
@@ -29,6 +29,14 @@ void BlobGroup::setColor(const QColor& c) {
markDirty();
}
void BlobGroup::setCornerFill(bool e) {
if (m_cornerFill == e)
return;
m_cornerFill = e;
emit cornerFillChanged();
markDirty();
}
void BlobGroup::addShape(BlobShape* shape) {
if (!shape || m_shapes.contains(shape))
return;
+9
View File
@@ -13,6 +13,7 @@ Q_OBJECT
QML_ELEMENT
Q_PROPERTY(qreal smoothing READ smoothing WRITE setSmoothing NOTIFY smoothingChanged)
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
Q_PROPERTY(bool cornerFill READ cornerFill WRITE setCornerFill NOTIFY cornerFillChanged)
public:
explicit BlobGroup(QObject* parent = nullptr);
@@ -30,6 +31,12 @@ void setSmoothing(qreal s);
void setColor(const QColor& c);
[[nodiscard]] bool cornerFill() const {
return m_cornerFill;
}
void setCornerFill(bool e);
void addShape(BlobShape* shape);
void removeShape(BlobShape* shape);
@@ -51,10 +58,12 @@ void ensurePhysicsUpdated();
signals:
void smoothingChanged();
void colorChanged();
void cornerFillChanged();
private:
qreal m_smoothing = 32.0;
QColor m_color{ 0x44, 0x88, 0xff };
bool m_cornerFill = true;
QList<BlobShape*> m_shapes;
BlobInvertedRect* m_invertedRect = nullptr;
bool m_physicsUpdated = false;
+68 -8
View File
@@ -3,6 +3,7 @@
#include <algorithm>
#include <cmath>
#include <qnamespace.h>
BlobRect::BlobRect(QQuickItem* parent)
: BlobShape(parent) {
@@ -35,7 +36,8 @@ void BlobRect::updatePolish() {
if (m_group)
m_group->markDirty();
},
Qt::QueuedConnection);
Qt::QueuedConnection
);
}
} else {
QMetaObject::invokeMethod(
@@ -105,17 +107,15 @@ void BlobRect::updatePhysics() {
const float kStiffness = static_cast<float>(m_stiffness);
const float kDamping = static_cast<float>(m_damping);
const float invDamp = 1.0f / (1.0f + kDamping * dt);
const float accel00 = -kStiffness * (m_dm00 - target00) - kDamping * m_dmVel00;
m_dmVel00 += accel00 * dt;
m_dmVel00 = (m_dmVel00 - kStiffness * (m_dm00 - target00) * dt) * invDamp;
m_dm00 += m_dmVel00 * dt;
const float accel01 = -kStiffness * (m_dm01 - target01) - kDamping * m_dmVel01;
m_dmVel01 += accel01 * dt;
m_dmVel01 = (m_dmVel01 - kStiffness * (m_dm01 - target01) * dt) * invDamp;
m_dm01 += m_dmVel01 * dt;
const float accel11 = -kStiffness * (m_dm11 - target11) - kDamping * m_dmVel11;
m_dmVel11 += accel11 * dt;
m_dmVel11 = (m_dmVel11 - kStiffness * (m_dm11 - target11) * dt) * invDamp;
m_dm11 += m_dmVel11 * dt;
m_deformMatrix = QMatrix4x4(m_dm00, m_dm01, 0, 0, m_dm01, m_dm11, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
@@ -178,11 +178,24 @@ bool BlobRect::isExcluded(const BlobShape* other) const {
return false;
}
bool BlobRect::isCornerExcluded(const BlobShape* other) const {
for (const auto& ptr : m_excludeCorners) {
if (ptr == other)
return true;
}
return false;
}
QQmlListProperty<BlobRect> BlobRect::exclude() {
return QQmlListProperty<BlobRect>(
this, nullptr, &excludeAppend, &excludeCount, &excludeAt, &excludeClear, &excludeReplace, &excludeRemoveLast);
}
QQmlListProperty<BlobRect> BlobRect::excludeCorners() {
return QQmlListProperty<BlobRect>(this, nullptr, &excludeCornersAppend, &excludeCornersCount, &excludeCornersAt,
&excludeCornersClear, &excludeCornersReplace, &excludeCornersRemoveLast);
}
void BlobRect::excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect) {
auto* self = static_cast<BlobRect*>(prop->object);
self->m_exclude.append(rect);
@@ -229,6 +242,52 @@ void BlobRect::excludeRemoveLast(QQmlListProperty<BlobRect>* prop) {
emit self->excludeChanged();
}
void BlobRect::excludeCornersAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect) {
auto* self = static_cast<BlobRect*>(prop->object);
self->m_excludeCorners.append(rect);
if (self->m_group)
self->m_group->markDirty();
emit self->excludeCornersChanged();
}
qsizetype BlobRect::excludeCornersCount(QQmlListProperty<BlobRect>* prop) {
auto* self = static_cast<BlobRect*>(prop->object);
return self->m_excludeCorners.size();
}
BlobRect* BlobRect::excludeCornersAt(QQmlListProperty<BlobRect>* prop, qsizetype index) {
auto* self = static_cast<BlobRect*>(prop->object);
return self->m_excludeCorners.at(index);
}
void BlobRect::excludeCornersClear(QQmlListProperty<BlobRect>* prop) {
auto* self = static_cast<BlobRect*>(prop->object);
if (self->m_excludeCorners.isEmpty())
return;
self->m_excludeCorners.clear();
if (self->m_group)
self->m_group->markDirty();
emit self->excludeCornersChanged();
}
void BlobRect::excludeCornersReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect) {
auto* self = static_cast<BlobRect*>(prop->object);
self->m_excludeCorners[index] = rect;
if (self->m_group)
self->m_group->markDirty();
emit self->excludeCornersChanged();
}
void BlobRect::excludeCornersRemoveLast(QQmlListProperty<BlobRect>* prop) {
auto* self = static_cast<BlobRect*>(prop->object);
if (self->m_excludeCorners.isEmpty())
return;
self->m_excludeCorners.removeLast();
if (self->m_group)
self->m_group->markDirty();
emit self->excludeCornersChanged();
}
void BlobRect::checkAtRest(float speed) {
constexpr float kEpsilon = 0.002f;
const bool atRest = std::abs(m_dm00 - 1.0f) < kEpsilon && std::abs(m_dm01) < kEpsilon &&
@@ -254,7 +313,8 @@ void BlobRect::checkAtRest(float speed) {
if (m_group)
m_group->markDirty();
},
Qt::QueuedConnection);
Qt::QueuedConnection
);
}
}
}
+12
View File
@@ -14,6 +14,7 @@ Q_PROPERTY(qreal stiffness READ stiffness WRITE setStiffness NOTIFY stiffnessCha
Q_PROPERTY(qreal damping READ damping WRITE setDamping NOTIFY dampingChanged)
Q_PROPERTY(qreal deformScale READ deformScale WRITE setDeformScale NOTIFY deformScaleChanged)
Q_PROPERTY(QQmlListProperty<BlobRect> exclude READ exclude NOTIFY excludeChanged)
Q_PROPERTY(QQmlListProperty<BlobRect> excludeCorners READ excludeCorners NOTIFY excludeCornersChanged)
Q_PROPERTY(qreal topLeftRadius READ topLeftRadius WRITE setTopLeftRadius NOTIFY topLeftRadiusChanged)
Q_PROPERTY(qreal topRightRadius READ topRightRadius WRITE setTopRightRadius NOTIFY topRightRadiusChanged)
Q_PROPERTY(qreal bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius NOTIFY bottomLeftRadiusChanged)
@@ -58,8 +59,10 @@ void setDeformScale(qreal s) {
}
QQmlListProperty<BlobRect> exclude();
QQmlListProperty<BlobRect> excludeCorners();
bool isExcluded(const BlobShape* other) const override;
bool isCornerExcluded(const BlobShape* other) const override;
void cornerRadii(float out[4]) const override;
[[nodiscard]] qreal topLeftRadius() const {
@@ -91,6 +94,7 @@ void stiffnessChanged();
void dampingChanged();
void deformScaleChanged();
void excludeChanged();
void excludeCornersChanged();
void topLeftRadiusChanged();
void topRightRadiusChanged();
void bottomLeftRadiusChanged();
@@ -130,6 +134,7 @@ qreal m_bottomLeftRadius = -1;
qreal m_bottomRightRadius = -1;
QList<QPointer<BlobRect> > m_exclude;
QList<QPointer<BlobRect> > m_excludeCorners;
static void excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect);
static qsizetype excludeCount(QQmlListProperty<BlobRect>* prop);
@@ -137,4 +142,11 @@ static BlobRect* excludeAt(QQmlListProperty<BlobRect>* prop, qsizetype index);
static void excludeClear(QQmlListProperty<BlobRect>* prop);
static void excludeReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect);
static void excludeRemoveLast(QQmlListProperty<BlobRect>* prop);
static void excludeCornersAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect);
static qsizetype excludeCornersCount(QQmlListProperty<BlobRect>* prop);
static BlobRect* excludeCornersAt(QQmlListProperty<BlobRect>* prop, qsizetype index);
static void excludeCornersClear(QQmlListProperty<BlobRect>* prop);
static void excludeCornersReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect);
static void excludeCornersRemoveLast(QQmlListProperty<BlobRect>* prop);
};

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