This commit is contained in:
Zacharias-Brohn
2026-03-14 17:29:24 +01:00
parent 8bc7826f26
commit f7b7260780
14 changed files with 635 additions and 77 deletions
+23 -1
View File
@@ -1,6 +1,7 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import qs.Config import qs.Config
import qs.Helpers
Row { Row {
id: root id: root
@@ -29,8 +30,29 @@ Row {
property int type: CustomSplitButton.Filled property int type: CustomSplitButton.Filled
property real verticalPadding: Appearance.padding.smaller property real verticalPadding: Appearance.padding.smaller
function closeDropdown(): void {
SettingsDropdowns.close(menu);
}
function openDropdown(): void {
if (root.disabled)
return;
SettingsDropdowns.open(menu, root);
}
function toggleDropdown(): void {
if (root.disabled)
return;
SettingsDropdowns.toggle(menu, root);
}
spacing: Math.floor(Appearance.spacing.small / 2) spacing: Math.floor(Appearance.spacing.small / 2)
onExpandedChanged: {
if (!expanded)
SettingsDropdowns.forget(menu);
}
CustomRect { CustomRect {
bottomRightRadius: Appearance.rounding.small / 2 bottomRightRadius: Appearance.rounding.small / 2
color: root.disabled ? root.disabledColor : root.color color: root.disabled ? root.disabledColor : root.color
@@ -109,7 +131,7 @@ Row {
id: expandStateLayer id: expandStateLayer
function onClicked(): void { function onClicked(): void {
root.expanded = !root.expanded; root.toggleDropdown();
} }
color: root.textColor color: root.textColor
+2 -1
View File
@@ -47,9 +47,10 @@ Item {
menu.onItemSelected: item => { menu.onItemSelected: item => {
root.selected(item); root.selected(item);
splitButton.closeDropdown();
} }
stateLayer.onClicked: { stateLayer.onClicked: {
splitButton.expanded = !splitButton.expanded; splitButton.toggleDropdown();
} }
} }
} }
+76
View File
@@ -0,0 +1,76 @@
import QtQuick
Path {
id: root
required property real viewHeight
required property real viewWidth
startX: root.viewWidth / 2
startY: 0
PathAttribute {
name: "itemOpacity"
value: 0.25
}
PathLine {
x: root.viewWidth / 2
y: root.viewHeight * (1 / 6)
}
PathAttribute {
name: "itemOpacity"
value: 0.45
}
PathLine {
x: root.viewWidth / 2
y: root.viewHeight * (2 / 6)
}
PathAttribute {
name: "itemOpacity"
value: 0.70
}
PathLine {
x: root.viewWidth / 2
y: root.viewHeight * (3 / 6)
}
PathAttribute {
name: "itemOpacity"
value: 1.00
}
PathLine {
x: root.viewWidth / 2
y: root.viewHeight * (4 / 6)
}
PathAttribute {
name: "itemOpacity"
value: 0.70
}
PathLine {
x: root.viewWidth / 2
y: root.viewHeight * (5 / 6)
}
PathAttribute {
name: "itemOpacity"
value: 0.45
}
PathLine {
x: root.viewWidth / 2
y: root.viewHeight
}
PathAttribute {
name: "itemOpacity"
value: 0.25
}
}
+178
View File
@@ -0,0 +1,178 @@
import QtQuick
import QtQuick.Effects
import qs.Config
Elevation {
id: root
required property int currentIndex
property bool expanded
required property int from
property color insideTextColor: DynamicColors.palette.m3onPrimary
property int itemHeight
property int listHeight: 200
property color outsideTextColor: DynamicColors.palette.m3onSurfaceVariant
readonly property var spinnerModel: root.range(root.from, root.to)
required property int to
property Item triggerItem
signal itemSelected(item: int)
function range(first, last) {
let out = [];
for (let i = first; i <= last; ++i)
out.push(i);
return out;
}
implicitHeight: root.expanded ? view.implicitHeight : 0
level: root.expanded ? 2 : 0
radius: itemHeight / 2
visible: implicitHeight > 0
Behavior on implicitHeight {
Anim {
}
}
onExpandedChanged: {
if (!root.expanded)
root.itemSelected(view.currentIndex + 1);
}
Component {
id: spinnerDelegate
Item {
id: wrapper
readonly property color delegateTextColor: wrapper.PathView.view ? wrapper.PathView.view.delegateTextColor : "white"
required property var modelData
height: root.itemHeight
opacity: wrapper.PathView.itemOpacity
visible: wrapper.PathView.onPath
width: wrapper.PathView.view ? wrapper.PathView.view.width : 0
z: wrapper.PathView.isCurrentItem ? 100 : Math.round(wrapper.PathView.itemScale * 100)
CustomText {
anchors.centerIn: parent
color: wrapper.delegateTextColor
font.pointSize: Appearance.font.size.large
text: wrapper.modelData
}
}
}
CustomClippingRect {
anchors.fill: parent
color: DynamicColors.palette.m3surfaceContainer
radius: parent.radius
// Main visible spinner: normal/outside text color
PathView {
id: view
property color delegateTextColor: root.outsideTextColor
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
currentIndex: root.currentIndex - 1
delegate: spinnerDelegate
dragMargin: width
highlightRangeMode: PathView.StrictlyEnforceRange
implicitHeight: root.listHeight
model: root.spinnerModel
pathItemCount: 7
preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
snapMode: PathView.SnapToItem
path: PathMenu {
viewHeight: view.height
viewWidth: view.width
}
}
// The selection rectangle itself
CustomRect {
id: selectionRect
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3primary
height: root.itemHeight
radius: root.itemHeight / 2
width: parent.width
z: 2
}
// Hidden source: same PathView, but with the "inside selection" text color
Item {
id: selectedTextSource
anchors.fill: parent
layer.enabled: true
visible: false
PathView {
id: selectedTextView
property color delegateTextColor: root.insideTextColor
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
currentIndex: view.currentIndex
delegate: spinnerDelegate
dragMargin: view.dragMargin
highlightRangeMode: view.highlightRangeMode
implicitHeight: root.listHeight
interactive: false
model: view.model
// Keep this PathView visually locked to the real one
offset: view.offset
pathItemCount: view.pathItemCount
preferredHighlightBegin: view.preferredHighlightBegin
preferredHighlightEnd: view.preferredHighlightEnd
snapMode: view.snapMode
path: PathMenu {
viewHeight: selectedTextView.height
viewWidth: selectedTextView.width
}
}
}
// Mask matching the selection rectangle
Item {
id: selectionMask
anchors.fill: parent
layer.enabled: true
visible: false
CustomRect {
color: "white"
height: selectionRect.height
radius: selectionRect.radius
width: selectionRect.width
x: selectionRect.x
y: selectionRect.y
}
}
// Only show the "inside selection" text where the mask exists
MultiEffect {
anchors.fill: selectedTextSource
maskEnabled: true
maskInverted: false
maskSource: selectionMask
source: selectedTextSource
z: 3
}
}
}
+60
View File
@@ -0,0 +1,60 @@
pragma Singleton
import QtQuick
QtObject {
id: root
property Item activeMenu: null
property Item activeTrigger: null
function close(menu) {
if (!menu)
return;
if (activeMenu === menu) {
activeMenu = null;
activeTrigger = null;
}
menu.expanded = false;
}
function closeActive() {
if (activeMenu)
activeMenu.expanded = false;
activeMenu = null;
activeTrigger = null;
}
function forget(menu) {
if (activeMenu === menu) {
activeMenu = null;
activeTrigger = null;
}
}
function hit(item, scenePos) {
if (!item || !item.visible)
return false;
const p = item.mapFromItem(null, scenePos.x, scenePos.y);
return item.contains(p);
}
function open(menu, trigger) {
if (activeMenu && activeMenu !== menu)
activeMenu.expanded = false;
activeMenu = menu;
activeTrigger = trigger || null;
menu.expanded = true;
}
function toggle(menu, trigger) {
if (activeMenu === menu && menu.expanded)
close(menu);
else
open(menu, trigger);
}
}
+7 -5
View File
@@ -94,11 +94,13 @@ Item {
CustomListView { CustomListView {
id: clayout id: clayout
anchors.centerIn: parent anchors.bottom: parent.bottom
contentHeight: contentItem.childrenRect.height anchors.horizontalCenter: parent.horizontalCenter
anchors.margins: Appearance.padding.smaller
anchors.top: parent.top
boundsBehavior: Flickable.StopAtBounds
contentWidth: contentItem.childrenRect.width contentWidth: contentItem.childrenRect.width
highlightFollowsCurrentItem: false highlightFollowsCurrentItem: false
implicitHeight: contentItem.childrenRect.height
implicitWidth: contentItem.childrenRect.width implicitWidth: contentItem.childrenRect.width
model: listModel model: listModel
spacing: 5 spacing: 5
@@ -109,7 +111,7 @@ Item {
color: DynamicColors.palette.m3primary color: DynamicColors.palette.m3primary
implicitHeight: clayout.currentItem?.implicitHeight ?? 0 implicitHeight: clayout.currentItem?.implicitHeight ?? 0
implicitWidth: clayout.width implicitWidth: clayout.width
radius: 4 radius: Appearance.rounding.normal - Appearance.padding.smaller
y: clayout.currentItem?.y ?? 0 y: clayout.currentItem?.y ?? 0
Behavior on y { Behavior on y {
@@ -131,7 +133,7 @@ Item {
implicitHeight: 42 implicitHeight: 42
implicitWidth: 200 implicitWidth: 200
radius: 4 radius: Appearance.rounding.normal - Appearance.padding.smaller
RowLayout { RowLayout {
id: layout id: layout
+27 -33
View File
@@ -5,7 +5,6 @@ import QtQuick.Layouts
import qs.Components import qs.Components
import qs.Modules as Modules import qs.Modules as Modules
import qs.Modules.Settings.Controls import qs.Modules.Settings.Controls
import qs.Modules.Settings.Categories.Appearance
import qs.Config import qs.Config
import qs.Helpers import qs.Helpers
@@ -14,13 +13,33 @@ CustomFlickable {
contentHeight: clayout.implicitHeight contentHeight: clayout.implicitHeight
TapHandler {
acceptedButtons: Qt.LeftButton
onTapped: function (eventPoint) {
const menu = SettingsDropdowns.activeMenu;
if (!menu)
return;
const p = eventPoint.scenePosition;
if (SettingsDropdowns.hit(SettingsDropdowns.activeTrigger, p))
return;
if (SettingsDropdowns.hit(menu, p))
return;
SettingsDropdowns.closeActive();
}
}
ColumnLayout { ColumnLayout {
id: clayout id: clayout
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
CustomClippingRect { CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: colorLayout.implicitHeight + Appearance.padding.normal * 2 Layout.preferredHeight: colorLayout.implicitHeight + Appearance.padding.normal * 2
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
@@ -33,6 +52,7 @@ CustomFlickable {
anchors.margins: Appearance.padding.large anchors.margins: Appearance.padding.large
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.normal
Settings { Settings {
name: "Color" name: "Color"
@@ -56,19 +76,11 @@ CustomFlickable {
Separator { Separator {
} }
SettingInput { SettingSpinner {
name: "Schedule dark mode start" name: "Schedule dark mode"
object: Config.general.color object: Config.general.color
setting: "scheduleDarkStart" settings: ["scheduleDarkStart", "scheduleDarkEnd"]
} z: 2
Separator {
}
SettingInput {
name: "Schedule dark mode end"
object: Config.general.color
setting: "scheduleDarkEnd"
} }
Separator { Separator {
@@ -106,22 +118,6 @@ CustomFlickable {
} }
} }
} }
CustomClippingRect {
Layout.fillWidth: true
Layout.preferredHeight: idleLayout.implicitHeight + Appearance.padding.normal * 2
color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.normal - Appearance.padding.smaller
Idle {
id: idleLayout
anchors.left: parent.left
anchors.margins: Appearance.padding.large
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
}
}
} }
component Settings: CustomRect { component Settings: CustomRect {
@@ -135,9 +131,7 @@ CustomFlickable {
CustomText { CustomText {
id: text id: text
anchors.left: parent.left anchors.fill: parent
anchors.right: parent.right
anchors.top: parent.top
font.bold: true font.bold: true
font.pointSize: Appearance.font.size.large * 2 font.pointSize: Appearance.font.size.large * 2
text: settingsItem.name text: settingsItem.name
+9 -27
View File
@@ -14,13 +14,6 @@ CustomRect {
id: clayout id: clayout
anchors.fill: parent anchors.fill: parent
Settings {
name: "apps"
}
Item {
}
} }
component Settings: CustomRect { component Settings: CustomRect {
@@ -28,28 +21,17 @@ CustomRect {
required property string name required property string name
implicitHeight: 42 Layout.preferredHeight: 60
implicitWidth: 200 Layout.preferredWidth: 200
radius: 4
RowLayout { CustomText {
id: layout id: text
anchors.left: parent.left anchors.fill: parent
anchors.margins: Appearance.padding.smaller font.bold: true
anchors.right: parent.right font.pointSize: Appearance.font.size.large * 2
anchors.verticalCenter: parent.verticalCenter text: settingsItem.name
verticalAlignment: Text.AlignVCenter
CustomText {
id: text
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillHeight: true
Layout.fillWidth: true
Layout.leftMargin: Appearance.spacing.normal
text: settingsItem.name
verticalAlignment: Text.AlignVCenter
}
} }
} }
} }
@@ -0,0 +1,77 @@
import Quickshell
import Quickshell.Widgets
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Modules as Modules
import qs.Modules.Settings.Categories.Lockscreen
import qs.Config
import qs.Helpers
CustomFlickable {
id: root
contentHeight: clayout.implicitHeight
TapHandler {
acceptedButtons: Qt.LeftButton
onTapped: function (eventPoint) {
const menu = SettingsDropdowns.activeMenu;
if (!menu)
return;
const p = eventPoint.scenePosition;
if (SettingsDropdowns.hit(SettingsDropdowns.activeTrigger, p))
return;
if (SettingsDropdowns.hit(menu, p))
return;
SettingsDropdowns.closeActive();
}
}
ColumnLayout {
id: clayout
anchors.fill: parent
CustomRect {
Layout.fillWidth: true
Layout.preferredHeight: idleLayout.implicitHeight + Appearance.padding.normal * 2
color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.normal - Appearance.padding.smaller
z: -1
Idle {
id: idleLayout
anchors.left: parent.left
anchors.margins: Appearance.padding.large
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
}
}
}
component Settings: CustomRect {
id: settingsItem
required property string name
Layout.preferredHeight: 60
Layout.preferredWidth: 200
CustomText {
id: text
anchors.fill: parent
font.bold: true
font.pointSize: Appearance.font.size.large * 2
text: settingsItem.name
verticalAlignment: Text.AlignVCenter
}
}
}
@@ -36,6 +36,10 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
spacing: Appearance.spacing.smaller spacing: Appearance.spacing.smaller
Settings {
name: "Idle Monitors"
}
Repeater { Repeater {
model: [...Config.general.idle.timeouts] model: [...Config.general.idle.timeouts]
@@ -57,4 +61,23 @@ ColumnLayout {
onClicked: root.addTimeoutEntry() onClicked: root.addTimeoutEntry()
} }
component Settings: CustomRect {
id: settingsItem
required property string name
Layout.preferredHeight: 60
Layout.preferredWidth: 200
CustomText {
id: text
anchors.fill: parent
font.bold: true
font.pointSize: Appearance.font.size.large * 2
text: settingsItem.name
verticalAlignment: Text.AlignVCenter
}
}
} }
+12 -5
View File
@@ -23,13 +23,14 @@ Item {
Connections { Connections {
function onCurrentCategoryChanged() { function onCurrentCategoryChanged() {
stack.pop(); stack.pop();
if (currentCategory === "general") { if (currentCategory === "general")
stack.push(general); stack.push(general);
} else if (currentCategory === "wallpaper") { else if (currentCategory === "wallpaper")
stack.push(background); stack.push(background);
} else if (currentCategory === "appearance") { else if (currentCategory === "appearance")
stack.push(appearance); stack.push(appearance);
} else if (currentCategory === "lockscreen")
stack.push(lockscreen);
} }
target: root target: root
@@ -48,7 +49,6 @@ Item {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top anchors.top: parent.top
implicitHeight: layout.implicitHeight
implicitWidth: layout.implicitWidth implicitWidth: layout.implicitWidth
Categories { Categories {
@@ -100,4 +100,11 @@ Item {
Cat.Appearance { Cat.Appearance {
} }
} }
Component {
id: lockscreen
Cat.Lockscreen {
}
}
} }
+11 -5
View File
@@ -229,14 +229,20 @@ Item {
anchors.right: parent.right anchors.right: parent.right
visible: root.modelData.activeAction === undefined visible: root.modelData.activeAction === undefined
Item { IconButton {
Layout.fillWidth: true id: button
Layout.alignment: Qt.AlignLeft
font.pointSize: Appearance.font.size.large
icon: "add"
onClicked: console.log(button.width)
// onClicked: root.addActiveActionRequested()
} }
CustomButton { CustomText {
Layout.alignment: Qt.AlignLeft
text: qsTr("Add active action") text: qsTr("Add active action")
onClicked: root.addActiveActionRequested()
} }
} }
} }
@@ -0,0 +1,88 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
Item {
id: root
required property string name
required property var object
required property list<string> settings
function commitChoice(choice: int, setting: string): void {
root.object[setting] = choice;
Config.save();
}
function formattedValue(setting: string): string {
const value = root.object[setting];
console.log(value);
if (value === null || value === undefined)
return "";
return String(value);
}
function hourToAmPm(hour) {
var h = Number(hour) % 24;
var d = new Date(2000, 0, 1, h, 0, 0);
return Qt.formatTime(d, "h AP");
}
Layout.fillWidth: true
Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2
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
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.normal
text: qsTr("Dark mode will turn on at %1, and turn off at %2.").arg(root.hourToAmPm(root.object[root.settings[0]])).arg(root.hourToAmPm(root.object[root.settings[1]]))
}
}
SpinnerButton {
Layout.preferredHeight: Appearance.font.size.large + Appearance.padding.smaller * 2
Layout.preferredWidth: height * 2
currentIndex: root.object[root.settings[0]]
text: root.formattedValue(root.settings[0])
menu.onItemSelected: item => {
root.commitChoice(item, root.settings[0]);
}
}
SpinnerButton {
Layout.preferredHeight: Appearance.font.size.large + Appearance.padding.smaller * 2
Layout.preferredWidth: height * 2
currentIndex: root.object[root.settings[1]]
text: root.formattedValue(root.settings[1])
menu.onItemSelected: item => {
root.commitChoice(item, root.settings[1]);
}
}
}
}
@@ -0,0 +1,42 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Helpers
import qs.Config
CustomRect {
id: root
property alias currentIndex: menu.currentIndex
property alias expanded: menu.expanded
property alias label: label
property alias menu: menu
property alias text: label.text
color: DynamicColors.palette.m3primary
radius: Appearance.rounding.full
CustomText {
id: label
anchors.centerIn: parent
color: DynamicColors.palette.m3onPrimary
font.pointSize: Appearance.font.size.large
}
StateLayer {
function onClicked(): void {
SettingsDropdowns.toggle(menu, root);
}
}
PathViewMenu {
id: menu
anchors.centerIn: parent
from: 1
implicitWidth: root.width
itemHeight: root.height
to: 24
}
}