Merge branch 'main' into module/wifi
C++ / fmt (pull_request) Successful in 7s
C++ / build (pull_request) Failing after 12s
C++ / clang-tidy (pull_request) Failing after 11s
JS/TS / fmt (pull_request) Successful in 13s
JS/TS / lint (pull_request) Successful in 9s
Python / static (pull_request) Successful in 35s
Rust / build (pull_request) Successful in 1m0s
Python / verify (pull_request) Successful in 1m44s
Rust / fmt (pull_request) Successful in 45s
Rust / clippy (pull_request) Successful in 1m7s

This commit is contained in:
2026-10-22 23:21:20 +02:00
469 changed files with 40652 additions and 7947 deletions
+4 -5
View File
@@ -60,12 +60,11 @@ ItemList {
anchors.centerIn: parent
color: device.active ? Colors.palette.m3onPrimary : Colors.palette.m3onSecondaryContainer
fill: device.active ? 1 : 0
font.pointSize: Tokens.font.size.large
fontStyle: Tokens.font.icon.medium
text: root.iconName
Behavior on fill {
Anim {
}
Anim {}
}
}
}
@@ -73,13 +72,13 @@ ItemList {
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Tokens.font.size.smaller
font: Tokens.font.body.small
text: device.modelData?.description || device.modelData?.name || qsTr("Unknown")
}
MaterialIcon {
color: Colors.palette.m3primary
font.pointSize: Tokens.font.size.large
fontStyle: Tokens.font.icon.medium
opacity: device.active ? 1 : 0
text: "check"
+3 -5
View File
@@ -35,8 +35,7 @@ Item {
smoothing: Tokens.rounding.largeIncreased
Behavior on color {
CAnim {
}
CAnim {}
}
}
@@ -49,8 +48,7 @@ Item {
radius: root.open ? Tokens.rounding.largeIncreased : Tokens.rounding.largeIncreased
Behavior on anchors.margins {
Anim {
}
Anim {}
}
Behavior on radius {
Anim {
@@ -122,7 +120,7 @@ Item {
anchors.centerIn: parent
color: Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.normal
fontStyle: Tokens.font.icon.medium
text: "view_apps"
}
}
+3 -3
View File
@@ -17,9 +17,9 @@ CustomRect {
bottomLeftRadius: last ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall
bottomRightRadius: last ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall
color: Colors.tPalette.m3surfaceContainer
opacity: enabled && (!parent || parent.enabled) ? 1 : 0.5
topLeftRadius: first ? Tokens.rounding.large : Tokens.rounding.extraSmall
topRightRadius: first ? Tokens.rounding.large : Tokens.rounding.extraSmall
opacity: enabled && (parent?.enabled ?? false) ? 1 : 0.5
topLeftRadius: first ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall
topRightRadius: first ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall
Behavior on opacity {
Anim {
+29 -14
View File
@@ -10,17 +10,24 @@ import qs.Services
Item {
id: root
property bool acceptAllowed: true
required property string acceptLabel
required property Component content
required property string header
property int horizontalContentMargin
required property string icon
required property string label
required property Item rootParent
required property string settingAnchor
property bool acceptAllowed: true
property bool first: false
property int horizontalContentMargin
property string icon
property alias iconLabel: openButton.iconLabel
property string subtext: ""
property bool last: false
property bool open
property real openHeight: Math.min(rootParent.height * 0.8, 600)
property real openWidth: Math.min(rootParent.width * 0.8, 400)
required property Item rootParent
property alias rowButton: openButton
property bool separateContent
signal accepted
@@ -44,8 +51,7 @@ Item {
color: root.open ? Colors.palette.m3surfaceContainerHighest : Colors.tPalette.m3surfaceContainer
Behavior on color {
CAnim {
}
CAnim {}
}
}
@@ -56,6 +62,7 @@ Item {
enabled: false
hoverEnabled: enabled
parent: root.open ? root.rootParent : root
preventStealing: true
onClicked: root.open = false
}
@@ -75,7 +82,8 @@ Item {
backdrop.enabled: true
dialogBg.bottomLeftRadius: Tokens.rounding.largeIncreased
dialogBg.bottomRightRadius: Tokens.rounding.largeIncreased
dialogBg.radius: Tokens.rounding.largeIncreased
dialogBg.topLeftRadius: Tokens.rounding.largeIncreased
dialogBg.topRightRadius: Tokens.rounding.largeIncreased
dialogContent.opacity: 1
dialogWrapper.height: root.openHeight
dialogWrapper.width: root.openWidth
@@ -103,7 +111,7 @@ Item {
}
Anim {
properties: "opacity,radius,bottomLeftRadius,bottomRightRadius"
properties: "opacity,topLeftRadius,topRightRadius,bottomLeftRadius,bottomRightRadius"
type: Anim.DefaultEffects
}
@@ -131,12 +139,13 @@ Item {
id: dialogBg
anchors.fill: parent
bottomLeftRadius: Tokens.rounding.largeIncreased
bottomRightRadius: Tokens.rounding.largeIncreased
bottomLeftRadius: root.last ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall
bottomRightRadius: root.last ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall
deformScale: 0
group: blobGroup
opacity: blobGroup.color.a * (root.enabled ? 1 : 0.5)
radius: Tokens.rounding.extraSmall
topLeftRadius: root.first ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall
topRightRadius: root.first ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall
}
RowButton {
@@ -145,9 +154,11 @@ Item {
anchors.left: parent.left
anchors.right: parent.right
color: "transparent"
height: Math.min(implicitHeight, parent.height) // Clamp to parent height due to overshoot anim
first: root.first
height: Math.min(implicitHeight, parent.height)
icon: root.icon
last: true
last: root.last
subtext: root.subtext
text: root.label
transform: Matrix4x4 {
@@ -166,6 +177,8 @@ Item {
opacity: 0
sourceComponent: MouseArea {
preventStealing: true
onWheel: event => event.accepted = true
ColumnLayout {
@@ -175,7 +188,7 @@ Item {
spacing: 0
CustomText {
font.pointSize: Tokens.font.size.large
font: Tokens.font.title.builders.large.weight(Font.Normal).build()
text: root.header
}
@@ -216,6 +229,7 @@ Item {
TextButton {
horizontalPadding: Tokens.padding.largeIncreased
isRound: true
stateLayer.preventStealing: true
text: qsTr("Cancel")
type: TextButton.Text
verticalPadding: Tokens.padding.small
@@ -230,6 +244,7 @@ Item {
enabled: root.acceptAllowed
horizontalPadding: Tokens.padding.largeIncreased
isRound: true
stateLayer.preventStealing: true
text: root.acceptLabel
type: TextButton.Text
verticalPadding: Tokens.padding.small
+12 -4
View File
@@ -9,6 +9,8 @@ DialogRowButton {
id: root
required property var model
property var initialSelect
property var selectedItem
function keyFor(item: var): string {
@@ -65,7 +67,6 @@ DialogRowButton {
anchors.verticalCenter: parent.verticalCenter
color: item.selected ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface
elide: Text.ElideRight
font.pointSize: Tokens.font.size.smaller
text: root.labelFor(item.modelData)
}
@@ -76,7 +77,7 @@ DialogRowButton {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
color: Colors.palette.m3onTertiaryContainer
font.pointSize: Tokens.font.size.normal
fontStyle: Tokens.font.icon.medium
opacity: item.selected ? 1 : 0
text: "check"
@@ -91,7 +92,14 @@ DialogRowButton {
}
onOpenChanged: {
if (open)
selectedItem = null;
if (open) {
root.rootParent.interactive = false;
if (!initialSelect)
selectedItem = null;
else
selectedItem = initialSelect;
} else {
root.rootParent.interactive = true;
}
}
}
+4 -4
View File
@@ -24,7 +24,7 @@ ConnectedRect {
MaterialIcon {
color: root.iconColor
font.pointSize: Tokens.font.size.small
fontStyle: Tokens.font.icon.small
text: root.icon
}
}
@@ -53,14 +53,14 @@ ConnectedRect {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
font: Tokens.font.body.small
}
CustomText {
Layout.fillWidth: true
color: Colors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
font: Tokens.font.label.small
text: root.subtext
visible: root.subtext
}
@@ -72,7 +72,7 @@ ConnectedRect {
Layout.maximumWidth: root.width / 2
color: Colors.palette.m3onSurfaceVariant
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
font: Tokens.font.body.small
horizontalAlignment: Text.AlignRight
}
}
+3 -4
View File
@@ -23,8 +23,7 @@ ConnectedRect {
implicitHeight: (showList && list.count > 0 ? list.contentHeight : placeholder.implicitHeight + Tokens.padding.extraLarge * 2) + extraHeight
Behavior on implicitHeight {
Anim {
}
Anim {}
}
Loader {
@@ -46,7 +45,7 @@ ConnectedRect {
Layout.alignment: Qt.AlignHCenter
animate: true
color: Colors.palette.m3outline
font.pointSize: Tokens.font.size.large
fontStyle: Tokens.font.icon.large
text: root.placeholderIcon
}
@@ -54,7 +53,7 @@ ConnectedRect {
Layout.alignment: Qt.AlignHCenter
animate: true
color: Colors.palette.m3outline
font.pointSize: Tokens.font.size.large
font: Tokens.font.body.large
text: root.placeholderText
}
}
+5 -8
View File
@@ -54,14 +54,12 @@ ListView {
}
}
Behavior on implicitHeight {
Anim {
}
Anim {}
}
model: DelegateModel {
id: visualModel
delegate: ListRow {
}
delegate: ListRow {}
model: ScriptModel {
id: valuesModel
}
@@ -280,7 +278,7 @@ ListView {
id: dragIcon
color: Qt.alpha(Colors.palette.m3onSurfaceVariant, enabledSwitch.checked ? 1 : 0.5)
font.pointSize: Tokens.font.size.normal
fontStyle: Tokens.font.icon.medium
text: "drag_indicator"
}
@@ -290,7 +288,6 @@ ListView {
Layout.fillWidth: true
color: Qt.alpha(Colors.palette.m3onSurface, enabledSwitch.checked ? 1 : 0.5)
elide: Text.ElideRight
font.pointSize: Tokens.font.size.smaller
text: root.labelFor(item.modelData)
}
@@ -298,13 +295,13 @@ ListView {
id: enabledSwitch
checked: root.toggledFor(item.modelData)
font.pointSize: Tokens.font.size.normal
font: Tokens.font.icon.medium
onToggled: root.itemToggled(item.DelegateModel.itemsIndex, checked)
}
IconButton {
font.pointSize: Tokens.font.size.large
font: Tokens.font.icon.medium
icon: "delete"
inactiveOnColor: Colors.palette.m3error
isRound: true
+3 -66
View File
@@ -1,71 +1,8 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import ZShell.Config
import qs.Services
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: Tokens.padding.largeIncreased
anchors.margins: Tokens.padding.small
anchors.rightMargin: Tokens.padding.largeIncreased
spacing: Tokens.spacing.medium
MaterialIcon {
id: icon
color: Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.large
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
id: label
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
}
CustomText {
id: status
Layout.fillWidth: true
animate: true
color: Colors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
visible: text
}
}
MaterialIcon {
color: Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.normal
text: "chevron_right"
}
}
RowButton {
trailingIcon: "chevron_right"
subLabel.animate: true
}
+18 -8
View File
@@ -30,7 +30,7 @@ ColumnLayout {
function findAnchor(item: Item, anchor: string): Item {
if (!item)
return null;
if (item.settingAnchor !== undefined && item.settingAnchor === anchor)
if (item.settingAnchor !== undefined && item.settingAnchor === anchor) // qmllint disable missing-property
return item;
const kids = item.children;
for (let i = 0; i < kids.length; i++) {
@@ -43,8 +43,8 @@ ColumnLayout {
function highlightAnchor(anchor: string): void {
const row = findAnchor(contentChild, anchor);
if (row && row.flashHighlight !== undefined)
row.flashHighlight();
if (row && row.flashHighlight !== undefined) // qmllint disable missing-property
row.flashHighlight(); // qmllint disable missing-property
}
function scrollToAnchor(anchor: string): bool {
@@ -61,8 +61,8 @@ ColumnLayout {
root.animateScroll = true;
flickable.contentY = target;
Qt.callLater(() => root.animateScroll = false);
if (row.flashHighlight !== undefined)
row.flashHighlight();
if (row.flashHighlight !== undefined) // qmllint disable missing-property
row.flashHighlight(); // qmllint disable missing-property
return true;
}
@@ -134,6 +134,7 @@ ColumnLayout {
inactiveOnColor: Colors.palette.m3onSurfaceVariant
isRound: true
type: IconButton.Tonal
font: Tokens.font.icon.medium
onClicked: root.sState.closeSubPage()
}
@@ -142,7 +143,7 @@ ColumnLayout {
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Tokens.font.size.large
font: Tokens.font.title.large
text: root.title
}
}
@@ -154,11 +155,20 @@ ColumnLayout {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.topMargin: -topMargin
bottomMargin: Tokens.padding.extraLarge
topMargin: Tokens.padding.large
fadeAmount: 0.1
contentHeight: root.contentChild?.implicitHeight ?? 0
contentItem.children: [root.contentChild]
fadeAmount: 0.1
topMargin: Tokens.padding.large
rebound: Transition {
Anim {
properties: "x,y"
type: Anim.DefaultEffects
}
}
Behavior on contentY {
enabled: root.animateScroll
+3 -3
View File
@@ -40,7 +40,7 @@ ConnectedRect {
id: icon
color: Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.larger
fontStyle: Tokens.font.icon.medium
}
ColumnLayout {
@@ -52,7 +52,7 @@ ConnectedRect {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
font: Tokens.font.body.small
}
CustomText {
@@ -62,7 +62,7 @@ ConnectedRect {
animate: true
color: Colors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
font: Tokens.font.label.small
visible: text
}
}
+27 -6
View File
@@ -17,6 +17,7 @@ ConnectedRect {
property alias subtext: subLabel.text
property alias text: label.text
property string trailingIcon
property string activeItem
signal clicked(event: MouseEvent)
@@ -40,16 +41,17 @@ ConnectedRect {
spacing: Tokens.spacing.medium
Behavior on opacity {
Anim {
}
Anim {}
}
MaterialIcon {
id: iconLabel
color: Colors.palette.m3onSurfaceVariant
visible: root.icon !== ""
fill: 1
font.pointSize: Tokens.font.size.normal
text: root.icon
fontStyle: Tokens.font.icon.medium
}
Column {
@@ -64,7 +66,6 @@ ConnectedRect {
anchors.left: parent.left
anchors.right: parent.right
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
}
CustomText {
@@ -74,11 +75,31 @@ ConnectedRect {
anchors.right: parent.right
color: Colors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
font: Tokens.font.label.small
visible: text
}
}
Loader {
active: root.activeItem && root.subtext
asynchronous: true
visible: active
Layout.fillHeight: true
sourceComponent: CustomRect {
color: Colors.palette.m3secondaryContainer
radius: Tokens.rounding.full
implicitWidth: text.implicitWidth + Tokens.padding.medium * 2
CustomText {
id: text
anchors.centerIn: parent
color: Colors.palette.m3onSecondaryContainer
text: root.activeItem
}
}
}
Loader {
active: root.trailingIcon
asynchronous: true
@@ -86,7 +107,7 @@ ConnectedRect {
sourceComponent: MaterialIcon {
color: Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.normal
fontStyle: Tokens.font.icon.medium
text: root.trailingIcon
}
}
+127
View File
@@ -0,0 +1,127 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import qs.Components
import qs.Services
DialogRowButton {
id: root
required property var model
property var selectedItem
property var initialSelect
acceptAllowed: !!selectedItem
horizontalContentMargin: -Tokens.padding.extraSmall
separateContent: true
content: Component {
ColumnLayout {
VerticalFadeListView {
bottomMargin: Tokens.padding.large
Layout.fillHeight: true
Layout.fillWidth: true
model: ScriptModel {
values: {
const s = searchField.text;
var regex = new RegExp(s, "i");
return root.model.filter(n => regex.test(n));
}
}
spacing: 0
topMargin: Tokens.padding.large
delegate: CustomRect {
id: item
required property var modelData
readonly property bool selected: root.selectedItem === modelData
anchors.left: ListView.view.contentItem.left
anchors.margins: 1 // Gets cut off for some reason without this
anchors.right: ListView.view.contentItem.right
color: Qt.alpha(Colors.palette.m3tertiaryContainer, selected ? 1 : 0)
implicitHeight: label.implicitHeight + Tokens.padding.small * 2
radius: stateLayer.pressed ? Tokens.rounding.extraSmall : selected ? Tokens.rounding.largeIncreased : Tokens.rounding.medium
Behavior on radius {
Anim {
type: Anim.SlowEffects
}
}
StateLayer {
id: stateLayer
onClicked: root.selectedItem = item.modelData
}
CustomText {
id: label
anchors.left: parent.left
anchors.margins: Tokens.padding.large
anchors.right: item.selected ? checkIcon.left : parent.right
anchors.rightMargin: item.selected ? Tokens.spacing.medium : anchors.margins
anchors.verticalCenter: parent.verticalCenter
color: item.selected ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface
elide: Text.ElideRight
text: item.modelData
}
MaterialIcon {
id: checkIcon
anchors.margins: Tokens.padding.large
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
color: Colors.palette.m3onTertiaryContainer
fontStyle: Tokens.font.icon.medium
opacity: item.selected ? 1 : 0
text: "check"
Behavior on opacity {
Anim {
type: Anim.SlowEffects
}
}
}
}
}
SearchBar {
id: searchField
Layout.fillWidth: true
bg.border.color: Colors.palette.m3outlineVariant
bg.color: Colors.tPalette.m3surfaceContainerLowest
clearIcon.font: Tokens.font.icon.medium
clearIcon.padding: Tokens.padding.extraSmall
font: Tokens.font.body.small
placeholderText: qsTr("Search...")
searchIcon.anchors.leftMargin: Tokens.padding.large
searchIcon.fontStyle: Tokens.font.icon.medium
Behavior on bg.border.color {
CAnim {}
}
}
}
}
onOpenChanged: {
if (open) {
root.rootParent.interactive = false;
if (!initialSelect)
selectedItem = null;
else
selectedItem = initialSelect;
} else {
root.rootParent.interactive = true;
}
}
}
+1 -1
View File
@@ -13,5 +13,5 @@ CustomText {
Layout.topMargin: first ? 0 : Tokens.spacing.largeIncreased - ((parent as ColumnLayout).spacing ?? 0)
color: Colors.palette.m3onSurfaceVariant
elide: Text.ElideRight
font.pointSize: Tokens.font.size.normal
font: Tokens.font.label.medium
}
+2 -2
View File
@@ -40,14 +40,14 @@ ConnectedRect {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Tokens.font.size.smaller
font: Tokens.font.body.small
}
CustomText {
Layout.fillWidth: true
color: Colors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
font: Tokens.font.label.small
text: root.subtext
visible: root.subtext
}
+2 -2
View File
@@ -40,14 +40,14 @@ ConnectedRect {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
font: Tokens.font.body.small
}
CustomText {
id: valueLabel
color: Colors.palette.m3outline
font.pointSize: Tokens.font.size.small
font: Tokens.font.body.small
}
}
+2 -2
View File
@@ -39,14 +39,14 @@ ConnectedRect {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Tokens.font.size.smaller
font: Tokens.font.body.small
}
CustomText {
Layout.fillWidth: true
color: Colors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
font: Tokens.font.label.small
text: root.subtext
visible: root.subtext
}
+84
View File
@@ -0,0 +1,84 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import qs.Components
import qs.Services
ConnectedRect {
id: root
property alias label: label.text
property string subtext
property string value
property alias placeholderText: input.placeholderText
property string errorText
property alias maximumLength: input.maximumLength
property alias validate: input.validate
property alias validator: input.validator
property bool smallField
readonly property alias field: input
signal valueEdited(value: string)
signal editingFinished(value: string)
function clear(): void {
input.clear();
}
Component.onDestruction: {
if (value !== input.text)
editingFinished(input.text);
}
Layout.fillWidth: true
implicitHeight: rowLayout.implicitHeight + Tokens.padding.medium + Math.max(0, Tokens.padding.large - input.verticalPadding) * 2
RowLayout {
id: rowLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Tokens.padding.largeIncreased
anchors.rightMargin: Tokens.padding.largeIncreased
spacing: Tokens.spacing.medium
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
id: label
Layout.fillWidth: true
font: Tokens.font.body.small
elide: Text.ElideRight
}
CustomText {
Layout.fillWidth: true
visible: root.subtext
text: input.isError && root.errorText ? root.errorText : root.subtext
color: input.isError && root.errorText ? Colors.palette.m3error : Colors.palette.m3outline
elide: Text.ElideRight
animate: root.errorText
}
}
CustomTextField {
id: input
Layout.preferredWidth: root.smallField ? Tokens.sizes.nexus.smallTextFieldWidth : Tokens.sizes.nexus.textFieldWidth
Layout.maximumWidth: root.width / 2
Layout.alignment: Qt.AlignVCenter
verticalPadding: Tokens.padding.small
text: root.value
onTextEdited: root.valueEdited(text)
onEditingFinished: root.editingFinished(text)
}
}
}
+249
View File
@@ -0,0 +1,249 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import ZShell.Blobs
import qs.Components
import ZShell.Config
import qs.Services
Item {
id: root
property bool acceptAllowed: true
required property string acceptLabel
required property Component content
required property string header
property int horizontalContentMargin
property string icon
required property string label
property alias iconLabel: openButton.iconText
property alias rowButton: openButton
property alias contentItem: dialogContent.item
property string subtext: ""
property bool open
property real openHeight: Math.min(rootParent.height * 0.8, 400)
property real openWidth: Math.min(rootParent.width * 0.8, 600)
required property Item rootParent
property bool first: false
property bool last: false
required property string settingAnchor
property color openColor: Colors.palette.m3surfaceContainerHighest
signal accepted
signal cancelled
function reparentWrapper(): void {
const newParent = open ? rootParent : root;
const pos = dialogWrapper.mapToItem(newParent, 0, 0);
dialogWrapper.parent = newParent;
dialogWrapper.x = pos.x;
dialogWrapper.y = pos.y;
}
Layout.fillWidth: true
implicitHeight: openButton.implicitHeight
z: open || dialogTransition.running ? 2 : 0
BlobGroup {
id: blobGroup
color: root.open ? root.openColor : Colors.tPalette.m3surfaceContainer
Behavior on color {
CAnim {}
}
}
MouseArea {
id: backdrop
anchors.fill: parent
enabled: false
hoverEnabled: enabled
preventStealing: true
parent: root.open ? root.rootParent : root
onClicked: root.open = false
}
Item {
id: dialogWrapper
height: openButton.implicitHeight
width: root.width
z: 1
states: State {
name: "open"
when: root.open
PropertyChanges {
backdrop.enabled: true
dialogBg.bottomLeftRadius: Tokens.rounding.largeIncreased
dialogBg.bottomRightRadius: Tokens.rounding.largeIncreased
dialogBg.topRightRadius: Tokens.rounding.largeIncreased
dialogBg.topLeftRadius: Tokens.rounding.largeIncreased
dialogContent.opacity: 1
dialogWrapper.height: root.openHeight
dialogWrapper.width: root.openWidth
dialogWrapper.x: (root.rootParent.width - root.openWidth) / 2
dialogWrapper.y: (root.rootParent.height - root.openHeight) / 2
elevation.opacity: 1
openButton.opacity: 0
}
}
transitions: Transition {
id: dialogTransition
SequentialAnimation {
ScriptAction {
script: root.reparentWrapper()
}
Anim {
properties: "x,y"
}
}
PropertyAction {
property: "enabled"
}
Anim {
properties: "opacity,topLeftRadius,topRightRadius,bottomLeftRadius,bottomRightRadius"
type: Anim.DefaultEffects
}
Anim {
properties: "width,height"
}
}
Elevation {
id: elevation
anchors.fill: parent
bottomLeftRadius: dialogBg.bottomLeftRadius
bottomRightRadius: dialogBg.bottomRightRadius
level: 4
opacity: 0
radius: dialogBg.radius
transform: Matrix4x4 {
matrix: dialogBg.deformMatrix
}
}
BlobRect {
id: dialogBg
anchors.fill: parent
bottomLeftRadius: root.last ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall
bottomRightRadius: root.last ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall
topRightRadius: root.first ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall
topLeftRadius: root.first ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall
deformScale: 0
group: blobGroup
opacity: blobGroup.color.a * (root.enabled ? 1 : 0.5)
}
TimeDialogToggle {
id: openButton
anchors.left: parent.left
anchors.right: parent.right
color: "transparent"
height: Math.min(implicitHeight, parent.height)
subtext: root.subtext
last: root.last
first: root.first
text: root.label
transform: Matrix4x4 {
matrix: dialogBg.deformMatrix
}
onClicked: root.open = true
}
Loader {
id: dialogContent
active: opacity > 0
anchors.fill: parent
asynchronous: false
opacity: 0
sourceComponent: MouseArea {
onWheel: event => event.accepted = true
preventStealing: true
acceptedButtons: Qt.NoButton
cursorShape: undefined
enabled: dialogContent.opacity > 0
clip: true
implicitWidth: innerLayout.implicitWidth + innerLayout.anchors.margins * 2
implicitHeight: innerLayout.implicitHeight + innerLayout.anchors.margins + innerLayout.anchors.bottomMargin
ColumnLayout {
id: innerLayout
anchors.bottomMargin: Tokens.padding.largeIncreased
anchors.fill: parent
anchors.margins: Tokens.padding.extraLarge
spacing: 0
CustomText {
font: Tokens.font.title.builders.large.weight(Font.Normal).build()
text: root.header
}
Loader {
Layout.fillWidth: true
Layout.leftMargin: root.horizontalContentMargin
Layout.topMargin: Tokens.spacing.medium
Layout.bottomMargin: Tokens.spacing.medium
Layout.rightMargin: root.horizontalContentMargin
sourceComponent: root.content
}
RowLayout {
Layout.alignment: Qt.AlignRight
spacing: Tokens.spacing.extraSmall
TextButton {
horizontalPadding: Tokens.padding.largeIncreased
isRound: true
text: qsTr("Cancel")
type: TextButton.Text
verticalPadding: Tokens.padding.small
stateLayer.preventStealing: true
onClicked: {
root.cancelled();
root.open = false;
}
}
TextButton {
enabled: root.acceptAllowed
horizontalPadding: Tokens.padding.largeIncreased
isRound: true
text: root.acceptLabel
type: TextButton.Text
stateLayer.preventStealing: true
verticalPadding: Tokens.padding.small
onClicked: {
root.accepted();
root.open = false;
}
}
}
}
}
transform: Matrix4x4 {
matrix: dialogBg.deformMatrix
}
}
}
}
@@ -0,0 +1,50 @@
pragma ComponentBehavior: Bound
import QtQuick
import ZShell.Config
import qs.Helpers
import qs.Components
import qs.Services
TimeDialogRow {
id: root
required property int start
required property int end
property int endCurrent
property int startCurrent
openColor: Colors.palette.m3surfaceContainer
openHeight: Math.min(rootParent.height * 0.8, contentItem?.implicitHeight ?? 0)
openWidth: Math.min(rootParent.width * 0.8, contentItem?.implicitWidth ?? 0)
function keyFor(item: var): string {
return item.id;
}
function labelFor(item: var): string {
return item.label;
}
horizontalContentMargin: -Tokens.padding.extraSmall
content: Component {
TimeInput {
id: timeContent
start: root.start
end: root.end
onEndEdit: value => root.endCurrent = value
onStartEdit: value => root.startCurrent = value
}
}
onOpenChanged: {
if (open) {
root.rootParent.interactive = false;
root.z = 5;
} else {
root.rootParent.interactive = true;
root.z = 1;
}
}
}
@@ -10,12 +10,14 @@ ConnectedRect {
property alias checked: switchButton.checked
property int horizontalPadding: Tokens.padding.largeIncreased
required property Component popup
property alias subtext: subtext.text
property alias text: text.text
property alias icon: icon
property string iconText: "open_in_new"
property int verticalPadding: Tokens.padding.small
signal clicked(checked: bool)
signal clicked
signal toggled(checked: bool)
Layout.fillWidth: true
implicitHeight: layout.implicitHeight + verticalPadding * 2
@@ -32,14 +34,14 @@ ConnectedRect {
CustomText {
id: text
font.pointSize: Tokens.font.size.smaller
font: Tokens.font.body.small
}
CustomText {
id: subtext
color: Colors.palette.m3outline
font.pointSize: Tokens.font.size.small
font: Tokens.font.label.small
wrapMode: Text.WordWrap
}
}
@@ -50,11 +52,11 @@ ConnectedRect {
anchors.right: switchButton.left
anchors.rightMargin: Tokens.spacing.medium
anchors.verticalCenter: parent.verticalCenter
text: "open_in_new"
text: root.iconText
}
StateLayer {
onClicked: PopupManager.requestOpen(root.popup)
onClicked: root.clicked()
}
CustomSwitch {
@@ -64,6 +66,6 @@ ConnectedRect {
anchors.rightMargin: root.horizontalPadding
anchors.verticalCenter: parent.verticalCenter
onClicked: root.clicked(checked)
onClicked: root.toggled(checked)
}
}
+421 -519
View File
@@ -6,15 +6,13 @@ import qs.Components
import qs.Helpers
import qs.Services
CustomClippingRect {
ColumnLayout {
id: root
required property var object
required property list<string> settings
property bool shouldBeActive: true
signal applySettings(startTime: int, endTime: int)
signal close
required property int start
required property int end
signal endEdit(time: int)
signal startEdit(time: int)
function convertHour(timeValue: int): int {
return Math.floor(timeValue / 60);
@@ -28,555 +26,459 @@ CustomClippingRect {
return hour * 60 + minute;
}
color: Colors.palette.m3surfaceContainer
implicitHeight: column.implicitHeight + column.anchors.margins * 2 + buttonRow.implicitHeight + buttonRow.anchors.topMargin
implicitWidth: column.implicitWidth + column.anchors.margins * 2
radius: Tokens.rounding.largeIncreased
spacing: Tokens.spacing.medium
ColumnLayout {
id: column
RowLayout {
CustomRect {
id: startHourRect
anchors.left: parent.left
anchors.margins: Tokens.padding.largeIncreased
anchors.right: parent.right
anchors.top: parent.top
spacing: Tokens.spacing.medium
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: startHourField.focus ? Colors.palette.m3onPrimaryContainer : Colors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Tokens.rounding.small
CustomRect {
anchors.fill: parent
border.color: startHourField.focus ? Colors.palette.m3primaryContainer : Colors.palette.m3surfaceContainerHighest
border.width: startHourField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {}
}
}
TextFieldBase {
id: startHourField
function setConfigText(): string {
var val = root.convertHour(root.start);
if (val === 0) {
return "00";
}
return String(val);
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
color: focus ? Colors.palette.m3primaryContainer : Colors.palette.m3onSurface
font.family: "Roboto"
font.letterSpacing: -0.25
font.pixelSize: 56
font.weight: 400
horizontalAlignment: TextInput.AlignHCenter
text: setConfigText()
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();
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
onTextChanged: {
var mins = parseInt(startMinuteField.text);
const hours = root.convertToMinutes(parseInt(startHourField.text));
root.startEdit(hours + mins);
}
}
}
CustomText {
text: qsTr("Select time")
id: startSeparator
font: Tokens.font.body.builders.large.size(28).weight(Font.DemiBold).build()
text: ":"
}
RowLayout {
CustomRect {
id: startHourRect
CustomRect {
id: startMinuteRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: startHourField.focus ? Colors.palette.m3onPrimaryContainer : Colors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Tokens.rounding.small
CustomRect {
anchors.fill: parent
border.color: startHourField.focus ? Colors.palette.m3primaryContainer : Colors.palette.m3surfaceContainerHighest
border.width: startHourField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {
}
}
}
TextFieldBase {
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 ? Colors.palette.m3primaryContainer : Colors.palette.m3onSurface
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: Tokens.font.size.extraLarge
text: ":"
}
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: startMinuteField.focus ? Colors.palette.m3onPrimaryContainer : Colors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Tokens.rounding.small
CustomRect {
id: startMinuteRect
anchors.fill: parent
border.color: startMinuteField.focus ? Colors.palette.m3primaryContainer : Colors.palette.m3surfaceContainerHighest
border.width: startMinuteField.focus ? 2 : 0
radius: parent.radius - border.width
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: startMinuteField.focus ? Colors.palette.m3onPrimaryContainer : Colors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Tokens.rounding.small
Behavior on border.width {
Anim {}
}
}
CustomRect {
anchors.fill: parent
border.color: startMinuteField.focus ? Colors.palette.m3primaryContainer : Colors.palette.m3surfaceContainerHighest
border.width: startMinuteField.focus ? 2 : 0
radius: parent.radius - border.width
TextFieldBase {
id: startMinuteField
Behavior on border.width {
Anim {
}
function setConfigText(): string {
var val = root.convertMinute(root.start);
if (val === 0) {
return "00";
}
return String(val);
}
TextFieldBase {
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 ? Colors.palette.m3primaryContainer : Colors.palette.m3onSurface
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();
}
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
color: focus ? Colors.palette.m3primaryContainer : Colors.palette.m3onSurface
font.family: "Roboto"
font.letterSpacing: -0.25
font.pixelSize: 56
font.weight: 400
horizontalAlignment: TextInput.AlignHCenter
text: setConfigText()
verticalAlignment: TextInput.AlignVCenter
Keys.onPressed: event => {
if (event.key === Qt.Key_Backspace) {
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: Tokens.spacing.largeIncreased
}
CustomRect {
id: endHourRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: endHourField.focus ? Colors.palette.m3onPrimaryContainer : Colors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Tokens.rounding.small
CustomRect {
anchors.fill: parent
border.color: endHourField.focus ? Colors.palette.m3primaryContainer : Colors.palette.m3surfaceContainerHighest
border.width: endHourField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {
if (startMinuteField.text.length >= 2) {
startMinuteField.text = "0" + startMinuteField.text[0];
} else if (startMinuteField.text.length === 1) {
startMinuteField.text = "0";
}
}
}
TextFieldBase {
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 ? Colors.palette.m3primaryContainer : Colors.palette.m3onSurface
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();
}
}
return;
} else if (event.key === Qt.Key_Escape) {
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: Tokens.font.size.extraLarge
text: ":"
}
CustomRect {
id: endMinuteRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: endMinuteField.focus ? Colors.palette.m3onPrimaryContainer : Colors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Tokens.rounding.small
CustomRect {
anchors.fill: parent
border.color: endMinuteField.focus ? Colors.palette.m3primaryContainer : Colors.palette.m3surfaceContainerHighest
border.width: endMinuteField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {
}
}
}
TextFieldBase {
id: endMinuteField
function setConfigText(setting: string): string {
var val = root.convertMinute(root.object[setting]);
if (val === 0) {
return "00";
}
return String(val);
startMinuteField.text = setConfigText();
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;
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
color: focus ? Colors.palette.m3primaryContainer : Colors.palette.m3onSurface
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();
}
}
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
event.accepted = true;
}
onCursorPositionChanged: cursorPosition = 2
onTextEdited: {
if (endMinuteField.text === "")
var digit = event.text;
var textLen = startMinuteField.text.length;
if (textLen >= 2 && startMinuteField.text[0] !== '0') {
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;
}
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
onTextChanged: {
var mins = parseInt(startMinuteField.text);
const hours = root.convertToMinutes(parseInt(startHourField.text));
root.startEdit(hours + mins);
}
}
}
RowLayout {
CustomText {
Layout.preferredWidth: spacer.x + spacer.width
text: qsTr("Start")
Item {
id: spacer
Layout.preferredWidth: Tokens.spacing.largeIncreased
}
CustomRect {
id: endHourRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: endHourField.focus ? Colors.palette.m3onPrimaryContainer : Colors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Tokens.rounding.small
CustomRect {
anchors.fill: parent
border.color: endHourField.focus ? Colors.palette.m3primaryContainer : Colors.palette.m3surfaceContainerHighest
border.width: endHourField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {}
}
}
CustomText {
Layout.preferredWidth: endMinuteRect.width + endHourRect.width
text: qsTr("End")
TextFieldBase {
id: endHourField
function setConfigText(): string {
var val = root.convertHour(root.end);
if (val === 0) {
return "00";
}
return String(val);
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
color: focus ? Colors.palette.m3primaryContainer : Colors.palette.m3onSurface
font.family: "Roboto"
font.letterSpacing: -0.25
font.pixelSize: 56
font.weight: 400
horizontalAlignment: TextInput.AlignHCenter
text: setConfigText()
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();
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
onTextChanged: {
var mins = parseInt(endMinuteField.text);
const hours = root.convertToMinutes(parseInt(endHourField.text));
root.endEdit(hours + mins);
}
}
}
CustomText {
id: endSeparator
font: Tokens.font.body.builders.large.size(28).weight(Font.DemiBold).build()
text: ":"
}
CustomRect {
id: endMinuteRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: endMinuteField.focus ? Colors.palette.m3onPrimaryContainer : Colors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Tokens.rounding.small
CustomRect {
anchors.fill: parent
border.color: endMinuteField.focus ? Colors.palette.m3primaryContainer : Colors.palette.m3surfaceContainerHighest
border.width: endMinuteField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {}
}
}
TextFieldBase {
id: endMinuteField
function setConfigText(): string {
var val = root.convertMinute(root.end);
if (val === 0) {
return "00";
}
return String(val);
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
color: focus ? Colors.palette.m3primaryContainer : Colors.palette.m3onSurface
font.family: "Roboto"
font.letterSpacing: -0.25
font.pixelSize: 56
font.weight: 400
horizontalAlignment: TextInput.AlignHCenter
text: setConfigText()
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();
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
onTextChanged: {
var mins = parseInt(endMinuteField.text);
const hours = root.convertToMinutes(parseInt(endHourField.text));
root.endEdit(hours + mins);
}
}
}
}
RowLayout {
id: buttonRow
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: Tokens.padding.largeIncreased
anchors.right: parent.right
anchors.top: column.bottom
anchors.topMargin: Tokens.spacing.medium
Item {
id: buttonSpacer
Layout.fillWidth: true
CustomText {
Layout.preferredWidth: spacer.x + spacer.width
text: qsTr("Start")
}
ButtonRow {
spacing: Tokens.spacing.medium
IconTextButton {
font.pointSize: Tokens.font.size.normal
icon: "close"
inactiveColor: Colors.layer(Colors.palette.m3surfaceContainerHighest, 2)
inactiveOnColor: Colors.palette.m3onSurfaceVariant
isRound: true
isToggle: false
shapeMorph: true
shapeMorphExpansion: pressed ? 12 : 0
text: "Cancel"
onClicked: root.close()
}
IconTextButton {
font.pointSize: Tokens.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);
}
}
CustomText {
Layout.preferredWidth: endMinuteRect.width + endHourRect.width
text: qsTr("End")
}
}
}
+13 -6
View File
@@ -12,6 +12,7 @@ CustomSwitch {
property alias last: bg.last
property string settingAnchor
property string subtext
property string iconText
function flashHighlight(): void {
bg.flashHighlight();
@@ -19,6 +20,7 @@ CustomSwitch {
Layout.fillWidth: true
cLayer: 2
font: Tokens.font.body.small
horizontalPadding: Tokens.padding.largeIncreased
implicitHeight: Math.max(implicitContentHeight, implicitIndicatorHeight) + verticalPadding * 2
implicitWidth: implicitContentWidth + implicitIndicatorWidth + horizontalPadding * 2
@@ -58,13 +60,12 @@ CustomSwitch {
anchors.left: parent.left
anchors.right: parent.right
elide: Text.ElideRight
font.pointSize: Tokens.font.size.smaller
font: root.font
opacity: root.enabled ? 1 : 0.5
text: root.text
Behavior on opacity {
Anim {
}
Anim {}
}
}
@@ -75,16 +76,22 @@ CustomSwitch {
anchors.right: parent.right
color: Colors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
font: Tokens.font.label.small
opacity: root.enabled ? 1 : 0.5
text: root.subtext
visible: root.subtext
Behavior on opacity {
Anim {
}
Anim {}
}
}
MaterialIcon {
id: icon
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
text: root.iconText ?? ""
}
}
}
+274 -162
View File
@@ -14,8 +14,20 @@ Item {
id: wrapper
property int buttonRowHeight: Tokens.padding.large * 3
property bool changesMade: false
readonly property bool changesMade: {
cropRevision;
const r = cropRectLoader.item;
if (!r || !currentScreen || scaledImg.status !== Image.Ready)
return false;
const t = r.savedTarget();
return Math.abs(r.x - t.x) > 0.5 || Math.abs(r.y - t.y) > 0.5 || Math.abs(r.zoom - t.zoom) > 0.001;
}
property int cropRevision: 0
readonly property var currentScreen: screens.length > selectedScreenIndex ? screens[selectedScreenIndex] : null
readonly property real maxZoom: 5.0
property var screens: []
property int selectedScreenIndex: 0
property bool shouldBeActive: true
@@ -26,12 +38,13 @@ Item {
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;
const cropXPercent = (cropRect.x - cropRect.imageX) / cropRect.imageW;
const cropYPercent = (cropRect.y - cropRect.imageY) / cropRect.imageH;
const cropWidthPercent = cropRect.width / cropRect.imageW;
const cropHeightPercent = cropRect.height / cropRect.imageH;
Wallpapers.setCrop(currentScreen.name, Qt.rect(cropXPercent, cropYPercent, cropWidthPercent, cropHeightPercent), cropRect.zoom);
cropRevision++;
}
function refreshScreens(): void {
@@ -49,6 +62,10 @@ Item {
Qt.callLater(syncCropToScreen);
}
function sliderFromZoom(zoom: real): real {
return (1 - 1 / zoom) / (1 - 1 / maxZoom);
}
function syncCropToScreen(): void {
if (cropRectLoader.item)
cropRectLoader.item.restoreFromData();
@@ -71,6 +88,10 @@ Item {
cropRect.clampToBounds();
}
function zoomFromSlider(t: real): real {
return 1 / (1 - t * (1 - 1 / maxZoom));
}
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: shouldBeActive ? 430 : 0
@@ -99,34 +120,10 @@ Item {
Qt.callLater(syncCropToScreen);
}
IconButton {
anchors.margins: Tokens.padding.small
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.bottom: imageSlot.top
anchors.bottomMargin: Tokens.spacing.medium
anchors.left: parent.left
anchors.leftMargin: Tokens.padding.extraLarge * 2
@@ -144,6 +141,7 @@ Item {
required property var modelData
fillWidth: true
font: Tokens.font.body.builders.large.weight(Font.DemiBold).build()
inactiveColor: isCurrent ? Colors.palette.m3primary : Qt.alpha(Colors.palette.m3surfaceContainer, 0.7)
inactiveOnColor: isCurrent ? Colors.palette.m3onPrimary : Qt.alpha(Colors.palette.m3onSurface, 0.7)
isRound: true
@@ -170,24 +168,54 @@ Item {
Layout.leftMargin: Tokens.padding.small
Layout.preferredHeight: Tokens.padding.medium * 3
Layout.rightMargin: Tokens.padding.small
from: 1.0
implicitHeight: Tokens.padding.small * 3
insetIcon: "crop"
to: 5.0
value: cropRectLoader.item ? cropRectLoader.item.zoom : 1.0
value: cropRectLoader.item ? wrapper.sliderFromZoom(cropRectLoader.item.zoom) : 0
onInteraction: value => {
wrapper.zoomClipRect(1 + (value * 4));
wrapper.changesMade = true;
wrapper.zoomClipRect(wrapper.zoomFromSlider(value));
}
}
ButtonRow {
spacing: Tokens.spacing.medium
IconTextButton {
enabled: wrapper.changesMade
font: Tokens.font.body.large
horizontalPadding: Tokens.padding.large
icon: "close"
inactiveColor: Colors.layer(Colors.palette.m3surfaceContainerHighest, 2)
inactiveOnColor: Colors.palette.m3onSurfaceVariant
isRound: true
shapeMorph: true
text: qsTr("Cancel")
onClicked: {
wrapper.syncCropToScreen();
}
}
IconTextButton {
enabled: wrapper.changesMade
font: Tokens.font.body.large
horizontalPadding: Tokens.padding.large
icon: "check"
inactiveColor: Colors.palette.m3secondary
inactiveOnColor: Colors.palette.m3onSecondary
isRound: true
shapeMorph: true
text: qsTr("Apply")
onClicked: {
wrapper.applyCrop();
}
}
}
}
Image {
id: scaledImg
property var displayData
property real monitorScale: 1.0
Item {
id: imageSlot
anchors.bottom: sliderLayout.top
anchors.bottomMargin: Tokens.spacing.medium
@@ -195,147 +223,231 @@ Item {
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: wrapper.buttonRowHeight + Tokens.spacing.medium
// 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();
CustomClippingRect {
id: fitBox
wrapper.syncCropToScreen();
}
}
onSourceChanged: {
if (cropRectLoader.item) {
if (!wrapper.screens.length)
wrapper.refreshScreens();
readonly property real aspect: scaledImg.implicitHeight > 0 ? scaledImg.implicitWidth / scaledImg.implicitHeight : 1
readonly property real fitW: imageSlot.height * aspect
wrapper.syncCropToScreen();
}
}
onStatusChanged: {
if (scaledImg.status == Image.Ready && cropRectLoader.item) {
if (!wrapper.screens.length)
wrapper.refreshScreens();
anchors.centerIn: parent
implicitHeight: Math.round(fitW / aspect)
implicitWidth: Math.round(fitW)
radius: Tokens.rounding.largeIncreased
wrapper.syncCropToScreen();
}
}
Loader {
active: opacity > 0
anchors.centerIn: parent
opacity: scaledImg.status === Image.Ready ? 0 : 1
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;
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
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
}
sourceComponent: CustomRect {
color: Colors.palette.m3primaryContainer
implicitHeight: loadingIndicator.implicitSize + Tokens.padding.large * 2
implicitWidth: loadingIndicator.implicitSize + Tokens.padding.large * 2
radius: Tokens.rounding.full
function centerInImage() {
x = imageX + (scaledImg.paintedWidth - width) / 2;
y = imageY + (scaledImg.paintedHeight - height) / 2;
LoadingIndicator {
id: loadingIndicator
anchors.centerIn: parent
containsIcon: true
implicitSize: Math.min(fitBox.width, fitBox.height) * 0.3
}
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);
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: Colors.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
Image {
id: scaledImg
function updateCrop(mouseX, mouseY) {
if (!cropRectLoader.item)
return;
anchors.fill: parent
asynchronous: true
fillMode: Image.PreserveAspectFit
mipmap: true
opacity: status === Image.Ready ? 1 : 0
retainWhileLoading: true
smooth: true
source: Wallpapers.current
sourceSize.height: {
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
return imageSlot.height * dpr;
}
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;
Behavior on opacity {
Anim {
type: Anim.SlowEffects
}
}
}
anchors.fill: parent
hoverEnabled: true
preventStealing: true
Loader {
id: cropRectLoader
onPositionChanged: mouse => {
if (pressed) {
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 = imageH;
let fittedWidth = fittedHeight * aspectRatio;
if (fittedWidth > imageW) {
fittedWidth = imageW;
fittedHeight = fittedWidth / aspectRatio;
}
return fittedWidth;
}
readonly property real imageH: Math.round(fitBox.height)
readonly property real imageW: Math.round(fitBox.width)
readonly property real imageX: 0
readonly property real imageY: 0
property real imgAspectRatio: imageH > 0 ? imageW / imageH : 1
property real zoom: 1.0
function centerInImage() {
x = Math.round(imageX + (imageW - width) / 2);
y = Math.round(imageY + (imageH - height) / 2);
}
function clampToBounds() {
x = Math.max(imageX, Math.min(x, imageX + imageW - width));
y = Math.max(imageY, Math.min(y, imageY + imageH - height));
}
function restoreFromData() {
const t = savedTarget();
zoom = t.zoom;
x = t.x;
y = t.y;
}
// Where the rect should be according to saved data (same rules as a restore).
function savedTarget() {
const data = Wallpapers.getCrop(wrapper.currentScreen.name);
const hasCrop = 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);
const z = hasCrop && data.zoom > 0 ? data.zoom : 1.0;
const w = Math.round(baseWidth / z);
const h = Math.round(baseHeight / z);
let tx = hasCrop ? Math.round(imageX + data.x * imageW) : Math.round(imageX + (imageW - w) / 2);
let ty = hasCrop ? Math.round(imageY + data.y * imageH) : Math.round(imageY + (imageH - h) / 2);
tx = Math.max(imageX, Math.min(tx, imageX + imageW - w));
ty = Math.max(imageY, Math.min(ty, imageY + imageH - h));
return {
x: tx,
y: ty,
zoom: z
};
}
color: zoomSlider.dragging || mouse.pressed ? Qt.alpha(Colors.palette.m3tertiary, 0.3) : "transparent"
height: Math.round(baseHeight / zoom)
width: Math.round(baseWidth / zoom)
Behavior on color {
CAnim {
}
}
Component.onCompleted: {
restoreFromData();
}
onHeightChanged: clampToBounds()
onWidthChanged: clampToBounds()
}
}
}
ShaderEffect {
property color borderColor: Colors.palette.m3tertiary
property real borderWidth: 2
property real cornerRadius: fitBox.radius
property vector4d cropBox: cropRectLoader.item ? Qt.vector4d(snap(cropRectLoader.item.x), snap(cropRectLoader.item.y), snap(cropRectLoader.item.width), snap(cropRectLoader.item.height)) : Qt.vector4d(0, 0, 0, 0)
readonly property real dpr: (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1
property vector2d itemSize: Qt.vector2d(width, height)
property real pixelScale: dpr
function snap(v: real): real {
return Math.round(v * dpr) / dpr;
}
anchors.fill: parent
fragmentShader: Qt.resolvedUrl(`${Quickshell.shellDir}/assets/shaders/crop_sdf.frag.qsb`)
}
MouseArea {
id: mouse
property real grabX: 0
property real grabY: 0
function beginGrab(mx, my): void {
const r = cropRectLoader.item;
if (!r)
return;
const inside = insideCropRect(mx, my);
if (inside) {
grabX = mx - r.x;
grabY = my - r.y;
} else {
grabX = r.width * 0.5;
grabY = r.height * 0.5;
}
}
function insideCropRect(mx, my): bool {
const r = cropRectLoader.item;
if (!r)
return false;
return mx >= r.x && mx <= r.x + r.width && my >= r.y && my <= r.y + r.height;
}
function updateCrop(mx, my): void {
if (!cropRectLoader.item)
return;
const cropRect = cropRectLoader.item;
let nx = mx - grabX;
let ny = my - grabY;
nx = Math.max(cropRect.imageX, Math.min(nx, cropRect.imageX + cropRect.imageW - cropRect.width));
ny = Math.max(cropRect.imageY, Math.min(ny, cropRect.imageY + cropRect.imageH - cropRect.height));
cropRect.x = nx;
cropRect.y = ny;
}
anchors.fill: parent
cursorShape: pressed ? Qt.ClosedHandCursor : insideCropRect(mouseX, mouseY) ? Qt.OpenHandCursor : Qt.PointingHandCursor
hoverEnabled: true
preventStealing: true
onPositionChanged: mouse => {
if (pressed) {
updateCrop(mouse.x, mouse.y);
}
}
onPressed: mouse => {
beginGrab(mouse.x, mouse.y);
updateCrop(mouse.x, mouse.y);
wrapper.changesMade = true;
}
}
onPressed: mouse => {
updateCrop(mouse.x, mouse.y);
wrapper.changesMade = true;
}
onReleased: {
wrapper.changesMade = true;
}
}
}
}
+1 -6
View File
@@ -24,8 +24,7 @@ CustomClippingRect {
radius: Tokens.rounding.largeIncreased + Tokens.padding.small
Behavior on blobColor {
CAnim {
}
CAnim {}
}
TapHandler {
@@ -70,8 +69,4 @@ CustomClippingRect {
anchors.top: parent.top
sState: root.sState
}
PopupOverlay {
anchors.fill: parent
}
}
+4 -5
View File
@@ -18,16 +18,15 @@ ColumnLayout {
Layout.fillWidth: true
bg.border.color: Colors.palette.m3outlineVariant
bg.color: Colors.tPalette.m3surfaceContainerLowest
clearIcon.font.pointSize: Tokens.font.size.large
clearIcon.font: Tokens.font.icon.medium
clearIcon.padding: Tokens.padding.extraSmall
font.pointSize: Tokens.font.size.normal
font: Tokens.font.body.large
placeholderText: qsTr("Search settings")
searchIcon.anchors.leftMargin: Tokens.padding.largeIncreased
searchIcon.font.pointSize: Tokens.font.size.large
searchIcon.fontStyle: Tokens.font.icon.medium
Behavior on bg.border.color {
CAnim {
}
CAnim {}
}
Binding {
+18 -25
View File
@@ -81,14 +81,10 @@ VerticalFadeFlickable {
topLeftRadius: stateLayer.pressed ? Tokens.rounding.medium : isCurrentPage ? Tokens.rounding.extraLarge : isCategoryStart ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall
topRightRadius: stateLayer.pressed ? Tokens.rounding.medium : isCurrentPage ? Tokens.rounding.extraLarge : isCategoryStart ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall
RadiusBehavior on bottomLeftRadius {
}
RadiusBehavior on bottomRightRadius {
}
RadiusBehavior on topLeftRadius {
}
RadiusBehavior on topRightRadius {
}
RadiusBehavior on bottomLeftRadius {}
RadiusBehavior on bottomRightRadius {}
RadiusBehavior on topLeftRadius {}
RadiusBehavior on topRightRadius {}
StateLayer {
id: stateLayer
@@ -107,7 +103,7 @@ VerticalFadeFlickable {
anchors.fill: parent
anchors.margins: Tokens.padding.large
spacing: Tokens.spacing.small
spacing: Tokens.spacing.medium
CustomRect {
Layout.bottomMargin: -1
@@ -122,7 +118,7 @@ VerticalFadeFlickable {
anchors.verticalCenterOffset: 1
color: item.isCurrentPage ? Colors.palette.m3onPrimary : Colors.palette.m3onSecondaryContainer
fill: item.modelData.noFill ? 0 : 1
font.pointSize: Tokens.font.size.large
fontStyle: Tokens.font.icon.builders.medium.weight(Font.Medium).build()
grade: 25
text: item.modelData.icon
}
@@ -135,6 +131,7 @@ VerticalFadeFlickable {
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
font: Tokens.font.body.medium
text: item.modelData.name
}
@@ -142,6 +139,7 @@ VerticalFadeFlickable {
Layout.fillWidth: true
color: Colors.palette.m3onSurfaceVariant
elide: Text.ElideRight
font: Tokens.font.label.small
text: item.modelData.description
}
}
@@ -198,7 +196,7 @@ VerticalFadeFlickable {
MaterialIcon {
color: Colors.palette.m3primary
fill: 1
font.pointSize: Tokens.font.size.large
fontStyle: Tokens.font.icon.builders.medium.weight(Font.Medium).build()
text: group.modelData.icon
}
@@ -206,7 +204,7 @@ VerticalFadeFlickable {
Layout.fillWidth: true
color: Colors.palette.m3secondary
elide: Text.ElideRight
font.pointSize: Tokens.font.size.large
font: Tokens.font.body.large
text: group.modelData.page
}
}
@@ -262,14 +260,10 @@ VerticalFadeFlickable {
topRightRadius: layer.pressed ? Tokens.rounding.largeIncreased : isFirst ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall
width: cardList.width
RadiusBehavior on bottomLeftRadius {
}
RadiusBehavior on bottomRightRadius {
}
RadiusBehavior on topLeftRadius {
}
RadiusBehavior on topRightRadius {
}
RadiusBehavior on bottomLeftRadius {}
RadiusBehavior on bottomRightRadius {}
RadiusBehavior on topLeftRadius {}
RadiusBehavior on topRightRadius {}
ColumnLayout {
id: resultLayout
@@ -283,7 +277,7 @@ VerticalFadeFlickable {
Layout.fillWidth: true
color: Colors.palette.m3onSurfaceVariant
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
font: Tokens.font.label.small
text: {
const labels = result.modelData.crumbLabels.slice(1);
const section = result.modelData.section;
@@ -297,7 +291,6 @@ VerticalFadeFlickable {
Layout.fillWidth: true
color: Colors.palette.m3onSurface
elide: Text.ElideRight
font.pointSize: Tokens.font.size.normal
text: SettingsSearcher.highlight(result.modelData.title, root.search, Colors.palette.m3primary)
textFormat: text.includes("<font") ? Text.StyledText : Text.PlainText
}
@@ -306,7 +299,7 @@ VerticalFadeFlickable {
Layout.fillWidth: true
color: Colors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
font: Tokens.font.label.small
text: SettingsSearcher.highlight(result.modelData.subtext, root.search, Colors.palette.m3primary)
textFormat: text.includes("<font") ? Text.StyledText : Text.PlainText
visible: result.modelData.subtext.length > 0
@@ -319,7 +312,8 @@ VerticalFadeFlickable {
z: 1
onClicked: {
root.sState.jumpToSetting(result.modelData.pageIdx, result.modelData.subPath, result.modelData.anchor);
const target = result.modelData.targetSubPath;
root.sState.jumpToSetting(result.modelData.pageIdx, target.length > 0 ? target : result.modelData.subPath, target.length > 0 ? "" : result.modelData.anchor);
}
}
@@ -349,7 +343,6 @@ VerticalFadeFlickable {
Layout.fillWidth: true
Layout.topMargin: Tokens.padding.large
color: Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.normal
horizontalAlignment: Text.AlignHCenter
text: qsTr("No matching settings")
visible: root.searching && root.results.length === 0
+38 -55
View File
@@ -11,6 +11,7 @@ import qs.Modules.Settings.Pages.Audio
import qs.Modules.Settings.Pages.Apps
import qs.Modules.Settings.Pages.Panels
import qs.Modules.Settings.Pages.Panels.Bar
import qs.Modules.Settings.Pages.Panels.Sidebar
import qs.Modules.Settings.Pages.Services
import qs.Modules.Settings.Pages.Network
import qs.Services
@@ -24,13 +25,15 @@ QtObject {
// Wallpaper & style
StackPage {
Component {
WallpaperPage {
}
WallpaperPage {}
}
Component {
WallpaperSelect {
}
WallpaperSelect {}
}
Component {
ColorsFonts {}
}
}
},
@@ -38,8 +41,7 @@ QtObject {
// Screenshot
StackPage {
Component {
ScreenshotPage {
}
ScreenshotPage {}
}
}
},
@@ -48,46 +50,38 @@ QtObject {
Component {
StackPage {
Component {
NetworkPage {
}
NetworkPage {}
}
Component {
AllNetworks {
}
AllNetworks {}
}
Component {
SavedNetworks {
}
SavedNetworks {}
}
Component {
AddNetwork {
}
AddNetwork {}
}
Component {
OldNetwork {
}
OldNetwork {}
}
}
},
Component {
PlaceholderComp {
}
PlaceholderComp {}
},
Component {
// Audio
StackPage {
Component {
AudioPage {
}
AudioPage {}
}
Component {
AppVolumes {
}
AppVolumes {}
}
}
},
@@ -96,49 +90,45 @@ QtObject {
Component {
StackPage {
Component {
PanelsPage {
}
PanelsPage {}
}
Component {
BarPanel {
}
BarPanel {}
}
Component {
DashboardPanel {
}
DashboardPanel {}
}
Component {
ResourcesPanel {
}
ResourcesPanel {}
}
Component {
LauncherPanel {
}
LauncherPanel {}
}
Component {
SidebarPanel {
}
SidebarPanel {}
}
// Bar sub pages
Component {
BarTray {
}
BarTray {}
}
Component {
BarStatusIcons {
}
BarStatusIcons {}
}
Component {
BarClock {
}
BarClock {}
}
// Sidebar sub pages
Component {
SidebarLlm {}
}
}
},
@@ -147,18 +137,15 @@ QtObject {
Component {
StackPage {
Component {
AppsPage {
}
AppsPage {}
}
Component {
AllApps {
}
AllApps {}
}
Component {
AppInfo {
}
AppInfo {}
}
}
},
@@ -167,13 +154,11 @@ QtObject {
Component {
StackPage {
Component {
ServicesPage {
}
ServicesPage {}
}
Component {
NotificationsPage {
}
NotificationsPage {}
}
}
},
@@ -182,15 +167,13 @@ QtObject {
Component {
StackPage {
Component {
AboutPage {
}
AboutPage {}
}
}
}
]
readonly property Component placeholderComp: Component {
PlaceholderComp {
}
PlaceholderComp {}
}
component PlaceholderComp: Item {
@@ -203,7 +186,7 @@ QtObject {
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
color: Colors.palette.m3outlineVariant
font.pointSize: Tokens.font.size.extraLarge
fontStyle: Tokens.font.icon.extraLarge
text: "handyman"
}
+1
View File
@@ -1,6 +1,7 @@
import QtQuick
import qs.Components
import ZShell.Config
import qs.Services
Item {
id: root
+2 -2
View File
@@ -56,14 +56,14 @@ PageBase {
CustomText {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Tokens.spacing.small
font.pointSize: Tokens.font.size.large
font: Tokens.font.headline.large
text: "ZShell"
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.normal
font: Tokens.font.body.medium
text: ZUtils.version ? `v${ZUtils.version}` : "…"
}
}
+6 -6
View File
@@ -52,11 +52,11 @@ PageBase {
anchors.leftMargin: Tokens.padding.largeIncreased
anchors.margins: Tokens.padding.small
anchors.rightMargin: Tokens.padding.largeIncreased
spacing: Tokens.spacing.small
spacing: Tokens.spacing.medium
IconImage {
asynchronous: true
implicitSize: Math.round(Tokens.font.size.large * 1.8)
implicitSize: Math.round(Tokens.font.icon.large.pointSize * 1.8)
source: Quickshell.iconPath(appItem.modelData.icon, "image-missing")
}
@@ -67,7 +67,7 @@ PageBase {
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
font: Tokens.font.body.small
text: appItem.modelData.name
}
@@ -75,7 +75,7 @@ PageBase {
Layout.fillWidth: true
color: Colors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
font: Tokens.font.label.small
text: (appItem.modelData.comment || appItem.modelData.genericName) ?? ""
visible: text
}
@@ -84,14 +84,14 @@ PageBase {
MaterialIcon {
color: Colors.palette.m3primary
fill: 1
font.pointSize: Tokens.font.size.small
fontStyle: Tokens.font.icon.small
text: "favorite"
visible: Strings.testRegexList(Config.launcher.favoriteApps, appItem.modelData.id)
}
MaterialIcon {
color: Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.normal
fontStyle: Tokens.font.icon.medium
text: "chevron_right"
}
}
+4 -5
View File
@@ -47,7 +47,7 @@ PageBase {
IconImage {
asynchronous: true
implicitSize: Math.round(Tokens.font.size.large * 3)
implicitSize: Math.round(Tokens.font.icon.large.pointSize * 3)
source: Quickshell.iconPath(root.app?.icon, "image-missing")
}
@@ -57,7 +57,7 @@ PageBase {
CustomText {
Layout.fillWidth: true
font.pointSize: Tokens.font.size.normal
font: Tokens.font.body.medium
text: root.app?.name ?? ""
wrapMode: Text.WordWrap
}
@@ -65,7 +65,7 @@ PageBase {
CustomText {
Layout.fillWidth: true
color: Colors.palette.m3outline
font.pointSize: Tokens.font.size.small
font: Tokens.font.body.small
text: (root.app?.comment || root.app?.genericName) ?? ""
visible: text
wrapMode: Text.WordWrap
@@ -152,7 +152,7 @@ PageBase {
id: label
Layout.alignment: Qt.AlignTop
font.pointSize: Tokens.font.size.small
font: Tokens.font.label.small
}
Item {
@@ -165,7 +165,6 @@ PageBase {
Layout.fillWidth: true
Layout.maximumWidth: implicitWidth + 1 // Whyyyyyyyyy
color: Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.small
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
}
}
+5 -5
View File
@@ -76,7 +76,7 @@ PageBase {
icon: "apps"
last: true
settingAnchor: "apps-all-apps"
status: qsTr("Browse installed apps, set favorites and hidden")
subtext: qsTr("Browse installed apps, set favorites and hidden")
text: qsTr("All apps")
onClicked: root.sState.openSubPage(1)
@@ -143,7 +143,7 @@ PageBase {
IconImage {
asynchronous: true
implicitSize: Math.round(Tokens.font.size.large * 1.8)
implicitSize: Math.round(Tokens.font.icon.large.pointSize * 1.8)
source: Quickshell.iconPath(appItem.modelData.icon, "image-missing")
}
@@ -154,7 +154,7 @@ PageBase {
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
font: Tokens.font.body.small
text: appItem.modelData.name
}
@@ -162,7 +162,7 @@ PageBase {
Layout.fillWidth: true
color: Colors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
font: Tokens.font.body.small
text: (appItem.modelData.comment || appItem.modelData.genericName) ?? ""
visible: text
}
@@ -171,7 +171,7 @@ PageBase {
MaterialIcon {
color: Colors.palette.m3primary
fill: 1
font.pointSize: Tokens.font.size.small
fontStyle: Tokens.font.icon.small
text: "favorite"
visible: Strings.testRegexList(Config.launcher.favoriteApps, appItem.modelData.id)
}
@@ -28,7 +28,6 @@ PageBase {
Layout.fillWidth: true
Layout.leftMargin: Tokens.padding.extraSmall
color: Colors.palette.m3outline
font.pointSize: Tokens.font.size.small
text: qsTr("Adjust the volume of individual apps currently playing audio.")
wrapMode: Text.WordWrap
}
+4 -4
View File
@@ -103,7 +103,7 @@ PageBase {
spacing: Tokens.spacing.small
MaterialIcon {
font.pointSize: Tokens.font.size.normal
fontStyle: Tokens.font.icon.medium
text: "tune"
}
@@ -114,7 +114,7 @@ PageBase {
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
font: Tokens.font.body.small
text: qsTr("App volumes")
}
@@ -123,14 +123,14 @@ PageBase {
animate: true
color: Colors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
font: Tokens.font.body.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: Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.normal
fontStyle: Tokens.font.icon.medium
text: "chevron_right"
}
}
@@ -57,6 +57,7 @@ PageBase {
enabled: Object.keys(model).length > 0
header: qsTr("Add new entry")
icon: "add"
last: true
label: qsTr("Add entry")
model: {
const present = new Set(Config.bar.tray.statusIcons.values.map(item => item.id));
@@ -66,6 +67,7 @@ PageBase {
}));
}
rootParent: root.flickable
settingAnchor: "panels-bar-status-icons-add-item"
onAccepted: {
if (!selectedItem)
+3 -3
View File
@@ -91,7 +91,7 @@ PageBase {
first: true
icon: "widgets"
settingAnchor: "bar-tray"
status: qsTr("System tray icons")
subtext: qsTr("System tray icons")
text: qsTr("Tray")
onClicked: root.sState.openSubPage(6)
@@ -100,7 +100,7 @@ PageBase {
NavRow {
icon: "signal_cellular_alt"
settingAnchor: "bar-statusicons"
status: qsTr("Visible indicators")
subtext: qsTr("Visible indicators")
text: qsTr("Status icons")
onClicked: root.sState.openSubPage(7)
@@ -110,7 +110,7 @@ PageBase {
icon: "schedule"
last: true
settingAnchor: "bar-clock"
status: qsTr("Date, icon, background")
subtext: qsTr("Date, icon, background")
text: qsTr("Clock")
onClicked: root.sState.openSubPage(8)
@@ -33,5 +33,73 @@ PageBase {
onToggled: Config.dashboard.enabled = checked
}
// Tabs
SectionHeader {
text: qsTr("Tabs")
}
ToggleRow {
first: true
checked: Config.dashboard.showDashboard
settingAnchor: "dashboard-show-dashboard"
text: qsTr("Dashboard")
onToggled: Config.dashboard.showDashboard = checked
}
ToggleRow {
checked: Config.dashboard.showWeather
settingAnchor: "dashboard-show-weather"
text: qsTr("Weather")
onToggled: Config.dashboard.showWeather = checked
}
ToggleRow {
checked: Config.dashboard.showMedia
settingAnchor: "dashboard-show-media"
text: qsTr("Media")
onToggled: Config.dashboard.showMedia = checked
}
ToggleRow {
checked: Config.dashboard.showProcesses
settingAnchor: "dashboard-show-processes"
text: qsTr("Processes")
last: true
onToggled: Config.dashboard.showProcesses = checked
}
// Behavior
SectionHeader {
text: qsTr("Behavior")
}
ToggleRow {
checked: Config.dashboard.showOnHover
first: true
settingAnchor: "dashboard-showonhover"
text: qsTr("Show on hover")
subtext: qsTr("Dashboard %1 be shown when hovering the middle-top part of the shell").arg(checked ? "will" : "will not")
onToggled: Config.dashboard.showOnHover = checked
}
SpinRow {
from: 30
settingAnchor: "dashboard-drag-threshold"
stepSize: 20
enabled: !Config.dashboard.showOnHover
subtext: qsTr("How far you need to drag before the panel opens")
text: qsTr("Drag threshold")
to: 100
value: Config.dashboard.dragThreshold
last: true
onMoved: v => Config.dashboard.dragThreshold = v
}
}
}
@@ -0,0 +1,112 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import qs.Components
import qs.Modules.Settings.Common
PageBase {
id: root
readonly property var schemes: [
{
id: "oneDark",
label: qsTr("One Dark")
},
{
id: "nord",
label: qsTr("Nord")
},
{
id: "dracula",
label: qsTr("Dracula")
},
{
id: "githubDark",
label: qsTr("Github Dark")
},
{
id: "solarizedDark",
label: qsTr("Solarized Dark")
},
{
id: "monokai",
label: qsTr("Monokai")
},
{
id: "gruvboxDark",
label: qsTr("Gruvbox Dark")
},
{
id: "catppuccinMocha",
label: qsTr("Catppuccin Mocha")
},
{
id: "tokyoNight",
label: qsTr("Tokyo Night")
},
{
id: "ayuDark",
label: qsTr("Ayu Dark")
},
{
id: "palenight",
label: qsTr("Pale Night")
}
]
isSubPage: true
title: qsTr("Sidebar")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Tokens.spacing.extraSmall / 2
width: root.cappedWidth
SectionHeader {
first: true
text: qsTr("General")
}
ToggleRow {
settingAnchor: "panels-sidebar-llm-enabled"
checked: Config.llm.enabled
text: qsTr("Enabled")
first: true
last: true
onToggled: Config.llm.enabled = checked
}
SectionHeader {
text: qsTr("Appearance")
}
DialogSelectButton {
id: selectScheme
acceptLabel: qsTr("Confirm")
settingAnchor: "panels-sidebar-llm-scheme"
enabled: Object.keys(model).length > 0
header: qsTr("Select scheme")
first: true
last: true
icon: "add"
label: qsTr("Color scheme")
subtext: qsTr("Select the color scheme used for code blocks")
model: root.schemes
rootParent: root.flickable
initialSelect: Config.llm.appearance.scheme
onAccepted: {
if (!selectedItem)
return;
Config.llm.appearance.scheme = selectedItem;
}
}
}
}
@@ -88,7 +88,12 @@ PageBase {
enabled: Object.keys(model).length > 0
header: qsTr("Add new entry")
icon: "add"
onOpenChanged: {
root.sState.dimmed = open;
}
settingAnchor: "panels-sidebar-quick-toggles-add-item"
label: qsTr("Add entry")
last: true
model: {
const present = new Set(Config.utilities.quickToggles.values.map(item => item.id));
return Object.keys(root.builtinIcons).filter(id => !present.has(id)).map(id => ({
@@ -108,5 +113,19 @@ PageBase {
});
}
}
SectionHeader {
text: qsTr("AI chat")
}
NavRow {
text: qsTr("AI chat")
settingAnchor: "panels-sidebar-llm"
icon: "robot_2"
first: true
last: true
onClicked: root.sState.openSubPage(9)
}
}
}
+5 -5
View File
@@ -17,7 +17,7 @@ PageBase {
first: true
icon: "dock_to_bottom"
settingAnchor: "panels-bar"
status: !Config.bar.autoHide ? qsTr("Always visible") : qsTr("Reveal on hover")
subtext: !Config.bar.autoHide ? qsTr("Always visible") : qsTr("Reveal on hover")
text: qsTr("Bar")
onClicked: root.sState.openSubPage(1)
@@ -26,7 +26,7 @@ PageBase {
NavRow {
icon: "dashboard"
settingAnchor: "panels-dashboard"
status: Config.dashboard.enabled ? qsTr("Enabled") : qsTr("Disabled")
subtext: Config.dashboard.enabled ? qsTr("Enabled") : qsTr("Disabled")
text: qsTr("Dashboard")
onClicked: root.sState.openSubPage(2)
@@ -35,7 +35,7 @@ PageBase {
NavRow {
icon: "insert_chart"
settingAnchor: "panels-resources"
status: Config.dashboard.performance.enabled ? qsTr("Enabled") : qsTr("Disabled")
subtext: Config.dashboard.performance.enabled ? qsTr("Enabled") : qsTr("Disabled")
text: qsTr("Resources")
onClicked: root.sState.openSubPage(3)
@@ -44,7 +44,7 @@ PageBase {
NavRow {
icon: "apps"
settingAnchor: "panels-launcher"
status: Config.launcher.enabled ? qsTr("Enabled") : qsTr("Disabled")
subtext: Config.launcher.enabled ? qsTr("Enabled") : qsTr("Disabled")
text: qsTr("Launcher")
onClicked: root.sState.openSubPage(4)
@@ -54,7 +54,7 @@ PageBase {
icon: "dock_to_right"
last: true
settingAnchor: "panels-sidebar"
status: Config.sidebar.enabled ? qsTr("Enabled") : qsTr("Disabled")
subtext: Config.sidebar.enabled ? qsTr("Enabled") : qsTr("Disabled")
text: qsTr("Sidebar")
onClicked: root.sState.openSubPage(5)
+5 -17
View File
@@ -25,18 +25,6 @@ PageBase {
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")
@@ -72,7 +60,7 @@ PageBase {
icon: "notifications"
last: true
settingAnchor: "services-notifications"
status: qsTr("Notifications, toasts, timeouts")
subtext: qsTr("Notifications, toasts, timeouts")
text: qsTr("Notifications")
onClicked: root.sState.openSubPage(1)
@@ -202,16 +190,16 @@ PageBase {
}
ToggleRow {
checked: Config.general.color.smart
checked: Config.colors.smart
settingAnchor: "services-smart-color-scheme"
subtext: qsTr("Derive theme mode from the wallpaper")
text: qsTr("Smart color scheme")
onToggled: Config.general.color.smart = checked
onToggled: Config.colors.smart = checked
}
SelectRow {
active: root.gpuItems[root.gpuKeyToIndex(Config.services.gpuType)]
active: root.gpuItems[Config.services.gpuType]
last: true
menuItems: root.gpuItems
menuOnTop: true
@@ -219,7 +207,7 @@ PageBase {
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)]
onSelected: item => Config.services.gpuType = root.gpuItems.indexOf(item)
}
}
}
@@ -0,0 +1,327 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Io
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import qs.Paths
import qs.Services
import qs.Modules.Settings.Common
PageBase {
id: root
readonly property list<string> fonts: Qt.fontFamilies()
readonly property var schemes: [
{
id: "onedark",
label: qsTr("One Dark")
},
{
id: "nord",
label: qsTr("Nord")
},
{
id: "dracula",
label: qsTr("Dracula")
},
{
id: "darkgreen",
label: qsTr("Darkgreen")
},
{
id: "solarized",
label: qsTr("Solarized")
},
{
id: "rosepine",
label: qsTr("Rosepine")
},
{
id: "gruvbox",
label: qsTr("Gruvbox")
},
{
id: "catppuccin",
label: qsTr("Catppuccin")
},
{
id: "tokyonight",
label: qsTr("Tokyo Night")
},
{
id: "shadotheme",
label: qsTr("Shadotheme")
}
]
isSubPage: true
title: qsTr("Colors & font")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Tokens.spacing.extraSmall / 2
width: root.cappedWidth
SectionHeader {
first: true
text: qsTr("Colors")
}
ToggleRow {
text: qsTr("Dynamic colors")
first: true
subtext: qsTr("Automatic color scheme based on wallpaper is %1").arg(Config.colors.schemeGen ? "enabled" : "disabled")
checked: Config.colors.schemeGen
settingAnchor: "colors-fonts-dynamic-colors"
onToggled: {
Config.colors.schemeGen = checked;
if (checked)
Colors.setMode(Config.colors.mode);
}
}
DialogSelectButton {
settingAnchor: "colors-fonts-preset-scheme"
model: root.schemes
acceptLabel: qsTr("Confirm")
enabled: !Config.colors.schemeGen
header: qsTr("Select scheme")
rowButton.activeItem: root.schemes.find(s => {
var regex = new RegExp(Config.colors.presets.name, "i");
return regex.test(s.id);
}).label ?? ""
label: qsTr("Preset scheme")
subtext: qsTr("Select preset color scheme")
rootParent: root.flickable
FileView {
id: view
path: `${Paths.state}/presets.json`
watchChanges: true
onFileChanged: reload()
JsonAdapter {
id: adapter
property var presets
}
}
initialSelect: Config.colors.presets.name.toLowerCase()
onAccepted: {
if (!selectedItem)
return;
const s = selectedItem.toLowerCase();
let v = "";
let a = "";
const existingVariant = Config.colors.presets.variant;
const existingAccent = Config.colors.presets.accent;
const variantKeys = Object.keys(adapter.presets[s].variants);
if (existingVariant !== "" && variantKeys.some(v => v === existingVariant))
v = existingVariant;
if (v !== "" && existingAccent !== "" && adapter.presets[s].variants[v]?.accents.some(a => a === existingAccent))
a = existingAccent;
if (a !== "")
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${s}:${v}`, "-a", `${a}`]);
else if (v !== "")
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${s}:${v}`]);
else
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${s}:${variantKeys[0]}`]);
}
}
DialogSelectButton {
settingAnchor: "colors-fonts-preset-variant"
// qmlformat off
model: {
const v = adapter.presets[Config.colors.presets.name].variants;
const map = Object.keys(v).map(v => ({
id: v,
label: qsTr("%1%2")
.arg(v.charAt(0).toUpperCase())
.arg(v.slice(1).toLowerCase())
}));
return map;
}
// qmlformat on
acceptLabel: qsTr("Confirm")
enabled: !Config.colors.schemeGen && model.length > 1
header: qsTr("Select variant")
rowButton.activeItem: {
const v = Config.colors.presets.variant ?? "";
if (v === "")
return "";
return qsTr("%1%2").arg(v.charAt(0).toUpperCase()).arg(v.slice(1).toLowerCase());
}
label: qsTr("Variant")
subtext: qsTr("Select preset variant")
rootParent: root.flickable
initialSelect: Config.colors.presets.variant
onAccepted: {
if (!selectedItem)
return;
const v = selectedItem.toLowerCase();
const s = Config.colors.presets.name;
const existingAccent = Config.colors.presets.accent;
let a = "";
if (existingAccent !== "" && adapter.presets[s].variants[v]?.accents.some(a => a === existingAccent))
a = existingAccent;
if (a !== "")
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${s}:${v}`, "-a", `${a}`]);
else
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${s}:${v}`]);
}
}
DialogSelectButton {
settingAnchor: "colors-fonts-preset-accent"
// qmlformat off
model: {
const a = adapter.presets[Config.colors.presets.name].variants[Config.colors.presets.variant]?.accents ?? [];
if (a.length === 0)
return a;
const map = a.map(a => ({
id: a,
label: qsTr("%1%2")
.arg(a.charAt(0).toUpperCase())
.arg(a.slice(1).toLowerCase())
}));
return map;
}
// qmlformat on
acceptLabel: qsTr("Confirm")
enabled: !Config.colors.schemeGen && model.length > 1
header: qsTr("Select accent")
rowButton.activeItem: {
const a = Config.colors.presets.accent ?? "";
if (a === "")
return "";
return qsTr("%1%2").arg(a.charAt(0).toUpperCase()).arg(a.slice(1).toLowerCase());
}
label: qsTr("Accent")
last: true
subtext: qsTr("Select preset accent")
rootParent: root.flickable
initialSelect: Config.colors.presets.accent
onAccepted: {
if (!selectedItem)
return;
const a = selectedItem.toLowerCase();
const v = Config.colors.presets.variant;
const s = Config.colors.presets.name;
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${s}:${v}`, "-a", `${a}`]);
}
}
SectionHeader {
text: qsTr("Fonts")
}
SearchPopup {
id: selectSansFont
settingAnchor: "colors-fonts-sans-font"
model: root.fonts
acceptLabel: qsTr("Confirm")
enabled: Object.keys(model).length > 0
separateContent: false
header: qsTr("Select font")
first: true
rowButton.activeItem: root.fonts.find(s => {
var regex = new RegExp(Config.appearance.font.family.sans, "i");
return regex.test(s);
}) ?? ""
label: qsTr("Sans font")
subtext: qsTr("Select desired sans font")
rootParent: root.flickable
initialSelect: Config.appearance.font.family.sans
onAccepted: {
if (!selectedItem)
return;
Config.appearance.font.family.sans = selectedItem;
}
}
SearchPopup {
id: selectMonoFont
acceptLabel: qsTr("Confirm")
enabled: Object.keys(model).length > 0
settingAnchor: "colors-fonts-mono-font"
separateContent: false
header: qsTr("Select font")
rowButton.activeItem: root.fonts.find(s => {
var regex = new RegExp(Config.appearance.font.family.mono, "i");
return regex.test(s);
}) ?? ""
label: qsTr("Mono font")
subtext: qsTr("Select desired mono font")
model: root.fonts
rootParent: root.flickable
initialSelect: Config.appearance.font.family.mono
onAccepted: {
if (!selectedItem)
return;
Config.appearance.font.family.mono = selectedItem;
}
}
SearchPopup {
id: selectClockFont
acceptLabel: qsTr("Confirm")
enabled: Object.keys(model).length > 0
separateContent: false
settingAnchor: "colors-fonts-clock-font"
header: qsTr("Select font")
last: true
rowButton.activeItem: root.fonts.find(s => {
var regex = new RegExp(Config.appearance.font.family.clock, "i");
return regex.test(s);
}) ?? ""
label: qsTr("Clock font")
subtext: qsTr("Select desired clock font")
model: root.fonts
rootParent: root.flickable
initialSelect: Config.appearance.font.family.clock
onAccepted: {
if (!selectedItem)
return;
Config.appearance.font.family.clock = selectedItem;
}
}
}
}
@@ -25,7 +25,7 @@ PageBase {
CustomText {
Layout.topMargin: Tokens.spacing.largeIncreased
font.pointSize: Tokens.font.size.large
font: Tokens.font.title.small
text: qsTr("Wallpapers")
}
+107 -59
View File
@@ -33,18 +33,35 @@ PageBase {
}
}
IconTextButton {
ButtonRow {
Layout.alignment: Qt.AlignHCenter
enabled: Config.background.enabled
horizontalPadding: Tokens.padding.extraLarge
icon: "wallpaper"
isRound: true
shapeMorph: true
text: qsTr("Wallpapers")
type: IconTextButton.Tonal
verticalPadding: Tokens.padding.small
spacing: Tokens.spacing.small
onClicked: root.sState.openSubPage(1) // Wallpaper page
IconTextButton {
enabled: Config.background.enabled
horizontalPadding: Tokens.padding.extraLarge
icon: "wallpaper"
isRound: true
shapeMorph: true
text: qsTr("Wallpapers")
type: IconTextButton.Tonal
verticalPadding: Tokens.padding.small
onClicked: root.sState.openSubPage(1) // Wallpaper page
}
IconTextButton {
enabled: Config.background.enabled
horizontalPadding: Tokens.padding.extraLarge
icon: "palette"
isRound: true
shapeMorph: true
text: qsTr("Colors & font")
type: IconTextButton.Tonal
verticalPadding: Tokens.padding.small
onClicked: root.sState.openSubPage(2) // Colors & font page
}
}
ToggleRow {
@@ -76,85 +93,116 @@ PageBase {
onToggled: Colors.setMode(checked ? "dark" : "light")
}
OverlayRow {
id: darkMode
TimeDialogSelect {
readonly property string endTime: {
var d = new Date(0, 0, 0, 0, 0, 0, 0);
d.setMinutes(Config.general.color.scheduleDarkEnd);
d.setMinutes(Config.colors.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);
d.setMinutes(Config.colors.scheduleDarkStart);
return Qt.formatTime(d, "hh:mm AP");
}
checked: Config.general.color.scheduleDark
rowButton.checked: Config.colors.scheduleDark
rowButton.onToggled: Config.colors.scheduleDark = rowButton.checked
start: Config.colors.scheduleDarkStart
end: Config.colors.scheduleDarkEnd
first: true
last: true
settingAnchor: "style-schedule-dark-mode"
acceptLabel: qsTr("Apply")
rootParent: root.flickable
header: qsTr("Select time")
subtext: qsTr("Dark mode will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime)
text: qsTr("Schedule dark mode")
label: 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;
ModeScheduler.checkStartup();
PopupManager.requestClose();
}
onClose: PopupManager.requestClose()
}
onOpenChanged: {
root.sState.dimmed = open;
}
onClicked: value => {
Config.general.color.scheduleDark = value;
onAccepted: {
Config.colors.scheduleDarkStart = startCurrent;
Config.colors.scheduleDarkEnd = endCurrent;
ModeScheduler.checkStartup();
}
}
OverlayRow {
id: hyprsunset
TimeDialogSelect {
readonly property string endTime: {
var d = new Date(0, 0, 0, 0, 0, 0, 0);
d.setMinutes(Config.general.color.scheduleHyprsunsetEnd);
d.setMinutes(Config.display.nightlight.scheduleEnd);
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);
d.setMinutes(Config.display.nightlight.scheduleStart);
return Qt.formatTime(d, "hh:mm AP");
}
rowButton.checked: Config.display.nightlight.schedule
rowButton.onToggled: Config.display.nightlight.schedule = rowButton.checked
start: Config.display.nightlight.scheduleStart
end: Config.display.nightlight.scheduleEnd
first: true
settingAnchor: "style-schedule-nightlight"
acceptLabel: qsTr("Apply")
rootParent: root.flickable
header: qsTr("Select time")
subtext: qsTr("Nightlight will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime)
label: qsTr("Schedule nightlight")
onOpenChanged: {
root.sState.dimmed = open;
}
onAccepted: {
Config.display.nightlight.scheduleStart = startCurrent;
Config.display.nightlight.scheduleEnd = endCurrent;
Nightlight.checkStartup();
}
}
ToggleRow {
Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing
checked: Config.general.color.scheduleHyprsunset
checked: Config.display.nightlight.useNative
settingAnchor: "display-nightlight-useNative"
text: qsTr("Use native")
subtext: qsTr("Use native shader nightlight for more features")
onToggled: Config.display.nightlight.useNative = checked
}
SpinRow {
Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing
from: 2000
settingAnchor: "display-nightlight-temp"
stepSize: 100
subtext: qsTr("How warm/orange the screen will be. Lower is warmer (Kelvin)")
text: qsTr("Temperature")
to: 6500
value: Config.display.nightlight.temp
onMoved: v => Config.display.nightlight.temp = v
}
SpinRow {
Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing
from: 0
settingAnchor: "display-nightlight-fade-duration"
enabled: Config.display.nightlight.useNative
last: true
settingAnchor: "style-schedule-hyprsunset"
subtext: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime)
text: qsTr("Schedule hyprsunset")
stepSize: 1
subtext: qsTr("How long it takes for the gradual nightlight to finish (s)")
text: qsTr("Fade duration")
to: 60 * 20
value: Config.display.nightlight.fadeDuration / 1000
popup: Component {
TimeInput {
object: Config.general.color
settings: ["scheduleHyprsunset", "scheduleHyprsunsetStart", "scheduleHyprsunsetEnd"]
onApplySettings: (start, end) => {
Config.general.color.scheduleHyprsunsetStart = start;
Config.general.color.scheduleHyprsunsetEnd = end;
Hyprsunset.checkStartup();
PopupManager.requestClose();
}
onClose: PopupManager.requestClose()
}
}
onClicked: value => {
Config.general.color.scheduleHyprsunset = value;
}
onMoved: v => Config.display.nightlight.fadeDuration = v * 1000
}
}
}
-20
View File
@@ -1,20 +0,0 @@
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;
}
}
-47
View File
@@ -1,47 +0,0 @@
import QtQuick
import ZShell.Config
import qs.Components
import qs.Services
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(Colors.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
}
}
+84 -59
View File
@@ -1,66 +1,24 @@
pragma Singleton
import "../../scripts/fzf.js" as Fzf
import "../../scripts/settings-indexer.js" as SettingsIndexer
import QtQuick
import Quickshell
import ZShell
import ZShell.Config
import qs.Paths
Singleton {
id: root
property var fzfFinder: null
property var inverted: ({})
property var ranking: ({})
readonly property var highlightCache: ({
"search": "",
"pattern": null
})
property var inverted: ({})
property var ranking: ({})
function highlight(text: string, search: string, colour: color): string {
const escaped = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
if (search.length === 0)
return escaped;
const cache = root.highlightCache;
if (search !== cache.search) {
const tokens = tokenize(search);
cache.search = search;
if (tokens.length === 0)
cache.pattern = null;
else {
const escapedTokens = tokens.map(t => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
cache.pattern = new RegExp("\\b(" + escapedTokens.join("|") + ")", "gi");
}
}
const pattern = cache.pattern;
if (!pattern)
return escaped;
pattern.lastIndex = 0;
if (!pattern.test(escaped))
return escaped;
pattern.lastIndex = 0;
return escaped.replace(pattern, `<font color="${colour}">$1</font>`);
}
function lookup(token: string): var {
const result = ({});
const exact = root.inverted[token] !== undefined;
const keys = exact ? [token] : Object.keys(root.inverted).filter(k => k.startsWith(token));
for (const key of keys) {
const rank = root.ranking[key] ?? ({});
for (const id of root.inverted[key]) {
const w = rank[id] ?? 0.1;
if (result[id] === undefined || w > result[id])
result[id] = w;
}
}
return result;
}
property var fzfFinder: null
readonly property string cachePath: Paths.cache + "/settings-index.json"
function query(search: string): list<QtObject> {
const tokens = root.tokenize(search);
@@ -70,7 +28,7 @@ Singleton {
const scores = ({});
const hitCounts = ({});
for (const token of tokens) {
const matches = root.lookup(token);
const matches = root.lookup(token); // { id: weight }
for (const id in matches) {
scores[id] = (scores[id] ?? 0) + matches[id];
hitCounts[id] = (hitCounts[id] ?? 0) + 1;
@@ -103,13 +61,76 @@ Singleton {
return out;
}
function lookup(token: string): var {
const result = ({});
const exact = root.inverted[token] !== undefined;
const keys = exact ? [token] : Object.keys(root.inverted).filter(k => k.startsWith(token));
for (const key of keys) {
const rank = root.ranking[key] ?? ({});
for (const id of root.inverted[key]) {
const w = rank[id] ?? 0.1;
if (result[id] === undefined || w > result[id])
result[id] = w;
}
}
return result;
}
function tokenize(text: string): var {
return text.toLowerCase().split(/[^a-z0-9]+/).filter(t => t.length > 0);
}
function highlight(text: string, search: string, colour: color): string {
const escaped = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
if (search.length === 0)
return escaped;
const cache = root.highlightCache;
if (search !== cache.search) {
const tokens = root.tokenize(search);
cache.search = search;
if (tokens.length === 0) {
cache.pattern = null;
} else {
const escapedTokens = tokens.map(t => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
cache.pattern = new RegExp("\\b(" + escapedTokens.join("|") + ")", "gi");
}
}
const pattern = cache.pattern;
if (!pattern)
return escaped;
pattern.lastIndex = 0;
if (!pattern.test(escaped))
return escaped;
pattern.lastIndex = 0;
return escaped.replace(pattern, `<font color="${colour}">$1</font>`);
}
function loadIndex(): var {
const revision = ZUtils.gitRevision();
const cached = ZUtils.readTextFile(cachePath);
if (cached) {
try {
const parsed = JSON.parse(cached);
if (parsed.version === 3 && revision && parsed.revision === revision && parsed.locale === Qt.locale().name)
return parsed;
} catch (e) {}
}
const data = SettingsIndexer.buildIndex(`${Quickshell.shellDir}/Modules/Settings`, p => ZUtils.readTextFile(p), (d, s) => ZUtils.listFiles(d, s), (ctx, text) => qsTranslate(ctx, text));
data.revision = revision;
data.locale = Qt.locale().name;
ZUtils.writeTextFile(cachePath, JSON.stringify(data));
console.log(`SettingsSearcher: indexed ${data.entries.length} settings (revision ${revision || "unknown"})`);
return data;
}
Component.onCompleted: {
try {
const data = JSON.parse(ZUtils.settingsIndex());
const data = root.loadIndex();
entries.model = data.entries;
root.inverted = data.inverted ?? {};
root.ranking = data.ranking ?? {};
@@ -122,6 +143,7 @@ Singleton {
limit: 25
});
} catch (e) {
console.warn("SettingsSearcher: failed to build settings index:", e);
entries.model = [];
root.inverted = {};
root.ranking = {};
@@ -132,22 +154,25 @@ Singleton {
Variants {
id: entries
SettingEntry {
}
SettingEntry {}
}
component SettingEntry: QtObject {
readonly property string anchor: modelData.anchor ?? ""
required property var modelData
readonly property int pageIdx: modelData.pageIdx
readonly property var subPath: modelData.subPath
readonly property var targetSubPath: modelData.targetSubPath ?? []
readonly property var crumbIcons: modelData.crumbIcons
readonly property var crumbLabels: modelData.crumbLabels
readonly property bool isToggle: togglePath.length > 0
required property var modelData
readonly property int pageIdx: modelData.pageIdx
readonly property string section: modelData.section ?? ""
readonly property var subPath: modelData.subPath
readonly property string subtext: modelData.subtext ?? ""
readonly property string title: modelData.title
readonly property string section: modelData.section ?? ""
readonly property string subtext: modelData.subtext ?? ""
readonly property string anchor: modelData.anchor ?? ""
readonly property string icon: modelData.icon ?? ""
readonly property string togglePath: modelData.togglePath ?? ""
readonly property bool isToggle: togglePath.length > 0
readonly property bool toggleValue: {
if (!isToggle)
return false;
+1
View File
@@ -16,6 +16,7 @@ QtObject {
property string searchText
property DesktopEntry selectedApp
property BluetoothDevice selectedBtDevice
property bool dimmed
property string selectedWallpaperCategory
property list<int> subPageIdxStack
+2 -2
View File
@@ -12,8 +12,8 @@ Item {
required property var panels
property string queuedMode: "wallpaper"
required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.settings
required property PersistentProperties visibilities
readonly property bool shouldBeActive: screenState.settings
required property ScreenState screenState
implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth