remove old Settings, rename new
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 11s
Python / lint-format (pull_request) Successful in 18s
Python / test (pull_request) Successful in 46s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m39s
C++ / build (pull_request) Successful in 2m26s

This commit is contained in:
2026-06-30 13:43:34 +02:00
parent deaeac4dbd
commit 38b9362515
93 changed files with 88 additions and 7943 deletions
-370
View File
@@ -1,370 +0,0 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Widgets
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Modules as Modules
import qs.Config
import qs.Helpers
Item {
id: root
required property Item content
signal settingSelected(string category, string section, string settingName)
// Function to select category by key
function selectCategory(categoryKey: string) {
for (let i = 0; i < appearanceCats.count; i++) {
const item = appearanceCats.get(i);
if (item.key === categoryKey) {
appearanceView.currentIndex = i;
root.content.setCategory(item.key, item._index);
return;
}
}
for (let i = 0; i < systemCats.count; i++) {
const item = systemCats.get(i);
if (item.key === categoryKey) {
sysView.currentIndex = i;
root.content.setCategory(item.key, item._index);
return;
}
}
for (let i = 0; i < panelCats.count; i++) {
const item = panelCats.get(i);
if (item.key === categoryKey) {
panelsView.currentIndex = i;
root.content.setCategory(item.key, item._index);
return;
}
}
}
implicitHeight: searchBar.implicitHeight + Appearance.spacing.smaller + clayout.contentHeight + clayout.anchors.margins * 2
implicitWidth: clayout.contentWidth + clayout.anchors.margins * 2
ListModel {
id: systemCats
ListElement {
_index: 0
icon: "settings"
key: "general"
name: "General"
}
ListElement {
_index: 1
icon: "build_circle"
key: "services"
name: "Services"
}
ListElement {
_index: 2
icon: "notifications"
key: "notifications"
name: "Notifications"
}
ListElement {
_index: 3
icon: "handyman"
key: "utilities"
name: "Utilities"
}
ListElement {
_index: 4
icon: "cached"
key: "updates"
name: "Updates"
}
}
ListModel {
id: panelCats
ListElement {
_index: 5
icon: "settop_component"
key: "bar"
name: "Bar"
}
ListElement {
_index: 6
icon: "lock"
key: "lockscreen"
name: "Lockscreen"
}
ListElement {
_index: 7
icon: "view_sidebar"
key: "sidebar"
name: "Sidebar"
}
ListElement {
_index: 8
icon: "dashboard"
key: "dashboard"
name: "Dashboard"
}
ListElement {
_index: 9
icon: "display_settings"
key: "osd"
name: "On screen display"
}
ListElement {
_index: 10
icon: "rocket_launch"
key: "launcher"
name: "Launcher"
}
}
ListModel {
id: appearanceCats
ListElement {
_index: 11
icon: "wallpaper"
key: "wallpaper"
name: "Wallpaper"
}
ListElement {
_index: 12
icon: "screenshot_region"
key: "screenshot"
name: "Screenshot"
}
ListElement {
_index: 13
icon: "colors"
key: "appearance"
name: "Appearance"
}
}
CustomClippingRect {
anchors.fill: parent
color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.normal
CustomFlickable {
id: clayout
anchors.fill: parent
anchors.margins: Appearance.padding.extraSmall
contentHeight: contentItem.childrenRect.height
contentWidth: contentItem.childrenRect.width
flickableDirection: Flickable.VerticalFlick
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.normal
CustomListView {
id: sysView
Layout.fillWidth: true
Layout.preferredHeight: contentItem.childrenRect.height
Layout.preferredWidth: contentItem.childrenRect.width
boundsBehavior: Flickable.StopAtBounds
highlightFollowsCurrentItem: false
interactive: false
model: systemCats
spacing: 2
delegate: Category {
view: sysView
}
// highlight: CustomRect {
// color: DynamicColors.palette.m3primary
// implicitHeight: sysView.currentItem?.implicitHeight ?? 0
// implicitWidth: sysView.width
// radius: Appearance.rounding.normal - Appearance.padding.smaller
// y: sysView.currentItem?.y ?? 0
//
// Behavior on y {
// Anim {
// duration: Appearance.anim.durations.small
// easing.bezierCurve: Appearance.anim.curves.expressiveEffects
// }
// }
// }
}
CustomListView {
id: panelsView
Layout.fillWidth: true
Layout.preferredHeight: contentItem.childrenRect.height
Layout.preferredWidth: contentItem.childrenRect.width
boundsBehavior: Flickable.StopAtBounds
contentWidth: contentItem.childrenRect.width
highlightFollowsCurrentItem: false
interactive: false
model: panelCats
spacing: 2
delegate: Category {
view: panelsView
}
// highlight: CustomRect {
// color: DynamicColors.palette.m3primary
// implicitHeight: panelsView.currentItem?.implicitHeight ?? 0
// implicitWidth: panelsView.width
// radius: Appearance.rounding.normal - Appearance.padding.smaller
// y: panelsView.currentItem?.y ?? 0
//
// Behavior on y {
// Anim {
// duration: Appearance.anim.durations.small
// easing.bezierCurve: Appearance.anim.curves.expressiveEffects
// }
// }
// }
}
CustomListView {
id: appearanceView
Layout.fillWidth: true
Layout.preferredHeight: contentItem.childrenRect.height
Layout.preferredWidth: contentItem.childrenRect.width
boundsBehavior: Flickable.StopAtBounds
contentWidth: contentItem.childrenRect.width
highlightFollowsCurrentItem: false
interactive: false
model: appearanceCats
spacing: 2
delegate: Category {
view: appearanceView
}
// highlight: CustomRect {
// color: DynamicColors.palette.m3primary
// implicitHeight: appearanceView.currentItem?.implicitHeight ?? 0
// implicitWidth: appearanceView.width
// radius: Appearance.rounding.normal - Appearance.padding.smaller
// y: appearanceView.currentItem?.y ?? 0
//
// Behavior on y {
// Anim {
// duration: Appearance.anim.durations.small
// easing.bezierCurve: Appearance.anim.curves.expressiveEffects
// }
// }
// }
}
}
}
}
component Category: CustomRect {
id: categoryItem
required property int _index
readonly property bool first: index === 0
required property string icon
required property int index
required property string key
readonly property bool last: index === view.model.count - 1
required property string name
readonly property bool selected: key === root.content.currentCategory
required property ListView view
bottomLeftRadius: layer.pressed ? Appearance.rounding.smallest : selected || last ? Appearance.rounding.normal - clayout.anchors.margins : Appearance.rounding.extraSmall
bottomRightRadius: layer.pressed ? Appearance.rounding.smallest : selected || last ? Appearance.rounding.normal - clayout.anchors.margins : Appearance.rounding.extraSmall
color: selected ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
implicitHeight: 50
implicitWidth: 256
topLeftRadius: layer.pressed ? Appearance.rounding.smallest : selected || first ? Appearance.rounding.normal - clayout.anchors.margins : Appearance.rounding.extraSmall
topRightRadius: layer.pressed ? Appearance.rounding.smallest : selected || first ? Appearance.rounding.normal - clayout.anchors.margins : Appearance.rounding.extraSmall
Behavior on bottomLeftRadius {
Anim {
type: Anim.FastEffects
}
}
Behavior on bottomRightRadius {
Anim {
type: Anim.FastEffects
}
}
Behavior on topLeftRadius {
Anim {
type: Anim.FastEffects
}
}
Behavior on topRightRadius {
Anim {
type: Anim.FastEffects
}
}
RowLayout {
id: layout
anchors.left: parent.left
anchors.margins: Appearance.padding.smaller
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
MaterialIcon {
id: icon
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillHeight: true
Layout.leftMargin: Appearance.padding.small
Layout.preferredWidth: icon.contentWidth
color: categoryItem.key === root.content.currentCategory ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
fill: categoryItem.key === root.content.currentCategory ? 1 : 0
font.pointSize: Appearance.font.size.small * 2
text: categoryItem.icon
verticalAlignment: Text.AlignVCenter
Behavior on fill {
Anim {
}
}
}
CustomText {
id: text
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillHeight: true
Layout.fillWidth: true
Layout.leftMargin: Appearance.spacing.normal
color: categoryItem.key === root.content.currentCategory ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
text: categoryItem.name
verticalAlignment: Text.AlignVCenter
}
}
StateLayer {
id: layer
color: categoryItem.key === root.content.currentCategory ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
onClicked: {
root.content.setCategory(categoryItem.key, categoryItem._index);
categoryItem.view.currentIndex = categoryItem.index;
}
}
}
}
-162
View File
@@ -1,162 +0,0 @@
import QtQuick
import qs.Modules.Settings.Controls
import qs.Config
SettingsPage {
id: root
SettingsSection {
sectionId: "Scale"
SettingsHeader {
name: "Scale"
}
SettingSpinBox {
name: "Rounding scale"
object: Config.appearance.rounding
setting: "scale"
step: 0.1
}
Separator {
}
SettingSpinBox {
name: "Spacing scale"
object: Config.appearance.spacing
setting: "scale"
step: 0.1
}
Separator {
}
SettingSpinBox {
name: "Padding scale"
object: Config.appearance.padding
setting: "scale"
step: 0.1
}
Separator {
}
SettingSpinBox {
name: "Font size scale"
object: Config.appearance.font.size
setting: "scale"
step: 0.1
}
Separator {
}
SettingSpinBox {
name: "Animation duration scale"
object: Config.appearance.anim.durations
setting: "scale"
step: 0.1
}
Separator {
}
SettingSpinBox {
name: "Deform animation scale"
object: Config.appearance.deform
setting: "scale"
step: 0.1
}
}
SettingsSection {
sectionId: "Fonts"
SettingsHeader {
name: "Fonts"
}
SettingListView {
name: "Sans family"
object: Config.appearance.font.family
setting: "sans"
stringList: Qt.fontFamilies()
}
Separator {
}
SettingListView {
name: "Monospace family"
object: Config.appearance.font.family
setting: "mono"
stringList: Qt.fontFamilies()
}
}
SettingsSection {
sectionId: "Animation"
SettingsHeader {
name: "Animation"
}
SettingSpinBox {
name: "Media GIF speed adjustment"
object: Config.appearance.anim
setting: "mediaGifSpeedAdjustment"
step: 10
}
Separator {
}
SettingSpinBox {
max: 5
min: 0
name: "Session GIF speed"
object: Config.appearance.anim
setting: "sessionGifSpeed"
step: 0.1
}
}
SettingsSection {
sectionId: "Transparency"
SettingsHeader {
name: "Transparency"
}
SettingSwitch {
name: "Enable transparency"
object: Config.appearance.transparency
setting: "enabled"
}
Separator {
}
SettingSpinBox {
max: 1
min: 0
name: "Base opacity"
object: Config.appearance.transparency
setting: "base"
step: 0.05
}
Separator {
}
SettingSpinBox {
max: 1
min: 0
name: "Layer opacity"
object: Config.appearance.transparency
setting: "layers"
step: 0.05
}
}
}
@@ -1,48 +0,0 @@
import Quickshell
import QtQuick.Layouts
import qs.Modules.Settings.Controls
import qs.Config
SettingsPage {
id: root
required property ShellScreen screen
SettingsSection {
sectionId: "Wallpaper"
SettingsHeader {
name: "Wallpaper"
}
SettingSwitch {
name: "Enable wallpaper rendering"
object: Config.background
setting: "enabled"
}
Separator {
}
SettingSpinBox {
min: 0
name: "Fade duration"
object: Config.background
setting: "wallFadeDuration"
step: 50
}
Separator {
}
WallpaperCropper {
}
}
SettingsSection {
sectionId: "Wallpapers"
WallpaperGrid {
}
}
}
-217
View File
@@ -1,217 +0,0 @@
import qs.Modules.Settings.Controls
import qs.Config
SettingsPage {
SettingsSection {
sectionId: "Bar"
SettingsHeader {
name: "Bar"
}
SettingSwitch {
name: "Auto hide"
object: Config.bar
setting: "autoHide"
}
Separator {
}
SettingSpinBox {
min: 1
name: "Height"
object: Config.bar
setting: "height"
}
Separator {
}
SettingSpinBox {
min: 0
name: "Rounding"
object: Config.bar
setting: "rounding"
}
Separator {
}
SettingSpinBox {
min: 0
name: "Border"
object: Config.bar
setting: "border"
}
Separator {
}
SettingSpinBox {
min: 0
name: "Smoothing"
object: Config.bar
setting: "smoothing"
}
}
SettingsSection {
sectionId: "Tray"
SettingsHeader {
name: "System tray"
}
SettingSpinBox {
min: 16
name: "Tray icon size"
object: Config.bar.tray
setting: "trayIconSize"
}
}
SettingsSection {
sectionId: "Popouts"
SettingsHeader {
name: "Popouts"
}
SettingSwitch {
name: "Tray"
object: Config.bar.popouts
setting: "tray"
}
Separator {
}
SettingSwitch {
name: "Audio"
object: Config.bar.popouts
setting: "audio"
}
Separator {
}
SettingSwitch {
name: "Active window"
object: Config.bar.popouts
setting: "activeWindow"
}
Separator {
}
SettingSwitch {
name: "Resources"
object: Config.bar.popouts
setting: "resources"
}
Separator {
}
SettingSwitch {
name: "Clock"
object: Config.bar.popouts
setting: "clock"
}
Separator {
}
SettingSwitch {
name: "Network"
object: Config.bar.popouts
setting: "network"
}
Separator {
}
SettingSwitch {
name: "Power"
object: Config.bar.popouts
setting: "upower"
}
}
SettingsSection {
sectionId: "Entries"
SettingsHeader {
name: "Entries"
}
SettingBarEntryList {
name: "Bar entries"
object: Config.bar
setting: "entries"
}
}
SettingsSection {
sectionId: "Dock"
SettingsHeader {
name: "Dock"
}
SettingSwitch {
name: "Enable dock"
object: Config.dock
setting: "enable"
}
Separator {
}
SettingSpinBox {
min: 1
name: "Dock height"
object: Config.dock
setting: "height"
}
Separator {
}
SettingSwitch {
name: "Hover to reveal"
object: Config.dock
setting: "hoverToReveal"
}
Separator {
}
SettingSwitch {
name: "Pin on startup"
object: Config.dock
setting: "pinnedOnStartup"
}
Separator {
}
SettingStringList {
addLabel: qsTr("Add pinned app")
name: "Pinned apps"
object: Config.dock
setting: "pinnedApps"
}
Separator {
}
SettingStringList {
addLabel: qsTr("Add ignored regex")
name: "Ignored app regexes"
object: Config.dock
setting: "ignoredAppRegexes"
}
}
}
-218
View File
@@ -1,218 +0,0 @@
import qs.Modules.Settings.Controls
import qs.Config
SettingsPage {
SettingsSection {
sectionId: "Dashboard"
SettingsHeader {
name: "Dashboard"
}
SettingSwitch {
name: "Enable dashboard"
object: Config.dashboard
setting: "enabled"
}
Separator {
}
SettingSpinBox {
min: 0
name: "Media update interval"
object: Config.dashboard
setting: "mediaUpdateInterval"
step: 50
}
Separator {
}
SettingSpinBox {
min: 0
name: "Resource update interval"
object: Config.dashboard
setting: "resourceUpdateInterval"
step: 50
}
Separator {
}
SettingSpinBox {
min: 0
name: "Drag threshold"
object: Config.dashboard
setting: "dragThreshold"
}
}
SettingsSection {
sectionId: "Performance"
SettingsHeader {
name: "Performance"
}
SettingSwitch {
name: "Show battery"
object: Config.dashboard.performance
setting: "showBattery"
}
Separator {
}
SettingSwitch {
name: "Show GPU"
object: Config.dashboard.performance
setting: "showGpu"
}
Separator {
}
SettingSwitch {
name: "Show CPU"
object: Config.dashboard.performance
setting: "showCpu"
}
Separator {
}
SettingSwitch {
name: "Show memory"
object: Config.dashboard.performance
setting: "showMemory"
}
Separator {
}
SettingSwitch {
name: "Show storage"
object: Config.dashboard.performance
setting: "showStorage"
}
Separator {
}
SettingSwitch {
name: "Show network"
object: Config.dashboard.performance
setting: "showNetwork"
}
}
// SettingsSection {
// sectionId: "Layout Sizes"
//
// SettingsHeader {
// name: "Layout Sizes"
// }
//
// SettingReadOnly {
// name: "Tab indicator height"
// value: String(Config.dashboard.sizes.tabIndicatorHeight)
// }
//
// Separator {
// }
//
// SettingReadOnly {
// name: "Tab indicator spacing"
// value: String(Config.dashboard.sizes.tabIndicatorSpacing)
// }
//
// Separator {
// }
//
// SettingReadOnly {
// name: "Info width"
// value: String(Config.dashboard.sizes.infoWidth)
// }
//
// Separator {
// }
//
// SettingReadOnly {
// name: "Info icon size"
// value: String(Config.dashboard.sizes.infoIconSize)
// }
//
// Separator {
// }
//
// SettingReadOnly {
// name: "Date time width"
// value: String(Config.dashboard.sizes.dateTimeWidth)
// }
//
// Separator {
// }
//
// SettingReadOnly {
// name: "Media width"
// value: String(Config.dashboard.sizes.mediaWidth)
// }
//
// Separator {
// }
//
// SettingReadOnly {
// name: "Media progress sweep"
// value: String(Config.dashboard.sizes.mediaProgressSweep)
// }
//
// Separator {
// }
//
// SettingReadOnly {
// name: "Media progress thickness"
// value: String(Config.dashboard.sizes.mediaProgressThickness)
// }
//
// Separator {
// }
//
// SettingReadOnly {
// name: "Resource progress thickness"
// value: String(Config.dashboard.sizes.resourceProgessThickness)
// }
//
// Separator {
// }
//
// SettingReadOnly {
// name: "Weather width"
// value: String(Config.dashboard.sizes.weatherWidth)
// }
//
// Separator {
// }
//
// SettingReadOnly {
// name: "Media cover art size"
// value: String(Config.dashboard.sizes.mediaCoverArtSize)
// }
//
// Separator {
// }
//
// SettingReadOnly {
// name: "Media visualiser size"
// value: String(Config.dashboard.sizes.mediaVisualiserSize)
// }
//
// Separator {
// }
//
// SettingReadOnly {
// name: "Resource size"
// value: String(Config.dashboard.sizes.resourceSize)
// }
// }
}
-308
View File
@@ -1,308 +0,0 @@
import Quickshell
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import qs.Modules.Settings.Controls
import qs.Config
import qs.Components
import qs.Helpers
SettingsPage {
id: root
function schemeTypeItem(items, value) {
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (item.value === value)
return item;
}
return items[0] ?? null;
}
SettingsSection {
sectionId: "General"
SettingsHeader {
name: "General"
}
SettingInput {
name: "Logo"
object: Config.general
setting: "logo"
}
Separator {
}
SettingInput {
name: "Wallpaper path"
object: Config.general
setting: "wallpaperPath"
}
Separator {
}
SettingSwitch {
name: "Desktop icons"
object: Config.general
setting: "desktopIcons"
}
Separator {
}
SettingInput {
name: "Date format"
object: Config.general
setting: "dateFormat"
}
}
SettingsSection {
sectionId: "Color"
z: 1
SettingsHeader {
name: "Color"
}
CustomSplitButtonRow {
active: Config.general.color.mode === "light" ? menuItems[0] : menuItems[1]
enabled: Config.general.color.schemeGeneration
label: qsTr("Scheme mode")
menuItems: [
MenuItem {
icon: "light_mode"
text: qsTr("Light")
value: "light"
},
MenuItem {
icon: "dark_mode"
text: qsTr("Dark")
value: "dark"
}
]
onSelected: item => {
Config.general.color.mode = item.value;
Config.save();
if (item.value === "light")
ModeScheduler.applyLightMode();
else if (item.value === "dark")
ModeScheduler.applyDarkMode();
}
}
Separator {
}
CustomSplitButtonRow {
id: schemeType
active: root.schemeTypeItem(menuItems, Config.colors.schemeType)
enabled: Config.general.color.schemeGeneration
label: qsTr("Scheme type")
menuItems: [
MenuItem {
icon: "palette"
text: qsTr("Vibrant")
value: "vibrant"
},
MenuItem {
icon: "gesture"
text: qsTr("Expressive")
value: "expressive"
},
MenuItem {
icon: "contrast"
text: qsTr("Monochrome")
value: "monochrome"
},
MenuItem {
icon: "tonality"
text: qsTr("Neutral")
value: "neutral"
},
MenuItem {
icon: "gradient"
text: qsTr("Tonal spot")
value: "tonal-spot"
},
MenuItem {
icon: "target"
text: qsTr("Fidelity")
value: "fidelity"
},
MenuItem {
icon: "article"
text: qsTr("Content")
value: "content"
},
MenuItem {
icon: "colors"
text: qsTr("Rainbow")
value: "rainbow"
},
MenuItem {
icon: "nutrition"
text: qsTr("Fruit salad")
value: "fruit-salad"
}
]
onSelected: item => {
Config.colors.schemeType = item.value;
Config.save();
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--scheme", item.value]);
}
}
Separator {
}
SettingSwitch {
name: "Automatic color scheme"
object: Config.general.color
setting: "schemeGeneration"
}
Separator {
shouldBeActive: Config.general.color.schemeGeneration ? 0 : 1
}
SchemesListView {
name: "Color scheme presets"
object: Config.colors.presets
setting: "name"
shouldBeActive: Config.general.color.schemeGeneration ? 0 : 1
stringList: FetchPresets.presetNames()
}
Separator {
shouldBeActive: Config.colors.presets.name !== "" && !Config.general.color.schemeGeneration
}
SchemesListView {
name: "Preset variant"
object: Config.colors.presets
setting: "variant"
shouldBeActive: Config.colors.presets.name !== "" && !Config.general.color.schemeGeneration
stringList: FetchPresets.variantNames(Config.colors.presets.name)
onOptionSet: item => {
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${Config.colors.presets.name.toLowerCase()}:${item}`]);
}
}
Separator {
shouldBeActive: Config.colors.presets.variant !== "" && FetchPresets.accents(Config.colors.presets.name, Config.colors.presets.variant).length > 0 && !Config.general.color.schemeGeneration
}
SchemesListView {
name: "Preset accent"
object: Config.colors.presets
setting: "accent"
shouldBeActive: Config.colors.presets.variant !== "" && FetchPresets.accents(Config.colors.presets.name, Config.colors.presets.variant).length > 0 && !Config.general.color.schemeGeneration
stringList: FetchPresets.accents(Config.colors.presets.name, Config.colors.presets.variant)
onOptionSet: item => {
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${Config.colors.presets.name.toLowerCase()}:${Config.colors.presets.variant}`, "--accent", `${item}`]);
}
}
Separator {
shouldBeActive: Config.general.color.schemeGeneration ? 1 : 0
}
SettingSwitch {
name: "Smart color scheme"
object: Config.general.color
setting: "smart"
shouldBeActive: Config.general.color.schemeGeneration ? 1 : 0
}
Separator {
shouldBeActive: Config.general.color.schemeGeneration ? 1 : 0
}
TimeInput {
name: "Schedule dark mode"
object: Config.general.color
settings: ["scheduleDark", "scheduleDarkStart", "scheduleDarkEnd"]
shouldBeActive: Config.general.color.schemeGeneration
}
Separator {
}
HyprTimeInput {
name: "Schedule Hyprsunset"
object: Config.general.color
settings: ["scheduleHyprsunset", "scheduleHyprsunsetStart", "scheduleHyprsunsetEnd", "hyprsunsetTemp"]
}
Separator {
}
SettingSpinBox {
max: 20000
min: 1000
name: "Hyprsunset temperature"
object: Config.general.color
setting: "hyprsunsetTemp"
step: 200
}
}
SettingsSection {
sectionId: "Default Apps"
SettingsHeader {
name: "Default Apps"
}
SettingStringList {
addLabel: qsTr("Add terminal command")
name: "Terminal"
object: Config.general.apps
setting: "terminal"
}
Separator {
}
SettingStringList {
addLabel: qsTr("Add audio command")
name: "Audio"
object: Config.general.apps
setting: "audio"
}
Separator {
}
SettingStringList {
addLabel: qsTr("Add playback command")
name: "Playback"
object: Config.general.apps
setting: "playback"
}
Separator {
}
SettingStringList {
addLabel: qsTr("Add explorer command")
name: "Explorer"
object: Config.general.apps
setting: "explorer"
}
}
}
-165
View File
@@ -1,165 +0,0 @@
import qs.Modules.Settings.Controls
import qs.Config
SettingsPage {
SettingsSection {
sectionId: "Launcher"
SettingsHeader {
name: "Launcher"
}
SettingSpinBox {
min: 1
name: "Max apps shown"
object: Config.launcher
setting: "maxAppsShown"
}
Separator {
}
SettingSpinBox {
min: 1
name: "Max wallpapers shown"
object: Config.launcher
setting: "maxWallpapers"
}
Separator {
}
SettingInput {
name: "Action prefix"
object: Config.launcher
setting: "actionPrefix"
}
Separator {
}
SettingInput {
name: "Special prefix"
object: Config.launcher
setting: "specialPrefix"
}
Separator {
}
SettingSwitch {
name: "Use UWSM launch command"
object: Config.launcher
setting: "uwsm"
}
}
SettingsSection {
sectionId: "Fuzzy Search"
SettingsHeader {
name: "Fuzzy Search"
}
SettingSwitch {
name: "Apps"
object: Config.launcher.useFuzzy
setting: "apps"
}
Separator {
}
SettingSwitch {
name: "Actions"
object: Config.launcher.useFuzzy
setting: "actions"
}
Separator {
}
SettingSwitch {
name: "Schemes"
object: Config.launcher.useFuzzy
setting: "schemes"
}
Separator {
}
SettingSwitch {
name: "Variants"
object: Config.launcher.useFuzzy
setting: "variants"
}
Separator {
}
SettingSwitch {
name: "Wallpapers"
object: Config.launcher.useFuzzy
setting: "wallpapers"
}
}
SettingsSection {
sectionId: "Sizes"
SettingsHeader {
name: "Sizes"
}
SettingSpinBox {
min: 1
name: "Item width"
object: Config.launcher.sizes
setting: "itemWidth"
}
Separator {
}
SettingSpinBox {
min: 1
name: "Item height"
object: Config.launcher.sizes
setting: "itemHeight"
}
Separator {
}
SettingSpinBox {
min: 1
name: "Wallpaper width"
object: Config.launcher.sizes
setting: "wallpaperWidth"
}
Separator {
}
SettingSpinBox {
min: 1
name: "Wallpaper height"
object: Config.launcher.sizes
setting: "wallpaperHeight"
}
}
SettingsSection {
sectionId: "Actions"
SettingsHeader {
name: "Actions"
}
SettingActionList {
name: "Launcher actions"
object: Config.launcher
setting: "actions"
}
}
}
-125
View File
@@ -1,125 +0,0 @@
import qs.Modules.Settings.Categories.Lockscreen
import qs.Modules.Settings.Controls
import qs.Helpers
import qs.Config
SettingsPage {
id: root
SettingsSection {
sectionId: "Lockscreen"
SettingsHeader {
name: "Lockscreen"
}
SettingSwitch {
name: "Recolor logo"
object: Config.lock
setting: "recolorLogo"
}
Separator {
}
SettingSwitch {
name: "Enable fingerprint"
object: Config.lock
setting: "enableFprint"
}
Separator {
}
SettingSpinBox {
min: 1
name: "Max fingerprint tries"
object: Config.lock
setting: "maxFprintTries"
step: 1
}
Separator {
}
SettingSwitch {
name: "Show notification details"
object: Config.lock
setting: "showNotifContent"
}
Separator {
}
SettingSwitch {
name: "Show notification icon"
object: Config.lock
setting: "showNotifIcon"
}
Separator {
}
SettingSpinBox {
min: 0
name: "Blur amount"
object: Config.lock
setting: "blurAmount"
step: 1
}
Separator {
}
SettingSpinBox {
max: 2
min: 0.1
name: "Height multiplier"
object: Config.lock.sizes
setting: "heightMult"
step: 0.05
}
Separator {
}
SettingSpinBox {
max: 4
min: 0.5
name: "Aspect ratio"
object: Config.lock.sizes
setting: "ratio"
step: 0.05
}
Separator {
}
SettingSpinBox {
min: 100
name: "Center width"
object: Config.lock.sizes
setting: "centerWidth"
step: 10
}
}
SettingsSection {
sectionId: "Greeter"
SettingsHeader {
name: "Greeter"
}
SettingsIconButton {
name: "Install wallpaper and color scheme to greeter"
}
}
SettingsSection {
sectionId: "Idle"
Idle {
}
}
}
@@ -1,115 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Modules.Settings.Controls
ColumnLayout {
id: root
property bool shouldBeActive: true
function addTimeoutEntry() {
let list = [...Config.general.idle.timeouts];
list.push({
name: "New Entry",
timeout: 600,
idleAction: "lock"
});
Config.general.idle.timeouts = list;
Config.save();
}
function deleteTimeoutEntry(index) {
let list = [...Config.general.idle.timeouts];
list.splice(index, 1);
Config.general.idle.timeouts = list;
Config.save();
}
function updateTimeoutEntry(i, key, value) {
const list = [...Config.general.idle.timeouts];
let entry = list[i];
entry[key] = value;
list[i] = entry;
Config.general.idle.timeouts = list;
Config.save();
}
anchors.left: parent.left
anchors.right: parent.right
height: shouldBeActive ? implicitHeight : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
spacing: Appearance.spacing.smaller
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Settings {
name: "Idle Monitors"
}
Repeater {
model: [...Config.general.idle.timeouts]
SettingList {
Layout.fillWidth: true
anchors.left: undefined
anchors.right: undefined
onAddActiveActionRequested: {
root.updateTimeoutEntry(index, "activeAction", "");
}
onDeleteRequested: function (index) {
root.deleteTimeoutEntry(index);
}
onFieldEdited: function (key, value) {
root.updateTimeoutEntry(index, key, value);
}
}
}
IconButton {
font.pointSize: Appearance.font.size.large
icon: "add"
onClicked: root.addTimeoutEntry()
}
component Settings: CustomRect {
id: settingsItem
required property string name
Layout.preferredHeight: 60
Layout.preferredWidth: 200
CustomText {
id: text
anchors.fill: parent
font.bold: true
font.pointSize: Appearance.font.size.large * 2
text: settingsItem.name
verticalAlignment: Text.AlignVCenter
}
}
}
@@ -1,116 +0,0 @@
import qs.Modules.Settings.Controls
import qs.Config
SettingsPage {
SettingsSection {
sectionId: "Notifications"
SettingsHeader {
name: "Notifications"
}
SettingSwitch {
name: "Expire notifications"
object: Config.notifs
setting: "expire"
}
Separator {
}
SettingSpinBox {
name: "Default expire timeout"
min: 0
object: Config.notifs
setting: "defaultExpireTimeout"
step: 100
}
Separator {
}
SettingSpinBox {
name: "App notification cooldown"
min: 0
object: Config.notifs
setting: "appNotifCooldown"
step: 100
}
Separator {
}
SettingSpinBox {
name: "Clear threshold"
max: 1
min: 0
object: Config.notifs
setting: "clearThreshold"
step: 0.05
}
Separator {
}
SettingSpinBox {
name: "Expand threshold"
min: 0
object: Config.notifs
setting: "expandThreshold"
}
Separator {
}
SettingSwitch {
name: "Action on click"
object: Config.notifs
setting: "actionOnClick"
}
Separator {
}
SettingSpinBox {
name: "Group preview count"
min: 1
object: Config.notifs
setting: "groupPreviewNum"
}
}
SettingsSection {
sectionId: "Sizes"
SettingsHeader {
name: "Sizes"
}
SettingSpinBox {
name: "Width"
min: 1
object: Config.notifs.sizes
setting: "width"
}
Separator {
}
SettingSpinBox {
name: "Image size"
min: 1
object: Config.notifs.sizes
setting: "image"
}
Separator {
}
SettingSpinBox {
name: "Badge size"
min: 1
object: Config.notifs.sizes
setting: "badge"
}
}
}
-81
View File
@@ -1,81 +0,0 @@
import qs.Modules.Settings.Controls
import qs.Config
SettingsPage {
SettingsSection {
sectionId: "On Screen Display"
SettingsHeader {
name: "On Screen Display"
}
SettingSwitch {
name: "Enable OSD"
object: Config.osd
setting: "enabled"
}
Separator {
}
SettingSpinBox {
name: "Hide delay"
min: 0
object: Config.osd
setting: "hideDelay"
step: 100
}
Separator {
}
SettingSwitch {
name: "Enable brightness OSD"
object: Config.osd
setting: "enableBrightness"
}
Separator {
}
SettingSwitch {
name: "Enable microphone OSD"
object: Config.osd
setting: "enableMicrophone"
}
Separator {
}
SettingSwitch {
name: "Brightness on all monitors"
object: Config.osd
setting: "allMonBrightness"
}
}
SettingsSection {
sectionId: "Sizes"
SettingsHeader {
name: "Sizes"
}
SettingSpinBox {
name: "Slider width"
min: 1
object: Config.osd.sizes
setting: "sliderWidth"
}
Separator {
}
SettingSpinBox {
name: "Slider height"
min: 1
object: Config.osd.sizes
setting: "sliderHeight"
}
}
}
-150
View File
@@ -1,150 +0,0 @@
import qs.Modules.Settings.Controls
import qs.Config
import qs.Components
SettingsPage {
SettingsSection {
sectionId: "Screenshot"
SettingsHeader {
name: "Screenshot"
}
SettingSwitch {
name: "Enable effects"
object: Config.screenshot
setting: "enable_pp"
}
Separator {
}
CustomSplitButtonRow {
active: Config.screenshot.mode === "manual" ? menuItems[0] : menuItems[1]
enabled: Config.screenshot.enable_pp
label: qsTr("Effects mode")
menuItems: [
MenuItem {
icon: "build"
text: qsTr("Manual")
value: "manual"
},
MenuItem {
icon: "rotate_auto"
text: qsTr("Auto")
value: "auto"
}
]
onSelected: item => {
Config.screenshot.mode = item.value;
Config.save();
}
}
Separator {
shouldBeActive: Config.screenshot.mode === "manual"
}
SettingSwitch {
enabled: Config.screenshot.enable_pp
name: "Enable rounded corners"
object: Config.screenshot
setting: "rounding"
shouldBeActive: Config.screenshot.mode === "manual"
}
Separator {
shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.rounding
}
SettingSpinBox {
enabled: Config.screenshot.enable_pp
min: 0
name: "Corner radius"
object: Config.screenshot
setting: "radius"
shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.rounding
step: 1
}
Separator {
shouldBeActive: Config.screenshot.mode === "manual"
}
SettingSwitch {
enabled: Config.screenshot.enable_pp
name: "Enable shadow"
object: Config.screenshot
setting: "shadow"
shouldBeActive: Config.screenshot.mode === "manual"
}
Separator {
shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.shadow
}
SettingSpinBox {
enabled: Config.screenshot.enable_pp
min: 0
name: "Shadow blur amount"
object: Config.screenshot
setting: "shadow_blur"
shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.shadow
step: 1
}
Separator {
shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.shadow
}
// SettingSwitch {
// name: "Shadow color broken atm"
// object: Config.Screenshot
// setting: "shadow_color"
// shouldBeActive: Config.screenshot.mode === "manual"
// }
//
// Separator {
// shouldBeActive: Config.screenshot.mode === "manual"
// }
// SettingSpinBox {
// min: 1
// name: "Shadow passes"
// object: Config.screenshot
// setting: "shadow_blur_passes"
// shouldBeActive: Config.screenshot.mode === "manual"
// step: 1
// }
//
// Separator {
// shouldBeActive: Config.screenshot.mode === "manual"
// }
SettingSpinBox {
enabled: Config.screenshot.enable_pp
min: 0
name: "Shadow offset X"
object: Config.screenshot
setting: "shadow_offset_x"
shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.shadow
step: 1
}
Separator {
shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.shadow
}
SettingSpinBox {
enabled: Config.screenshot.enable_pp
min: 0
name: "Shadow offset Y"
object: Config.screenshot
setting: "shadow_offset_y"
shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.shadow
step: 1
}
}
}
-145
View File
@@ -1,145 +0,0 @@
import qs.Modules.Settings.Controls
import qs.Config
SettingsPage {
SettingsSection {
sectionId: "Services"
SettingsHeader {
name: "Services"
}
SettingInput {
name: "Weather location"
object: Config.services
setting: "weatherLocation"
}
Separator {
}
SettingSwitch {
name: "Check for updates"
object: Config.services
setting: "updates"
}
Separator {
}
SettingSwitch {
name: "Use Fahrenheit"
object: Config.services
setting: "useFahrenheit"
}
Separator {
}
SettingSwitch {
name: "Use twelve hour clock"
object: Config.services
setting: "useTwelveHourClock"
}
Separator {
}
SettingSwitch {
name: "Enable ddcutil service"
object: Config.services
setting: "ddcutilService"
}
Separator {
}
SettingInput {
name: "GPU type"
object: Config.services
setting: "gpuType"
}
}
SettingsSection {
sectionId: "Media"
SettingsHeader {
name: "Media"
}
SettingSpinBox {
max: 1
min: 0
name: "Audio increment"
object: Config.services
setting: "audioIncrement"
step: 0.05
}
Separator {
}
SettingSpinBox {
max: 1
min: 0
name: "Brightness increment"
object: Config.services
setting: "brightnessIncrement"
step: 0.05
}
Separator {
}
SettingSpinBox {
max: 1.0
min: 0
name: "Minimum brightness"
object: Config.services
setting: "minBrightness"
step: 0.01
}
Separator {
}
SettingSpinBox {
max: 5
min: 0
name: "Max volume"
object: Config.services
setting: "maxVolume"
step: 0.05
}
Separator {
}
SettingInput {
name: "Default player"
object: Config.services
setting: "defaultPlayer"
}
Separator {
}
SettingSpinBox {
min: 1
name: "Visualizer resolution"
object: Config.services
setting: "visualizerBars"
step: 1
}
Separator {
}
SettingAliasList {
name: "Player aliases"
object: Config.services
setting: "playerAliases"
}
}
}
-28
View File
@@ -1,28 +0,0 @@
import qs.Modules.Settings.Controls
import qs.Config
SettingsPage {
SettingsSection {
sectionId: "Sidebar"
SettingsHeader {
name: "Sidebar"
}
SettingSwitch {
name: "Enable sidebar"
object: Config.sidebar
setting: "enabled"
}
Separator {
}
SettingSpinBox {
name: "Width"
min: 1
object: Config.sidebar.sizes
setting: "width"
}
}
}
@@ -1,197 +0,0 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick.Layouts
import QtQuick
import qs.Config
import qs.Helpers
import qs.Components
import qs.Modules.Settings.Controls
CustomClippingRect {
id: root
radius: Appearance.rounding.normal - Appearance.padding.smaller
ColumnLayout {
anchors.fill: parent
RowLayout {
Layout.fillWidth: true
Layout.margins: Appearance.padding.large
spacing: Appearance.spacing.large
MaterialIcon {
font.pointSize: Appearance.font.size.larger * 4
text: "update"
}
ColumnLayout {
CustomText {
font.pointSize: Appearance.font.size.large * 2
text: "System updates"
}
RowLayout {
id: row
Layout.fillWidth: true
CustomText {
id: text
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: `${Updates.availableUpdates} available updates`
}
CustomRect {
Layout.preferredHeight: 40
Layout.preferredWidth: 150
color: Updates.updating ? DynamicColors.layer(DynamicColors.palette.m3outline, 2) : DynamicColors.palette.m3primary
radius: Appearance.rounding.full
RowLayout {
anchors.centerIn: parent
MaterialIcon {
animate: true
color: DynamicColors.palette.m3onPrimary
font.pointSize: Appearance.font.size.large
text: Updates.updating ? "update" : "download"
}
CustomText {
color: Updates.updating ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onPrimary
text: "Update all"
}
}
StateLayer {
color: DynamicColors.palette.m3onPrimary
enabled: !Updates.updating
onClicked: Updates.performSystemUpdate()
}
}
}
}
}
CustomListView {
id: view
readonly property int itemHeight: 50 + Appearance.padding.smaller * 2
Layout.fillHeight: true
Layout.fillWidth: true
clip: true
contentHeight: height
spacing: Appearance.spacing.normal
delegate: CustomRect {
id: update
required property var modelData
readonly property list<string> sections: modelData.update.split(" ")
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: view.itemHeight
implicitWidth: parent.width
radius: Appearance.rounding.small - Appearance.padding.small
RowLayout {
anchors.fill: parent
anchors.leftMargin: Appearance.padding.smaller
anchors.rightMargin: Appearance.padding.smaller
MaterialIcon {
font.pointSize: Appearance.font.size.large * 2
text: "package_2"
}
ColumnLayout {
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
Layout.preferredHeight: 25
elide: Text.ElideRight
font.pointSize: Appearance.font.size.large
text: update.sections[0]
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurfaceVariant
text: Updates.formatUpdateTime(update.modelData.timestamp)
}
}
RowLayout {
Layout.fillHeight: true
Layout.preferredWidth: 500
MarqueeText {
id: versionFrom
Layout.fillHeight: true
Layout.preferredWidth: 225
animate: true
color: DynamicColors.palette.m3tertiary
font.pointSize: Appearance.font.size.large
horizontalAlignment: Text.AlignHCenter
marqueeEnabled: true
pauseMs: 4000
text: update.sections[1]
width: 225
}
MaterialIcon {
Layout.fillHeight: true
color: DynamicColors.palette.m3secondary
font.pointSize: Appearance.font.size.extraLarge
horizontalAlignment: Text.AlignHCenter
text: "arrow_right_alt"
verticalAlignment: Text.AlignVCenter
}
MarqueeText {
id: versionTo
Layout.fillHeight: true
Layout.preferredWidth: 225
animate: true
color: DynamicColors.palette.m3primary
font.pointSize: Appearance.font.size.large
horizontalAlignment: Text.AlignHCenter
marqueeEnabled: true
pauseMs: 4000
text: update.sections[3]
width: 225
}
}
IconButton {
Layout.preferredHeight: width
icon: "download"
onClicked: {
Updates.performPackageUpdate(update.sections[0]);
}
}
}
}
model: ScriptModel {
id: script
objectProp: "update"
values: Object.entries(Updates.updates).sort((a, b) => b[1] - a[1]).map(([update, timestamp]) => ({
update,
timestamp
}))
}
}
}
}
-245
View File
@@ -1,245 +0,0 @@
import qs.Modules.Settings.Controls
import qs.Config
SettingsPage {
SettingsSection {
sectionId: "Utilities"
SettingsHeader {
name: "Utilities"
}
SettingSwitch {
name: "Enable utilities"
object: Config.utilities
setting: "enabled"
}
Separator {
}
SettingSpinBox {
min: 1
name: "Max toasts"
object: Config.utilities
setting: "maxToasts"
}
Separator {
}
SettingSpinBox {
min: 1
name: "Panel width"
object: Config.utilities.sizes
setting: "width"
}
Separator {
}
SettingSpinBox {
min: 1
name: "Toast width"
object: Config.utilities.sizes
setting: "toastWidth"
}
}
SettingsSection {
sectionId: "Clipboard"
SettingsHeader {
name: "Clipboard"
}
SettingSwitch {
name: "Enable clipboard history viewer"
object: Config.clipboard
setting: "enabled"
}
Separator {
}
SettingSpinBox {
max: 20
min: 1
name: "Max entries visible"
object: Config.clipboard
setting: "maxEntriesShown"
}
Separator {
}
SettingSpinBox {
max: 80
min: 30
name: "Entry height"
object: Config.clipboard.sizes
setting: "itemHeight"
}
Separator {
}
SettingSpinBox {
max: 700
min: 300
name: "Entry width"
object: Config.clipboard.sizes
setting: "width"
}
Separator {
}
SettingSpinBox {
max: 1800
min: 50
name: "Minimum preview width"
object: Config.clipboard.sizes
setting: "minPreviewWidth"
}
Separator {
}
SettingSpinBox {
max: 1800
min: 50
name: "Maximum preview width"
object: Config.clipboard.sizes
setting: "previewWidth"
}
}
SettingsSection {
sectionId: "Toasts"
SettingsHeader {
name: "Toasts"
}
SettingSwitch {
name: "Config loaded"
object: Config.utilities.toasts
setting: "configLoaded"
}
Separator {
}
SettingSwitch {
name: "Charging changed"
object: Config.utilities.toasts
setting: "chargingChanged"
}
Separator {
}
SettingSwitch {
name: "Game mode changed"
object: Config.utilities.toasts
setting: "gameModeChanged"
}
// Separator {
// }
//
// SettingSwitch {
// name: "Do not disturb changed"
// object: Config.utilities.toasts
// setting: "dndChanged"
// }
//
// Separator {
// }
//
// SettingSwitch {
// name: "Audio output changed"
// object: Config.utilities.toasts
// setting: "audioOutputChanged"
// }
//
// Separator {
// }
//
// SettingSwitch {
// name: "Audio input changed"
// object: Config.utilities.toasts
// setting: "audioInputChanged"
// }
//
// Separator {
// }
//
// SettingSwitch {
// name: "Caps lock changed"
// object: Config.utilities.toasts
// setting: "capsLockChanged"
// }
//
// Separator {
// }
//
// SettingSwitch {
// name: "Num lock changed"
// object: Config.utilities.toasts
// setting: "numLockChanged"
// }
//
// Separator {
// }
//
// SettingSwitch {
// name: "Keyboard layout changed"
// object: Config.utilities.toasts
// setting: "kbLayoutChanged"
// }
//
// Separator {
// }
//
// SettingSwitch {
// name: "VPN changed"
// object: Config.utilities.toasts
// setting: "vpnChanged"
// }
//
// Separator {
// }
//
// SettingSwitch {
// name: "Now playing"
// object: Config.utilities.toasts
// setting: "nowPlaying"
// }
}
// SettingsSection {
// sectionId: "VPN"
//
// SettingsHeader {
// name: "VPN"
// }
//
// SettingSwitch {
// name: "Enable VPN integration"
// object: Config.utilities.vpn
// setting: "enabled"
// }
//
// Separator {
// }
//
// SettingStringList {
// name: "Provider"
// addLabel: qsTr("Add VPN provider")
// object: Config.utilities.vpn
// setting: "provider"
// }
// }
}
@@ -0,0 +1,96 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.Pipewire
import qs.Components
import qs.Config
ItemList {
id: root
property int currentId: -1
property string iconName: "speaker"
property var nodes: []
signal selected(node: PwNode)
last: true
showList: true
delegate: Item {
id: device
readonly property bool active: device.modelData?.id === root.currentId
required property int index
required property PwNode modelData
anchors.left: root.list.contentItem.left
anchors.right: root.list.contentItem.right
implicitHeight: deviceLayout.implicitHeight + deviceLayout.anchors.margins * 2
StateLayer {
bottomLeftRadius: device.index === root?.list.count - 1 ? Appearance.rounding.large : radius
bottomRightRadius: device.index === root?.list.count - 1 ? Appearance.rounding.large : radius
radius: Appearance.rounding.extraSmall
onClicked: root.selected(device.modelData)
}
RowLayout {
id: deviceLayout
anchors.fill: parent
anchors.leftMargin: Appearance.padding.largeIncreased
anchors.margins: Appearance.padding.large
anchors.rightMargin: Appearance.padding.largeIncreased
spacing: Appearance.spacing.normal
CustomRect {
color: device.active ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondaryContainer
implicitHeight: devIcon.implicitHeight + Appearance.padding.normal * 2
implicitWidth: implicitHeight
radius: Appearance.rounding.full
MaterialIcon {
id: devIcon
anchors.centerIn: parent
color: device.active ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSecondaryContainer
fill: device.active ? 1 : 0
font.pointSize: Appearance.font.size.large
text: root.iconName
Behavior on fill {
Anim {
}
}
}
}
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.smaller
text: device.modelData?.description || device.modelData?.name || qsTr("Unknown")
}
MaterialIcon {
color: DynamicColors.palette.m3primary
font.pointSize: Appearance.font.size.large
opacity: device.active ? 1 : 0
text: "check"
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
}
}
model: ScriptModel {
values: [...root.nodes].sort((a, b) => (a.description || a.name || "").localeCompare(b.description || b.name || ""))
}
}
+128
View File
@@ -0,0 +1,128 @@
import QtQuick
import ZShell.Blobs
import qs.Components
import qs.Config
Item {
id: root
property real animDriver
property alias color: blobGroup.color
default required property Item content
property bool hoverOverride
readonly property alias hovered: btn.containsMouse
property alias icon: icon.text
property bool open
property int padding
property bool pressOverride
property int topMovement: Appearance.padding.large
implicitHeight: btn.implicitHeight * 0.9
implicitWidth: btn.implicitWidth * 0.9
Binding {
property: "opacity"
target: root.content
value: root.animDriver
}
BlobGroup {
id: blobGroup
color: DynamicColors.palette.m3surfaceContainerHighest
cornerFill: false
smoothing: Appearance.rounding.medium
Behavior on color {
CAnim {
}
}
}
BlobRect {
id: btnRect
anchors.fill: parent
anchors.margins: (!(btn.pressed || root.pressOverride) && (btn.containsMouse || root.hoverOverride) ? -Appearance.padding.extraSmall : 0) + (root.open ? -Appearance.padding.extraSmall : 0)
group: blobGroup
radius: root.open ? Appearance.rounding.large : Appearance.rounding.medium
Behavior on anchors.margins {
Anim {
}
}
Behavior on radius {
Anim {
type: Anim.DefaultEffects
}
}
}
BlobRect {
id: rect
anchors.right: parent.right
anchors.top: parent.top
deformScale: 0.00001
group: blobGroup
implicitHeight: parent.height
implicitWidth: parent.width
radius: Appearance.rounding.small + Appearance.padding.small
states: State {
name: "open"
when: root.open
PropertyChanges {
rect.anchors.rightMargin: root.width - Appearance.spacing.small
rect.anchors.topMargin: -root.topMovement
rect.implicitHeight: root.content.implicitHeight + root.padding * 2
rect.implicitWidth: root.content.implicitWidth + root.padding * 2
root.animDriver: 1
}
}
transitions: Transition {
Anim {
properties: "rightMargin,implicitWidth"
}
Anim {
duration: Appearance.anim.durations.expressiveFastEffects
easing.bezierCurve: Appearance.anim.curves.expressiveFastEffects
properties: "topMargin,implicitHeight"
}
Anim {
property: "animDriver"
type: Anim.DefaultEffects
}
}
MouseArea { // MouseArea to catch inputs
anchors.fill: parent
children: [root.content]
clip: true
}
}
MouseArea {
id: btn
anchors.centerIn: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
implicitHeight: icon.implicitHeight + Appearance.padding.extraSmall * 2
implicitWidth: implicitHeight
onClicked: root.open = !root.open
MaterialIcon {
id: icon
anchors.centerIn: parent
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.medium
text: "view_apps"
}
}
}
+14
View File
@@ -0,0 +1,14 @@
import QtQuick
import qs.Components
import qs.Config
CustomRect {
property bool first
property bool last
bottomLeftRadius: last ? Appearance.rounding.large : Appearance.rounding.extraSmall
bottomRightRadius: last ? Appearance.rounding.large : Appearance.rounding.extraSmall
color: DynamicColors.tPalette.m3surfaceContainer
topLeftRadius: first ? Appearance.rounding.large : Appearance.rounding.extraSmall
topRightRadius: first ? Appearance.rounding.large : Appearance.rounding.extraSmall
}
+78
View File
@@ -0,0 +1,78 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
ConnectedRect {
id: root
property string icon
property color iconColor: DynamicColors.palette.m3onSurfaceVariant
property alias label: label.text
property Component leadingComponent: icon ? iconComp : null
property string subtext
property alias value: value.text
Layout.fillWidth: true
implicitHeight: rowLayout.implicitHeight + rowLayout.anchors.margins * 2
Component {
id: iconComp
MaterialIcon {
color: root.iconColor
font.pointSize: Appearance.font.size.small
text: root.icon
}
}
RowLayout {
id: rowLayout
anchors.fill: parent
anchors.leftMargin: Appearance.padding.largeIncreased
anchors.margins: Appearance.padding.larger
anchors.rightMargin: Appearance.padding.largeIncreased
spacing: Appearance.spacing.small
Loader {
active: root.leadingComponent
sourceComponent: root.leadingComponent
visible: root.leadingComponent
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
id: label
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: root.subtext
visible: root.subtext
}
}
CustomText {
id: value
Layout.maximumWidth: root.width / 2
color: DynamicColors.palette.m3onSurfaceVariant
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
horizontalAlignment: Text.AlignRight
}
}
}
+116
View File
@@ -0,0 +1,116 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
ConnectedRect {
id: root
property alias delegate: list.delegate
property int extraHeight
readonly property alias list: list
property alias model: list.model
property string placeholderIcon
property string placeholderText
property bool showList
Layout.fillWidth: true
clip: true
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: (showList && list.count > 0 ? list.contentHeight : placeholder.implicitHeight + Appearance.padding.extraLarge * 2) + extraHeight
Behavior on implicitHeight {
Anim {
}
}
Loader {
id: placeholder
active: opacity > 0
anchors.centerIn: parent
opacity: root.showList && list.count > 0 ? 0 : 1
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
sourceComponent: ColumnLayout {
spacing: Appearance.spacing.extraSmall
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
animate: true
color: DynamicColors.palette.m3outline
font.pointSize: Appearance.font.size.large
text: root.placeholderIcon
}
CustomText {
Layout.alignment: Qt.AlignHCenter
animate: true
color: DynamicColors.palette.m3outline
font.pointSize: Appearance.font.size.large
text: root.placeholderText
}
}
}
ListView {
id: list
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
interactive: false
opacity: root.showList ? 1 : 0
spacing: 0
add: Transition {
Anim {
from: 0
property: "opacity"
to: 1
type: Anim.DefaultEffects
}
}
displaced: Transition {
Anim {
property: "opacity"
to: 1
type: Anim.DefaultEffects
}
Anim {
property: "y"
}
}
move: Transition {
Anim {
property: "opacity"
to: 1
type: Anim.DefaultEffects
}
Anim {
property: "y"
}
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
remove: Transition {
Anim {
property: "opacity"
to: 0
type: Anim.DefaultEffects
}
}
}
}
+70
View File
@@ -0,0 +1,70 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
ConnectedRect {
id: root
property alias icon: icon.text
property alias status: status.text
property alias text: label.text
signal clicked
Layout.fillWidth: true
implicitHeight: navLayout.implicitHeight + navLayout.anchors.margins * 2
StateLayer {
onClicked: root.clicked()
}
RowLayout {
id: navLayout
anchors.fill: parent
anchors.leftMargin: Appearance.padding.largeIncreased
anchors.margins: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.largeIncreased
spacing: Appearance.spacing.normal
MaterialIcon {
id: icon
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.large
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
id: label
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
}
CustomText {
id: status
Layout.fillWidth: true
animate: true
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
visible: text
}
}
MaterialIcon {
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.medium
text: "chevron_right"
}
}
}
+68
View File
@@ -0,0 +1,68 @@
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Components
import qs.Modules.Settings
ConnectedRect {
id: root
property alias checked: switchButton.checked
property int horizontalPadding: Appearance.padding.largeIncreased
required property Component popup
property alias subtext: subtext.text
property alias text: text.text
property int verticalPadding: Appearance.padding.normal
signal clicked(checked: bool)
Layout.fillWidth: true
implicitHeight: layout.implicitHeight + verticalPadding * 2
Column {
id: layout
anchors.left: parent.left
anchors.leftMargin: root.horizontalPadding
anchors.right: icon.left
anchors.rightMargin: Appearance.padding.normal
anchors.verticalCenter: parent.verticalCenter
CustomText {
id: text
font.pointSize: Appearance.font.size.smaller
}
CustomText {
id: subtext
color: DynamicColors.palette.m3outline
font.pointSize: Appearance.font.size.small
wrapMode: Text.WordWrap
}
}
MaterialIcon {
id: icon
anchors.right: switchButton.left
anchors.rightMargin: Appearance.spacing.normal
anchors.verticalCenter: parent.verticalCenter
text: "open_in_new"
}
StateLayer {
onClicked: PopupManager.requestOpen(root.popup)
}
CustomSwitch {
id: switchButton
anchors.right: parent.right
anchors.rightMargin: root.horizontalPadding
anchors.verticalCenter: parent.verticalCenter
onClicked: root.clicked(checked)
}
}
+76
View File
@@ -0,0 +1,76 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Modules.Settings
import qs.Components
import qs.Config
ColumnLayout {
id: root
readonly property int cappedWidth: Math.min(800, width)
default property Item contentChild
readonly property alias flickable: flickable
property bool isSubPage
required property SettingsState sState
required property string title
spacing: Appearance.spacing.large
MouseArea { // Prevent clicks from reaching flickable
Layout.bottomMargin: -flickable.topMargin // Extra height to block clicks on flickable top margin
implicitHeight: header.implicitHeight - Layout.bottomMargin
implicitWidth: header.implicitWidth
z: 1
onClicked: focus = true
RowLayout {
id: header
spacing: Appearance.spacing.large
Loader {
active: root.isSubPage
asynchronous: true
visible: active
sourceComponent: IconButton {
icon: "arrow_back"
inactiveColor: DynamicColors.tPalette.m3surfaceContainerHigh
inactiveOnColor: DynamicColors.palette.m3onSurfaceVariant
isRound: true
type: IconButton.Tonal
onClicked: root.sState.closeSubPage()
}
}
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.large
text: root.title
}
}
}
VerticalFadeFlickable {
id: flickable
Layout.fillHeight: true
Layout.fillWidth: true
Layout.topMargin: -topMargin
bottomMargin: Appearance.padding.extraLarge
contentHeight: root.contentChild?.implicitHeight ?? 0
contentItem.children: [root.contentChild]
fadeAmount: 0.1
topMargin: Appearance.padding.large
TapHandler {
onTapped: flickable.focus = true
}
}
}
+121
View File
@@ -0,0 +1,121 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Components
import qs.Config
import qs.Drawers
ConnectedRect {
id: root
default required property Item content
property alias icon: icon.text
property bool keepPopupAsChild
readonly property alias popup: popup
property alias status: status.text
property alias text: label.text
Layout.fillWidth: true
implicitHeight: navLayout.implicitHeight + navLayout.anchors.margins * 2
StateLayer {
id: stateLayer
manualHoverOverride: popup.hovered && !popup.open
onClicked: popup.open = true
}
RowLayout {
id: navLayout
anchors.fill: parent
anchors.leftMargin: Appearance.padding.largeIncreased
anchors.margins: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.largeIncreased
spacing: Appearance.spacing.normal
MaterialIcon {
id: icon
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.larger
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
id: label
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
}
CustomText {
id: status
Layout.fillWidth: true
animate: true
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
visible: text
}
}
Item {
id: triggerArea
implicitHeight: popup.implicitHeight
implicitWidth: popup.implicitWidth
TransformWatcher {
id: tWatcher
a: area.parent
b: triggerArea
}
MouseArea {
id: area
anchors.fill: parent
cursorShape: popup.open ? Qt.ArrowCursor : undefined
enabled: popup.open
hoverEnabled: popup.open
parent: {
if (root.keepPopupAsChild)
return triggerArea;
const win = QsWindow.window;
const contentWin = win as Windows; // If inside the drawer content window, put it inside the interaction wrapper so hover works
return contentWin ? contentWin.interactionWrapper : (win as QsWindow).contentItem;
}
z: popup.animDriver > 0 ? 1 : 0
onClicked: popup.open = false
BlobPopup {
id: popup
color: open || hovered || stateLayer.containsMouse ? DynamicColors.palette.m3secondaryContainer : DynamicColors.palette.m3surfaceContainerHighest
content: root.content
hoverOverride: stateLayer.containsMouse
padding: Appearance.padding.small
pressOverride: stateLayer.pressed
x: {
tWatcher.transform;
return triggerArea.mapToItem(area.parent, 0, 0).x;
}
y: {
tWatcher.transform;
return triggerArea.mapToItem(area.parent, 0, 0).y;
}
}
}
}
}
}
+16
View File
@@ -0,0 +1,16 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
CustomText {
property bool first
Layout.bottomMargin: Appearance.spacing.extraSmall
Layout.fillWidth: true
Layout.leftMargin: Appearance.padding.small
Layout.topMargin: first ? 0 : Appearance.spacing.large - ((parent as ColumnLayout).spacing ?? 0)
color: DynamicColors.palette.m3onSurfaceVariant
elide: Text.ElideRight
font.pointSize: Appearance.font.size.medium
}
+64
View File
@@ -0,0 +1,64 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
ConnectedRect {
id: root
property alias active: splitButton.active
property alias fallbackIcon: splitButton.fallbackIcon
property alias fallbackText: splitButton.fallbackText
property alias menuItems: splitButton.menuItems
property alias menuOnTop: splitButton.menuOnTop
property string subtext
property alias text: label.text
signal selected(item: MenuItem)
Layout.fillWidth: true
clip: false
implicitHeight: rowLayout.implicitHeight + rowLayout.anchors.margins * 2
z: splitButton.expanded ? 1 : 0
RowLayout {
id: rowLayout
anchors.fill: parent
anchors.leftMargin: Appearance.padding.largeIncreased
anchors.margins: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.largeIncreased
spacing: Appearance.spacing.small
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
id: label
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.smaller
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: root.subtext
visible: root.subtext
}
}
CustomSplitButton {
id: splitButton
type: CustomSplitButton.Tonal
menu.onItemSelected: item => root.selected(item)
stateLayer.onClicked: splitButton.expanded = !splitButton.expanded
}
}
}
+81
View File
@@ -0,0 +1,81 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
ConnectedRect {
id: root
property alias icon: slider.insetIcon
property alias label: label.text
property real value
property alias valueLabel: valueLabel.text
signal moved(value: real)
Layout.fillWidth: true
implicitHeight: rowLayout.implicitHeight + rowLayout.anchors.margins + rowLayout.anchors.topMargin
RowLayout {
id: rowLayout
anchors.fill: parent
anchors.margins: Appearance.padding.largeIncreased
anchors.topMargin: Appearance.padding.large
spacing: Appearance.spacing.small
ColumnLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.small
RowLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.small
CustomText {
id: label
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
}
CustomText {
id: valueLabel
color: DynamicColors.palette.m3outline
font.pointSize: Appearance.font.size.small
}
}
CustomMouseArea {
function onWheel(event: WheelEvent): void {
const step = Config.services.audioIncrement;
if (event.angleDelta.y > 0)
root.moved(Math.min(1, root.value + step));
else if (event.angleDelta.y < 0)
root.moved(Math.max(0, root.value - step));
}
Layout.fillWidth: true
implicitHeight: Appearance.padding.larger * 3
CustomSlider {
id: slider
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
enabled: root.enabled
implicitHeight: parent.implicitHeight
radius: Appearance.rounding.small
value: root.value
onInteraction: v => root.moved(v)
}
}
}
}
}
+63
View File
@@ -0,0 +1,63 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
ConnectedRect {
id: root
property real from: 0
property real stepSize: 1
property string subtext
property alias text: label.text
property real to: 99
property real value
signal moved(value: real)
Layout.fillWidth: true
implicitHeight: rowLayout.implicitHeight + rowLayout.anchors.margins * 2
RowLayout {
id: rowLayout
anchors.fill: parent
anchors.leftMargin: Appearance.padding.largeIncreased
anchors.margins: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.largeIncreased
spacing: Appearance.spacing.small
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
id: label
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.smaller
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: root.subtext
visible: root.subtext
}
}
CustomSpinBox {
from: root.from
stepSize: root.stepSize
to: root.to
value: root.value
onValueModified: root.moved(value)
}
}
}
+125
View File
@@ -0,0 +1,125 @@
import QtQuick
import QtQuick.Controls
import qs.Components
import qs.Modules.Settings
import qs.Config
StackView {
id: root
readonly property int animMovement: Appearance.padding.extraLarge * 2
default property list<Component> pages
required property SettingsState sState
function openSubPage(idx: int, immediate: bool): void {
const page = pages[idx];
if (page) {
push(page, {
sState
}, immediate ? StackView.Immediate : StackView.PushTransition);
} else {
console.warn(logCat, "Attempted to open invalid sub-page index", idx);
sState.closeSubPage();
}
}
clip: busy
popEnter: Transition {
SequentialAnimation {
PropertyAction {
property: "opacity"
value: 0
}
PauseAnimation {
duration: Appearance.anim.durations.expressiveEffects
}
ParallelAnimation {
Anim {
property: "opacity"
to: 1
type: Anim.SlowEffects
}
Anim {
from: -root.animMovement
property: "x"
to: 0
type: Anim.SlowEffects
}
}
}
}
popExit: Transition {
Anim {
property: "opacity"
to: 0
type: Anim.DefaultEffects
}
}
pushEnter: Transition {
SequentialAnimation {
PropertyAction {
property: "opacity"
value: 0
}
PauseAnimation {
duration: Appearance.anim.durations.expressiveEffects
}
ParallelAnimation {
Anim {
property: "opacity"
to: 1
type: Anim.SlowEffects
}
Anim {
from: root.animMovement
property: "x"
to: 0
type: Anim.SlowEffects
}
}
}
}
pushExit: Transition {
Anim {
property: "opacity"
to: 0
type: Anim.DefaultEffects
}
}
Component.onCompleted: {
openSubPage(0, true);
for (const page of sState.subPageIdxStack)
openSubPage(page, true);
}
LoggingCategory {
id: logCat
defaultLogLevel: LoggingCategory.Info
name: "ZShell.settings"
}
Connections {
function onSubPageClosed(): void {
if (root.depth < root.sState.subPageIdxStack.length) {
console.log(logCat, "Attempted to close page while depth < stack depth. Ignoring.");
return;
}
root.pop();
}
function onSubPageOpened(idx: int): void {
root.openSubPage(idx, false);
}
target: root.sState
}
}
+581
View File
@@ -0,0 +1,581 @@
import QtQuick
import QtQuick.Layouts
import ZShell.Components
import qs.Config
import qs.Components
import qs.Helpers
CustomClippingRect {
id: root
required property var object
required property list<string> settings
property bool shouldBeActive: true
signal applySettings(startTime: int, endTime: int)
signal close
function convertHour(timeValue: int): int {
return Math.floor(timeValue / 60);
}
function convertMinute(timeValue: int): int {
return timeValue % 60;
}
function convertToMinutes(hour: int, minute: int): int {
return hour * 60 + minute;
}
color: DynamicColors.palette.m3surfaceContainer
implicitHeight: column.implicitHeight + column.anchors.margins * 2 + buttonRow.implicitHeight + buttonRow.anchors.topMargin
implicitWidth: column.implicitWidth + column.anchors.margins * 2
radius: Appearance.rounding.large
ColumnLayout {
id: column
anchors.left: parent.left
anchors.margins: Appearance.padding.largeIncreased
anchors.right: parent.right
anchors.top: parent.top
spacing: Appearance.spacing.normal
CustomText {
text: qsTr("Select time")
}
RowLayout {
CustomRect {
id: startHourRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: startHourField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Appearance.rounding.small
CustomRect {
anchors.fill: parent
border.color: startHourField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest
border.width: startHourField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {
}
}
}
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 ? DynamicColors.palette.m3primaryContainer : DynamicColors.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: Appearance.font.size.extraLarge
text: ":"
}
CustomRect {
id: startMinuteRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: startMinuteField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Appearance.rounding.small
CustomRect {
anchors.fill: parent
border.color: startMinuteField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest
border.width: startMinuteField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {
}
}
}
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 ? DynamicColors.palette.m3primaryContainer : DynamicColors.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();
}
}
event.accepted = true;
}
onCursorPositionChanged: cursorPosition = 2
onTextEdited: {
if (startMinuteField.text === "")
return;
var val = parseInt(startMinuteField.text);
if (isNaN(val))
return;
val = Math.max(0, Math.min(23, val));
var newText = val.toString();
if (newText !== startMinuteField.text)
startMinuteField.text = newText;
}
}
}
Item {
id: spacer
Layout.preferredWidth: Appearance.spacing.large
}
CustomRect {
id: endHourRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: endHourField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Appearance.rounding.small
CustomRect {
anchors.fill: parent
border.color: endHourField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest
border.width: endHourField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {
}
}
}
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 ? DynamicColors.palette.m3primaryContainer : DynamicColors.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();
}
}
event.accepted = true;
}
onCursorPositionChanged: cursorPosition = 2
onTextEdited: {
if (endHourField.text === "")
return;
var val = parseInt(endHourField.text);
if (isNaN(val))
return;
val = Math.max(0, Math.min(23, val));
var newText = val.toString();
if (newText !== endHourField.text)
endHourField.text = newText;
}
}
}
CustomText {
id: endSeparator
font.pointSize: Appearance.font.size.extraLarge
text: ":"
}
CustomRect {
id: endMinuteRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: endMinuteField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Appearance.rounding.small
CustomRect {
anchors.fill: parent
border.color: endMinuteField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest
border.width: endMinuteField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {
}
}
}
TextFieldBase {
id: endMinuteField
function setConfigText(setting: string): string {
var val = root.convertMinute(root.object[setting]);
if (val === 0) {
return "00";
}
return String(val);
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
color: focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface
font.family: "Roboto"
font.letterSpacing: -0.25
font.pixelSize: 56
font.weight: 400
horizontalAlignment: TextInput.AlignHCenter
text: setConfigText(root.settings[2])
verticalAlignment: TextInput.AlignVCenter
Keys.onPressed: event => {
if (event.key === Qt.Key_Backspace) {
event.accepted = true;
if (endMinuteField.text.length >= 2) {
endMinuteField.text = "0" + endMinuteField.text[0];
} else if (endMinuteField.text.length === 1) {
endMinuteField.text = "0";
}
return;
} else if (event.key === Qt.Key_Escape) {
event.accepted = true;
endMinuteField.text = setConfigText(root.settings[2]);
endMinuteField.focus = false;
} else if (event.key === Qt.Key_Return) {
endMinuteField.focus = false;
return;
} else if (event.key === Qt.Key_Tab) {
startHourField.focus = true;
} else if (event.key === Qt.Key_Backtab) {
endHourField.focus = true;
}
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
event.accepted = true;
var digit = event.text;
var textLen = endMinuteField.text.length;
if (textLen >= 2 && endMinuteField.text[0] !== '0') {
return;
}
var val = 0;
if (textLen === 0) {
val = parseInt(digit);
} else if (textLen === 1) {
val = parseInt(endMinuteField.text + digit);
} else {
val = parseInt(endMinuteField.text[1] + digit);
}
val = Math.max(0, Math.min(59, val));
if (textLen >= 2 && val < 10) {
endMinuteField.text = "0" + val;
} else {
endMinuteField.text = val.toString();
}
}
event.accepted = true;
}
onCursorPositionChanged: cursorPosition = 2
onTextEdited: {
if (endMinuteField.text === "")
return;
var val = parseInt(endMinuteField.text);
if (isNaN(val))
return;
val = Math.max(0, Math.min(23, val));
var newText = val.toString();
if (newText !== endMinuteField.text)
endMinuteField.text = newText;
}
}
}
}
RowLayout {
CustomText {
Layout.preferredWidth: spacer.x + spacer.width
text: qsTr("Start")
}
CustomText {
Layout.preferredWidth: endMinuteRect.width + endHourRect.width
text: qsTr("End")
}
}
}
RowLayout {
id: buttonRow
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: Appearance.padding.largeIncreased
anchors.right: parent.right
anchors.top: column.bottom
anchors.topMargin: Appearance.spacing.normal
Item {
id: buttonSpacer
Layout.fillWidth: true
}
ButtonRow {
spacing: Appearance.spacing.normal
IconTextButton {
font.pointSize: Appearance.font.size.normal
icon: "close"
inactiveColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2)
inactiveOnColor: DynamicColors.palette.m3onSurfaceVariant
isRound: true
isToggle: false
shapeMorph: true
shapeMorphExpansion: pressed ? 12 : 0
text: "Cancel"
onClicked: root.close()
}
IconTextButton {
font.pointSize: Appearance.font.size.normal
icon: "check"
isRound: true
isToggle: false
shapeMorph: true
shapeMorphExpansion: pressed ? 12 : 0
text: "Apply"
onClicked: {
const start = root.convertToMinutes(parseInt(startHourField.text)) + parseInt(startMinuteField.text);
const end = root.convertToMinutes(parseInt(endHourField.text)) + parseInt(endMinuteField.text);
root.applySettings(start, end);
}
}
}
}
}
+82
View File
@@ -0,0 +1,82 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
CustomSwitch {
id: root
readonly property alias bg: bg
property alias first: bg.first
property alias last: bg.last
property string subtext
Layout.fillWidth: true
cLayer: 2
horizontalPadding: Appearance.padding.largeIncreased
implicitHeight: Math.max(implicitContentHeight, implicitIndicatorHeight) + verticalPadding * 2
implicitWidth: implicitContentWidth + implicitIndicatorWidth + horizontalPadding * 2
indicator.anchors.right: right
indicator.anchors.rightMargin: root.horizontalPadding
indicator.anchors.verticalCenter: verticalCenter
verticalPadding: Appearance.padding.normal
background: ConnectedRect {
id: bg
StateLayer {
id: stateLayer
manualPressOverride: root.pressed
}
}
contentItem: Item {
anchors.left: parent.left
anchors.leftMargin: root.horizontalPadding
anchors.right: root.indicator.left
anchors.rightMargin: Appearance.spacing.normal
implicitHeight: column.implicitHeight
implicitWidth: column.implicitWidth
Column {
id: column
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 0
CustomText {
id: label
anchors.left: parent.left
anchors.right: parent.right
elide: Text.ElideRight
font: {
const f = Qt.font(label.font);
f.pointSize = Appearance.font.size.smaller;
return f;
}
text: root.text
}
CustomText {
id: subtext
anchors.left: parent.left
anchors.right: parent.right
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font: {
const f = Qt.font(subtext.font);
f.pointSize = Appearance.font.size.small;
return f;
}
text: root.subtext
visible: root.subtext
}
}
}
onPressed: stateLayer.press(stateLayer.mouseX, stateLayer.mouseY)
}
+62
View File
@@ -0,0 +1,62 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Components
import qs.Config
Item {
id: root
property alias imgHeight: imgWrapper.implicitHeight
property alias radius: imgWrapper.radius
property alias source: img.source
signal clicked
Layout.fillWidth: true
implicitHeight: layout.implicitHeight
ColumnLayout {
id: layout
anchors.fill: parent
spacing: Appearance.spacing.small
CustomClippingRect {
id: imgWrapper
Layout.fillWidth: true
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: width
radius: Appearance.rounding.large
Image {
id: img
anchors.fill: parent
asynchronous: true
fillMode: Image.PreserveAspectCrop
opacity: status === Image.Ready ? 1 : 0
retainWhileLoading: true
sourceSize: {
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
return Qt.size(width * dpr, height * dpr);
}
Behavior on opacity {
Anim {
type: Anim.SlowEffects
}
}
}
}
}
StateLayer {
anchors.bottomMargin: layout.implicitHeight - imgWrapper.implicitHeight
onClicked: root.clicked()
}
}
@@ -0,0 +1,342 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import ZShell.Components
import qs.Config
import qs.Components
import qs.Helpers
Item {
id: wrapper
property int buttonRowHeight: Appearance.padding.large * 3
property bool changesMade: false
readonly property var currentScreen: screens.length > selectedScreenIndex ? screens[selectedScreenIndex] : null
property var screens: []
property int selectedScreenIndex: 0
property bool shouldBeActive: true
function applyCrop(): void {
if (!cropRectLoader.item || !currentScreen)
return;
const cropRect = cropRectLoader.item;
const cropXPercent = (cropRect.x - cropRect.imageX) / scaledImg.paintedWidth;
const cropYPercent = (cropRect.y - cropRect.imageY) / scaledImg.paintedHeight;
const cropWidthPercent = cropRect.width / scaledImg.paintedWidth;
const cropHeightPercent = cropRect.height / scaledImg.paintedHeight;
Wallpapers.setCrop(currentScreen.name, Qt.rect(cropXPercent, cropYPercent, cropWidthPercent, cropHeightPercent), cropRect.zoom);
}
function refreshScreens(): void {
screens = [...Quickshell.screens].sort((a, b) => a.x - b.x);
selectedScreenIndex = 0;
}
function selectScreen(index: int): void {
if (index < 0 || index >= screens.length || index === selectedScreenIndex)
return;
selectedScreenIndex = index;
if (cropRectLoader.item)
Qt.callLater(syncCropToScreen);
}
function syncCropToScreen(): void {
if (cropRectLoader.item)
cropRectLoader.item.restoreFromData();
}
function zoomClipRect(zoom: real): void {
if (!cropRectLoader.item)
return;
const cropRect = cropRectLoader.item;
const centerX = cropRect.x + cropRect.width * 0.5;
const centerY = cropRect.y + cropRect.height * 0.5;
cropRect.zoom = zoom;
cropRect.x = centerX - cropRect.width * 0.5;
cropRect.y = centerY - cropRect.height * 0.5;
cropRect.clampToBounds();
}
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: shouldBeActive ? 430 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Component.onCompleted: {
refreshScreens();
Qt.callLater(syncCropToScreen);
}
onSelectedScreenIndexChanged: {
Qt.callLater(syncCropToScreen);
}
IconButton {
anchors.margins: Appearance.padding.normal
anchors.right: parent.right
anchors.top: parent.top
icon: "check"
opacity: wrapper.changesMade ? 1 : 0
scale: wrapper.changesMade ? 1 : 0
z: 2
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
onClicked: {
wrapper.applyCrop();
wrapper.changesMade = false;
}
}
ButtonRow {
id: screenSelector
anchors.bottom: scaledImg.top
anchors.bottomMargin: Appearance.spacing.normal
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.extraLarge * 2
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.extraLarge * 2
anchors.top: parent.top
spacing: Appearance.spacing.small
Repeater {
model: wrapper.screens
delegate: TextButton {
required property int index
readonly property bool isCurrent: wrapper.selectedScreenIndex === index
required property var modelData
fillWidth: true
inactiveColor: isCurrent ? DynamicColors.palette.m3primary : Qt.alpha(DynamicColors.palette.m3surfaceContainer, 0.7)
inactiveOnColor: isCurrent ? DynamicColors.palette.m3onPrimary : Qt.alpha(DynamicColors.palette.m3onSurface, 0.7)
isRound: true
shapeMorph: true
text: modelData.name
onClicked: wrapper.selectScreen(index)
}
}
}
RowLayout {
id: sliderLayout
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: 30
CustomSlider {
id: zoomSlider
Layout.fillWidth: true
Layout.leftMargin: Appearance.padding.normal
Layout.preferredHeight: Appearance.padding.larger * 3
Layout.rightMargin: Appearance.padding.normal
from: 1.0
implicitHeight: Appearance.padding.larger * 3
insetIcon: "crop"
to: 5.0
value: cropRectLoader.item ? cropRectLoader.item.zoom : 1.0
onInteraction: value => {
wrapper.zoomClipRect(1 + (value * 4));
wrapper.changesMade = true;
}
}
}
Image {
id: scaledImg
property var displayData
property real monitorScale: 1.0
anchors.bottom: sliderLayout.top
anchors.bottomMargin: Appearance.spacing.normal
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: wrapper.buttonRowHeight + Appearance.spacing.normal
// anchors.top: screenSelector.bottom
asynchronous: true
fillMode: Image.PreserveAspectFit
retainWhileLoading: true
source: Wallpapers.current
sourceSize.height: parent.height
sourceSize.width: parent.width
onPaintedWidthChanged: {
if (paintedWidth > 0 && cropRectLoader.item) {
if (!wrapper.screens.length)
wrapper.refreshScreens();
wrapper.syncCropToScreen();
}
}
onSourceChanged: {
if (cropRectLoader.item) {
if (!wrapper.screens.length)
wrapper.refreshScreens();
wrapper.syncCropToScreen();
}
}
onStatusChanged: {
if (scaledImg.status == Image.Ready && cropRectLoader.item) {
if (!wrapper.screens.length)
wrapper.refreshScreens();
wrapper.syncCropToScreen();
}
}
Loader {
id: cropRectLoader
active: scaledImg.status === Image.Ready
sourceComponent: Component {
CustomRect {
id: cropRect
property real aspectRatio: wrapper.currentScreen ? wrapper.currentScreen.width / wrapper.currentScreen.height : 1
readonly property real baseHeight: baseWidth / aspectRatio
readonly property real baseWidth: {
let fittedHeight = scaledImg.paintedHeight;
let fittedWidth = fittedHeight * aspectRatio;
if (fittedWidth > scaledImg.paintedWidth) {
fittedWidth = scaledImg.paintedWidth;
fittedHeight = fittedWidth / aspectRatio;
}
return fittedWidth;
}
readonly property real imageX: (scaledImg.width - scaledImg.paintedWidth) / 2
readonly property real imageY: (scaledImg.height - scaledImg.paintedHeight) / 2
property real imgAspectRatio: scaledImg.paintedWidth / scaledImg.paintedHeight
property real zoom: 1.0
function centerInImage() {
x = imageX + (scaledImg.paintedWidth - width) / 2;
y = imageY + (scaledImg.paintedHeight - height) / 2;
}
function clampToBounds() {
x = Math.max(imageX, Math.min(x, imageX + scaledImg.paintedWidth - width));
y = Math.max(imageY, Math.min(y, imageY + scaledImg.paintedHeight - height));
}
function restoreFromData() {
let data = Wallpapers.getCrop(wrapper.currentScreen.name);
console.log(data.x, data.y);
if (data && (Math.abs(data.x) > 0.001 || Math.abs(data.y) > 0.001 || Math.abs(data.width - 1.0) > 0.001 || Math.abs(data.height - 1.0) > 0.001)) {
zoom = data.zoom > 0 ? data.zoom : 1.0;
x = imageX + (data.x * scaledImg.paintedWidth);
y = imageY + (data.y * scaledImg.paintedHeight);
clampToBounds();
} else {
zoom = 1.0;
centerInImage();
}
}
border.color: DynamicColors.palette.m3primary
border.width: 2
height: baseHeight / zoom
opacity: 1
width: baseWidth / zoom
Behavior on opacity {
Anim {
}
}
Component.onCompleted: {
restoreFromData();
}
onHeightChanged: clampToBounds()
onWidthChanged: clampToBounds()
}
}
}
MouseArea {
id: mouse
function updateCrop(mouseX, mouseY) {
if (!cropRectLoader.item)
return;
const cropRect = cropRectLoader.item;
let nx = mouseX - cropRect.width * 0.5;
let ny = mouseY - cropRect.height * 0.5;
nx = Math.max(cropRect.imageX, Math.min(nx, cropRect.imageX + scaledImg.paintedWidth - cropRect.width));
ny = Math.max(cropRect.imageY, Math.min(ny, cropRect.imageY + scaledImg.paintedHeight - cropRect.height));
cropRect.x = nx;
cropRect.y = ny;
}
anchors.fill: parent
hoverEnabled: true
preventStealing: true
onPositionChanged: mouse => {
if (pressed) {
updateCrop(mouse.x, mouse.y);
wrapper.changesMade = true;
}
}
onPressed: mouse => {
updateCrop(mouse.x, mouse.y);
wrapper.changesMade = true;
}
onReleased: {
wrapper.changesMade = true;
}
}
}
}
+47 -369
View File
@@ -1,398 +1,76 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Widgets
import QtQuick
import QtQuick.Controls
import ZShell.Blobs
import qs.Components
import qs.Modules as Modules
import qs.Modules.Settings.Categories as Cat
import qs.Config
import qs.Helpers
Item {
CustomClippingRect {
id: root
property string currentCategory: "general"
property int currentIndex: 0
property int lastIndex: 0
readonly property real nonAnimHeight: Math.floor(screen.height / 1.5) + viewWrapper.anchors.margins * 2
readonly property real nonAnimWidth: view.implicitWidth + Math.floor(screen.width / 2) + viewWrapper.anchors.margins * 2
property string pendingSection: ""
property string pendingSetting: ""
required property ShellScreen screen
required property PersistentProperties visibilities
property color blobColor: DynamicColors.tPalette.m3surfaceContainerLow
readonly property real ratio: 16.0 / 9.0
readonly property SettingsState sState: SettingsState {
id: sState
function scrollToSetting(section: string, settingName: string) {
root.pendingSection = section;
root.pendingSetting = settingName;
scrollTimer.restart();
onClose: root.close()
}
function setCategory(category: string, index: int): void {
currentIndex = index;
currentCategory = category;
}
signal close
implicitHeight: nonAnimHeight
implicitWidth: nonAnimWidth
implicitHeight: sState.screen.height * 0.7
implicitWidth: implicitHeight * ratio
radius: Appearance.rounding.large + Appearance.padding.normal
Timer {
id: scrollTimer
interval: 50
onTriggered: {
if (root.pendingSection && stack.currentItem) {
stack.currentItem.scrollToSectionAndHighlight(root.pendingSection, root.pendingSetting);
root.pendingSection = "";
root.pendingSetting = "";
}
Behavior on blobColor {
CAnim {
}
}
Connections {
function onCurrentCategoryChanged() {
if (root.currentCategory === "general") {
stack.replaceCurrentItem(general, [], StackView.PopTransition);
root.lastIndex = root.currentIndex;
} else if (root.currentCategory === "wallpaper") {
if (root.currentIndex < root.lastIndex)
stack.replaceCurrentItem(background, [], StackView.PopTransition);
else
stack.replaceCurrentItem(background, [], StackView.PushTransition);
root.lastIndex = root.currentIndex;
} else if (root.currentCategory === "bar") {
if (root.currentIndex < root.lastIndex)
stack.replaceCurrentItem(bar, [], StackView.PopTransition);
else
stack.replaceCurrentItem(bar, [], StackView.PushTransition);
root.lastIndex = root.currentIndex;
} else if (root.currentCategory === "appearance") {
if (root.currentIndex < root.lastIndex)
stack.replaceCurrentItem(appearance, [], StackView.PopTransition);
else
stack.replaceCurrentItem(appearance, [], StackView.PushTransition);
root.lastIndex = root.currentIndex;
} else if (root.currentCategory === "lockscreen") {
if (root.currentIndex < root.lastIndex)
stack.replaceCurrentItem(lockscreen, [], StackView.PopTransition);
else
stack.replaceCurrentItem(lockscreen, [], StackView.PushTransition);
root.lastIndex = root.currentIndex;
} else if (root.currentCategory === "services") {
if (root.currentIndex < root.lastIndex)
stack.replaceCurrentItem(services, [], StackView.PopTransition);
else
stack.replaceCurrentItem(services, [], StackView.PushTransition);
root.lastIndex = root.currentIndex;
} else if (root.currentCategory === "notifications") {
if (root.currentIndex < root.lastIndex)
stack.replaceCurrentItem(notifications, [], StackView.PopTransition);
else
stack.replaceCurrentItem(notifications, [], StackView.PushTransition);
root.lastIndex = root.currentIndex;
} else if (root.currentCategory === "sidebar") {
if (root.currentIndex < root.lastIndex)
stack.replaceCurrentItem(sidebar, [], StackView.PopTransition);
else
stack.replaceCurrentItem(sidebar, [], StackView.PushTransition);
root.lastIndex = root.currentIndex;
} else if (root.currentCategory === "utilities") {
if (root.currentIndex < root.lastIndex)
stack.replaceCurrentItem(utilities, [], StackView.PopTransition);
else
stack.replaceCurrentItem(utilities, [], StackView.PushTransition);
root.lastIndex = root.currentIndex;
} else if (root.currentCategory === "dashboard") {
if (root.currentIndex < root.lastIndex)
stack.replaceCurrentItem(dashboard, [], StackView.PopTransition);
else
stack.replaceCurrentItem(dashboard, [], StackView.PushTransition);
root.lastIndex = root.currentIndex;
} else if (root.currentCategory === "osd") {
if (root.currentIndex < root.lastIndex)
stack.replaceCurrentItem(osd, [], StackView.PopTransition);
else
stack.replaceCurrentItem(osd, [], StackView.PushTransition);
root.lastIndex = root.currentIndex;
} else if (root.currentCategory === "launcher") {
if (root.currentIndex < root.lastIndex)
stack.replaceCurrentItem(launcher, [], StackView.PopTransition);
else
stack.replaceCurrentItem(launcher, [], StackView.PushTransition);
root.lastIndex = root.currentIndex;
} else if (root.currentCategory === "screenshot") {
if (root.currentIndex < root.lastIndex)
stack.replaceCurrentItem(screenshot, [], StackView.PopTransition);
else
stack.replaceCurrentItem(screenshot, [], StackView.PushTransition);
root.lastIndex = root.currentIndex;
} else if (root.currentCategory === "updates") {
if (root.currentIndex < root.lastIndex)
stack.replaceCurrentItem(updates, [], StackView.PopTransition);
else
stack.replaceCurrentItem(updates, [], StackView.PushTransition);
root.lastIndex = root.currentIndex;
}
}
target: root
TapHandler {
onTapped: root.focus = true
}
CustomClippingRect {
id: viewWrapper
BlobGroup {
id: blobGroup
color: root.blobColor
smoothing: Appearance.rounding.normal
}
BlobInvertedRect {
anchors.fill: parent
anchors.margins: Appearance.padding.smaller
radius: Appearance.rounding.large - Appearance.padding.smaller
SettingsSearch {
id: searchBar
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
onSettingSelected: (category, section, settingName) => {
layout.selectCategory(category);
root.scrollToSetting(section, settingName);
}
}
Item {
id: view
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.top: searchBar.bottom
anchors.topMargin: Appearance.spacing.smaller
implicitWidth: layout.implicitWidth
Categories {
id: layout
anchors.fill: parent
content: root
onSettingSelected: (category, section, settingName) => {
root.scrollToSetting(section, settingName);
}
}
}
CustomClippingRect {
id: categoryContent
anchors.bottom: parent.bottom
anchors.left: view.right
anchors.leftMargin: Appearance.spacing.smaller
anchors.right: parent.right
anchors.top: searchBar.bottom
anchors.topMargin: Appearance.spacing.smaller
color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.normal
StackView {
id: stack
anchors.fill: parent
initialItem: general
popEnter: Transition {
SequentialAnimation {
Anim {
duration: 0
from: 0
property: "opacity"
to: 0
}
PauseAnimation {
duration: Appearance.anim.durations.expressiveEffects
}
ParallelAnimation {
Anim {
duration: Appearance.anim.durations.expressiveFastSpatial
from: 0
property: "opacity"
to: 1
}
Anim {
duration: Appearance.anim.durations.expressiveFastSpatial
from: -50
property: "y"
to: 0
}
}
}
}
popExit: Transition {
ParallelAnimation {
Anim {
duration: Appearance.anim.durations.expressiveFastSpatial
from: 1
property: "opacity"
to: 0
}
Anim {
duration: Appearance.anim.durations.expressiveFastSpatial
from: 0
property: "y"
to: 50
}
}
}
pushEnter: Transition {
SequentialAnimation {
Anim {
duration: 0
from: 0
property: "opacity"
to: 0
}
PauseAnimation {
duration: Appearance.anim.durations.expressiveEffects
}
ParallelAnimation {
Anim {
duration: Appearance.anim.durations.expressiveFastSpatial
from: 0
property: "opacity"
to: 1
}
Anim {
duration: Appearance.anim.durations.expressiveFastSpatial
from: 50
property: "y"
to: 0
}
}
}
}
pushExit: Transition {
ParallelAnimation {
Anim {
duration: Appearance.anim.durations.expressiveFastSpatial
from: 1
property: "opacity"
to: 0
}
Anim {
duration: Appearance.anim.durations.expressiveFastSpatial
from: 0
property: "y"
to: -50
}
}
}
}
}
borderBottom: Appearance.padding.normal
borderLeft: navPane.width + navPane.anchors.margins * 2
borderRight: Appearance.padding.normal
borderTop: Appearance.padding.normal
group: blobGroup
opacity: root.blobColor.a
radius: Appearance.rounding.large
}
Component {
id: general
NavPane {
id: navPane
Cat.General {
}
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: Appearance.padding.large
anchors.top: parent.top
sState: root.sState
width: Math.min(600, Math.round(root.width / 3))
}
Component {
id: background
Cat.Background {
screen: root.screen
}
Pages {
anchors.bottom: parent.bottom
anchors.left: navPane.right
anchors.leftMargin: navPane.anchors.margins + anchors.margins
anchors.margins: Appearance.padding.extraLarge
anchors.right: parent.right
anchors.top: parent.top
sState: root.sState
}
Component {
id: appearance
Cat.Appearance {
}
}
Component {
id: bar
Cat.Bar {
}
}
Component {
id: lockscreen
Cat.Lockscreen {
}
}
Component {
id: services
Cat.Services {
}
}
Component {
id: notifications
Cat.Notifications {
}
}
Component {
id: sidebar
Cat.Sidebar {
}
}
Component {
id: utilities
Cat.Utilities {
}
}
Component {
id: dashboard
Cat.Dashboard {
}
}
Component {
id: osd
Cat.Osd {
}
}
Component {
id: launcher
Cat.Launcher {
}
}
Component {
id: screenshot
Cat.Screenshot {
}
}
Component {
id: updates
Cat.SystemUpdates {
}
PopupOverlay {
anchors.fill: parent
}
}
-660
View File
@@ -1,660 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Components
import qs.Helpers
Item {
id: root
readonly property string endTime: {
var d = new Date(0, 0, 0, 0, 0, 0, 0);
d.setMinutes(object[settings[2]]);
return Qt.formatTime(d, "hh:mm AP");
}
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
required property string name
required property var object
required property list<string> settings
property bool shouldBeActive: true
readonly property string startTime: {
var d = new Date(0, 0, 0, 0, 0, 0, 0);
d.setMinutes(object[settings[1]]);
return Qt.formatTime(d, "hh:mm AP");
}
function commitChoice(choice: int, setting: string): void {
root.object[setting] = choice;
Config.save();
Hyprsunset.checkStartup();
}
function convertHour(timeValue: int): int {
return Math.floor(timeValue / 60);
}
function convertMinute(timeValue: int): int {
return timeValue % 60;
}
function convertToMinutes(hour: int, minute: int): int {
return hour * 60 + minute;
}
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Rectangle {
anchors.fill: parent
anchors.margins: -Appearance.padding.smaller
color: DynamicColors.palette.m3primaryContainer
opacity: root.highlighted ? 0.5 : 0
radius: Appearance.rounding.small
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.normal
}
}
}
RowLayout {
id: row
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
ColumnLayout {
Layout.fillHeight: true
Layout.fillWidth: true
CustomText {
id: text
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
CustomText {
Layout.alignment: Qt.AlignLeft
Layout.preferredWidth: Math.min(contentWidth, optionLayout.x - Appearance.spacing.normal)
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.normal
text: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(root.startTime).arg(root.endTime)
wrapMode: Text.WordWrap
}
}
ColumnLayout {
id: optionLayout
Layout.fillHeight: true
Layout.fillWidth: true
RowLayout {
CustomText {
Layout.preferredWidth: spacer.x + spacer.width
text: qsTr("Start")
}
CustomText {
Layout.preferredWidth: endMinuteRect.width + endHourRect.width
text: qsTr("End")
}
CustomText {
Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter
text: qsTr("Enabled: ")
}
CustomSwitch {
id: enabledSwitch
Layout.alignment: Qt.AlignRight | Qt.AlignHCenter
checked: root.object[root.settings[0]]
onToggled: {
root.object[root.settings[0]] = checked;
Config.save();
}
}
}
RowLayout {
Layout.fillHeight: true
CustomRect {
id: startHourRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: startHourField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Appearance.rounding.small
CustomRect {
anchors.fill: parent
border.color: startHourField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest
border.width: startHourField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {
}
}
}
CustomTextField {
id: startHourField
function setConfigText(setting: string): string {
var val = root.convertHour(root.object[setting]);
if (val === 0) {
return "00";
}
return String(val);
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
color: focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface
cursorHeight: height - Appearance.padding.normal * 2
font.family: "Roboto"
font.letterSpacing: -0.25
font.pixelSize: 56
font.weight: 400
horizontalAlignment: TextInput.AlignHCenter
text: setConfigText(root.settings[1])
verticalAlignment: TextInput.AlignVCenter
Keys.onPressed: event => {
if (event.key === Qt.Key_Backspace) {
event.accepted = true;
if (startHourField.text.length >= 2) {
startHourField.text = "0" + startHourField.text[0];
} else if (startHourField.text.length === 1) {
startHourField.text = "0";
}
return;
} else if (event.key === Qt.Key_Escape) {
event.accepted = true;
startHourField.text = setConfigText(root.settings[1]);
startHourField.focus = false;
} else if (event.key === Qt.Key_Return) {
startHourField.focus = false;
return;
} else if (event.key === Qt.Key_Tab) {
startMinuteField.focus = true;
} else if (event.key === Qt.Key_Backtab) {
endMinuteField.focus = true;
}
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
event.accepted = true;
var digit = event.text;
var textLen = startHourField.text.length;
if (textLen >= 2 && startHourField.text[0] !== '0') {
return;
}
var val = 0;
if (textLen === 0) {
val = parseInt(digit);
} else if (textLen === 1) {
val = parseInt(startHourField.text + digit);
} else {
val = parseInt(startHourField.text[1] + digit);
}
val = Math.max(0, Math.min(23, val));
if (textLen >= 2 && val < 10) {
startHourField.text = "0" + val;
} else {
startHourField.text = val.toString();
}
}
event.accepted = true;
}
onCursorPositionChanged: cursorPosition = 2
onEditingFinished: {
root.commitChoice(root.convertToMinutes(parseInt(startHourField.text), parseInt(startMinuteField.text)), root.settings[1]);
}
onTextEdited: {
if (startHourField.text === "")
return;
var val = parseInt(startHourField.text);
if (isNaN(val))
return;
val = Math.max(0, Math.min(23, val));
var newText = val.toString();
if (newText !== startHourField.text)
startHourField.text = newText;
}
}
}
CustomText {
id: startSeparator
font.pointSize: Appearance.font.size.extraLarge
text: ":"
}
CustomRect {
id: startMinuteRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: startMinuteField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Appearance.rounding.small
CustomRect {
anchors.fill: parent
border.color: startMinuteField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest
border.width: startMinuteField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {
}
}
}
CustomTextField {
id: startMinuteField
function setConfigText(setting: string): string {
var val = root.convertMinute(root.object[setting]);
if (val === 0) {
return "00";
}
return String(val);
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
color: focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface
cursorHeight: height - Appearance.padding.normal * 2
font.family: "Roboto"
font.letterSpacing: -0.25
font.pixelSize: 56
font.weight: 400
horizontalAlignment: TextInput.AlignHCenter
text: setConfigText(root.settings[1])
verticalAlignment: TextInput.AlignVCenter
Keys.onPressed: event => {
if (event.key === Qt.Key_Backspace) {
event.accepted = true;
if (startMinuteField.text.length >= 2) {
startMinuteField.text = "0" + startMinuteField.text[0];
} else if (startMinuteField.text.length === 1) {
startMinuteField.text = "0";
}
return;
} else if (event.key === Qt.Key_Escape) {
event.accepted = true;
startMinuteField.text = setConfigText(root.settings[1]);
startMinuteField.focus = false;
} else if (event.key === Qt.Key_Return) {
startMinuteField.focus = false;
return;
} else if (event.key === Qt.Key_Tab) {
endHourField.focus = true;
} else if (event.key === Qt.Key_Backtab) {
startHourField.focus = true;
}
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
event.accepted = true;
var digit = event.text;
var textLen = startMinuteField.text.length;
if (textLen >= 2 && startMinuteField.text[0] !== '0') {
return;
}
var val = 0;
if (textLen === 0) {
val = parseInt(digit);
} else if (textLen === 1) {
val = parseInt(startMinuteField.text + digit);
} else {
val = parseInt(startMinuteField.text[1] + digit);
}
val = Math.max(0, Math.min(59, val));
if (textLen >= 2 && val < 10) {
startMinuteField.text = "0" + val;
} else {
startMinuteField.text = val.toString();
}
}
event.accepted = true;
}
onCursorPositionChanged: cursorPosition = 2
onEditingFinished: {
root.commitChoice(root.convertToMinutes(parseInt(startHourField.text), parseInt(startMinuteField.text)), root.settings[1]);
}
onTextEdited: {
if (startMinuteField.text === "")
return;
var val = parseInt(startMinuteField.text);
if (isNaN(val))
return;
val = Math.max(0, Math.min(23, val));
var newText = val.toString();
if (newText !== startMinuteField.text)
startMinuteField.text = newText;
}
}
}
Item {
id: spacer
Layout.preferredWidth: Appearance.spacing.large
}
CustomRect {
id: endHourRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: endHourField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Appearance.rounding.small
CustomRect {
anchors.fill: parent
border.color: endHourField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest
border.width: endHourField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {
}
}
}
CustomTextField {
id: endHourField
function setConfigText(setting: string): string {
var val = root.convertHour(root.object[setting]);
if (val === 0) {
return "00";
}
return String(val);
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
color: focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface
cursorHeight: height - Appearance.padding.normal * 2
font.family: "Roboto"
font.letterSpacing: -0.25
font.pixelSize: 56
font.weight: 400
horizontalAlignment: TextInput.AlignHCenter
text: setConfigText(root.settings[2])
verticalAlignment: TextInput.AlignVCenter
Keys.onPressed: event => {
if (event.key === Qt.Key_Backspace) {
event.accepted = true;
if (endHourField.text.length >= 2) {
endHourField.text = "0" + endHourField.text[0];
} else if (endHourField.text.length === 1) {
endHourField.text = "0";
}
return;
} else if (event.key === Qt.Key_Escape) {
event.accepted = true;
endHourField.text = setConfigText(root.settings[2]);
endHourField.focus = false;
} else if (event.key === Qt.Key_Return) {
endHourField.focus = false;
return;
} else if (event.key === Qt.Key_Tab) {
endMinuteField.focus = true;
} else if (event.key === Qt.Key_Backtab) {
startMinuteField.focus = true;
}
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
event.accepted = true;
var digit = event.text;
var textLen = endHourField.text.length;
if (textLen >= 2 && endHourField.text[0] !== '0') {
return;
}
var val = 0;
if (textLen === 0) {
val = parseInt(digit);
} else if (textLen === 1) {
val = parseInt(endHourField.text + digit);
} else {
val = parseInt(endHourField.text[1] + digit);
}
val = Math.max(0, Math.min(23, val));
if (textLen >= 2 && val < 10) {
endHourField.text = "0" + val;
} else {
endHourField.text = val.toString();
}
}
event.accepted = true;
}
onCursorPositionChanged: cursorPosition = 2
onEditingFinished: {
root.commitChoice(root.convertToMinutes(parseInt(endHourField.text), parseInt(endMinuteField.text)), root.settings[2]);
}
onTextEdited: {
if (endHourField.text === "")
return;
var val = parseInt(endHourField.text);
if (isNaN(val))
return;
val = Math.max(0, Math.min(23, val));
var newText = val.toString();
if (newText !== endHourField.text)
endHourField.text = newText;
}
}
}
CustomText {
id: endSeparator
font.pointSize: Appearance.font.size.extraLarge
text: ":"
}
CustomRect {
id: endMinuteRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: endMinuteField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Appearance.rounding.small
CustomRect {
anchors.fill: parent
border.color: endMinuteField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest
border.width: endMinuteField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {
}
}
}
CustomTextField {
id: endMinuteField
function setConfigText(setting: string): string {
var val = root.convertMinute(root.object[setting]);
if (val === 0) {
return "00";
}
return String(val);
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
color: focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface
cursorHeight: height - Appearance.padding.normal * 2
font.family: "Roboto"
font.letterSpacing: -0.25
font.pixelSize: 56
font.weight: 400
horizontalAlignment: TextInput.AlignHCenter
text: setConfigText(root.settings[2])
verticalAlignment: TextInput.AlignVCenter
Keys.onPressed: event => {
if (event.key === Qt.Key_Backspace) {
event.accepted = true;
if (endMinuteField.text.length >= 2) {
endMinuteField.text = "0" + endMinuteField.text[0];
} else if (endMinuteField.text.length === 1) {
endMinuteField.text = "0";
}
return;
} else if (event.key === Qt.Key_Escape) {
event.accepted = true;
endMinuteField.text = setConfigText(root.settings[2]);
endMinuteField.focus = false;
} else if (event.key === Qt.Key_Return) {
endMinuteField.focus = false;
return;
} else if (event.key === Qt.Key_Tab) {
startHourField.focus = true;
} else if (event.key === Qt.Key_Backtab) {
endHourField.focus = true;
}
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
event.accepted = true;
var digit = event.text;
var textLen = endMinuteField.text.length;
if (textLen >= 2 && endMinuteField.text[0] !== '0') {
return;
}
var val = 0;
if (textLen === 0) {
val = parseInt(digit);
} else if (textLen === 1) {
val = parseInt(endMinuteField.text + digit);
} else {
val = parseInt(endMinuteField.text[1] + digit);
}
val = Math.max(0, Math.min(59, val));
if (textLen >= 2 && val < 10) {
endMinuteField.text = "0" + val;
} else {
endMinuteField.text = val.toString();
}
}
event.accepted = true;
}
onCursorPositionChanged: cursorPosition = 2
onEditingFinished: {
root.commitChoice(root.convertToMinutes(parseInt(endHourField.text), parseInt(endMinuteField.text)), root.settings[2]);
}
onTextEdited: {
if (endMinuteField.text === "")
return;
var val = parseInt(endMinuteField.text);
if (isNaN(val))
return;
val = Math.max(0, Math.min(23, val));
var newText = val.toString();
if (newText !== endMinuteField.text)
endMinuteField.text = newText;
}
}
}
}
RowLayout {
Layout.fillWidth: true
CustomText {
id: startHour
Layout.preferredWidth: startSeparator.x + startSeparator.width
text: qsTr("Hour")
}
CustomText {
id: startMinute
Layout.preferredWidth: spacer.x + spacer.width - x
text: qsTr("Minute")
}
CustomText {
Layout.preferredWidth: endSeparator.x + endSeparator.width - x
text: qsTr("Hour")
}
CustomText {
text: qsTr("Minute")
}
}
}
}
}
@@ -1,161 +0,0 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Components
Item {
id: root
required property string name
required property var object
property alias row: row
required property string setting
property bool shouldBeActive: true
required property list<var> stringList
signal optionSet(option: string)
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: shouldBeActive ? row.height : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
RowLayout {
id: row
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
CustomText {
id: text
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
CustomClippingRect {
Layout.preferredHeight: 42 * 6 + Appearance.padding.normal * 2 + Appearance.spacing.small * 5
Layout.preferredWidth: 500
color: DynamicColors.tPalette.m3surfaceContainer
radius: (21 + Appearance.padding.normal) * Appearance.rounding.scale
CustomRect {
id: searchBox
anchors.left: parent.left
anchors.margins: Appearance.padding.normal
anchors.right: parent.right
anchors.top: parent.top
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: 42
radius: Appearance.rounding.full
MaterialIcon {
id: searchIcon
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.large
anchors.top: parent.top
font.pointSize: Appearance.font.size.large
text: "search"
verticalAlignment: Text.AlignVCenter
}
CustomTextField {
id: textSearch
anchors.left: searchIcon.right
anchors.leftMargin: Appearance.spacing.small
anchors.right: parent.right
anchors.rightMargin: Appearance.spacing.normal
anchors.verticalCenter: parent.verticalCenter
placeholderText: "Search..."
}
}
CustomClippingRect {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: Appearance.padding.normal
anchors.right: parent.right
anchors.top: searchBox.bottom
bottomLeftRadius: 21
bottomRightRadius: 21
CustomListView {
anchors.fill: parent
clip: true
spacing: Appearance.spacing.small
delegate: CustomRect {
id: delegate
required property string modelData
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: 42
radius: Appearance.rounding.smallest
CustomText {
anchors.fill: parent
anchors.leftMargin: Appearance.padding.normal
text: modelData
verticalAlignment: Text.AlignVCenter
}
MaterialIcon {
anchors.fill: parent
anchors.rightMargin: Appearance.padding.normal
color: DynamicColors.palette.m3primary
font.pointSize: Appearance.font.size.large
horizontalAlignment: Text.AlignRight
text: "check_circle"
verticalAlignment: Text.AlignVCenter
visible: root.object[root.setting] === delegate.modelData
}
StateLayer {
onClicked: {
root.object[root.setting] = delegate.modelData;
root.optionSet(delegate.modelData);
Config.save();
}
}
}
model: ScriptModel {
values: {
const values = root.stringList;
const search = textSearch.text;
var regex = new RegExp(search, "i");
return values.filter(n => regex.test(n));
}
}
}
}
}
}
}
-31
View File
@@ -1,31 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
CustomRect {
id: root
property bool shouldBeActive: true
anchors.left: parent.left
anchors.right: parent.right
color: DynamicColors.tPalette.m3outlineVariant
implicitHeight: shouldBeActive ? 1 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
}
@@ -1,282 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Helpers
ColumnLayout {
id: root
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
required property string name
required property var object
required property string setting
property bool shouldBeActive: true
function addAction() {
const list = [...root.object[root.setting]];
list.push({
name: "New Action",
icon: "bolt",
description: "",
command: [],
enabled: true,
dangerous: false
});
root.object[root.setting] = list;
Config.save();
}
function removeAction(index) {
const list = [...root.object[root.setting]];
list.splice(index, 1);
root.object[root.setting] = list;
Config.save();
}
function updateAction(index, key, value) {
const list = [...root.object[root.setting]];
const entry = list[index];
entry[key] = value;
list[index] = entry;
root.object[root.setting] = list;
Config.save();
}
anchors.left: parent.left
anchors.right: parent.right
height: shouldBeActive ? implicitHeight : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
spacing: Appearance.spacing.smaller
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Rectangle {
anchors.fill: parent
anchors.margins: -Appearance.padding.smaller
color: DynamicColors.palette.m3primaryContainer
opacity: root.highlighted ? 0.5 : 0
radius: Appearance.rounding.small
z: -1
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.normal
}
}
}
CustomText {
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
Repeater {
model: [...root.object[root.setting]]
CustomRect {
required property int index
required property var modelData
Layout.fillWidth: true
Layout.preferredHeight: layout.implicitHeight + Appearance.padding.normal * 2
color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.normal
ColumnLayout {
id: layout
anchors.left: parent.left
anchors.margins: Appearance.padding.normal
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.small
RowLayout {
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: modelData.name ?? qsTr("Action")
}
IconButton {
font.pointSize: Appearance.font.size.large
icon: "delete"
type: IconButton.Tonal
onClicked: root.removeAction(index)
}
}
Separator {
Layout.fillWidth: true
anchors.left: undefined
anchors.right: undefined
}
RowLayout {
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
text: qsTr("Name")
}
CustomRect {
Layout.preferredHeight: 33
Layout.preferredWidth: 350
color: DynamicColors.tPalette.m3surfaceContainerHigh
radius: Appearance.rounding.full
CustomTextField {
anchors.fill: parent
anchors.leftMargin: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.normal
text: modelData.name ?? ""
onEditingFinished: root.updateAction(index, "name", text)
}
}
}
RowLayout {
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
text: qsTr("Icon")
}
CustomRect {
Layout.preferredHeight: 33
Layout.preferredWidth: 350
color: DynamicColors.tPalette.m3surfaceContainerHigh
radius: Appearance.rounding.full
CustomTextField {
anchors.fill: parent
anchors.leftMargin: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.normal
text: modelData.icon ?? ""
onEditingFinished: root.updateAction(index, "icon", text)
}
}
}
RowLayout {
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
text: qsTr("Description")
}
CustomRect {
Layout.preferredHeight: 33
Layout.preferredWidth: 350
color: DynamicColors.tPalette.m3surfaceContainerHigh
radius: Appearance.rounding.full
CustomTextField {
anchors.fill: parent
anchors.leftMargin: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.normal
text: modelData.description ?? ""
onEditingFinished: root.updateAction(index, "description", text)
}
}
}
StringListEditor {
Layout.fillWidth: true
addLabel: qsTr("Add command argument")
anchors.left: undefined
anchors.right: undefined
values: [...(modelData.command ?? [])]
onListEdited: function (values) {
root.updateAction(index, "command", values);
}
}
Separator {
Layout.fillWidth: true
anchors.left: undefined
anchors.right: undefined
}
RowLayout {
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
text: qsTr("Enabled")
}
CustomSwitch {
checked: modelData.enabled ?? true
onToggled: root.updateAction(index, "enabled", checked)
}
}
Separator {
Layout.fillWidth: true
anchors.left: undefined
anchors.right: undefined
}
RowLayout {
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
text: qsTr("Dangerous")
}
CustomSwitch {
checked: modelData.dangerous ?? false
onToggled: root.updateAction(index, "dangerous", checked)
}
}
}
}
}
RowLayout {
Layout.fillWidth: true
IconButton {
font.pointSize: Appearance.font.size.large
icon: "add"
onClicked: root.addAction()
}
CustomText {
Layout.fillWidth: true
text: qsTr("Add action")
}
}
}
@@ -1,203 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Helpers
CustomRect {
id: root
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
required property string name
required property var object
required property string setting
property bool shouldBeActive: true
function addAlias() {
const list = [...root.object[root.setting]];
list.push({
from: "",
to: ""
});
root.object[root.setting] = list;
Config.save();
}
function removeAlias(index) {
const list = [...root.object[root.setting]];
list.splice(index, 1);
root.object[root.setting] = list;
Config.save();
}
function updateAlias(index, key, value) {
const list = [...root.object[root.setting]];
const entry = [...list[index]];
entry[key] = value;
list[index] = entry;
root.object[root.setting] = list;
Config.save();
}
anchors.left: parent.left
anchors.right: parent.right
height: shouldBeActive ? layout.implicitHeight : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Rectangle {
anchors.fill: parent
anchors.margins: -Appearance.padding.smaller
color: DynamicColors.palette.m3primaryContainer
opacity: root.highlighted ? 0.5 : 0
radius: Appearance.rounding.small
z: -1
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.normal
}
}
}
ColumnLayout {
id: layout
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.smaller
CustomText {
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
Repeater {
model: [...root.object[root.setting]]
Item {
required property int index
required property var modelData
Layout.fillWidth: true
Layout.preferredHeight: layout.implicitHeight + Appearance.padding.smaller * 2
CustomRect {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: -(Appearance.spacing.smaller / 2)
color: DynamicColors.tPalette.m3outlineVariant
implicitHeight: 1
visible: index !== 0
}
ColumnLayout {
id: layout
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.small
RowLayout {
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
text: qsTr("From")
}
CustomRect {
Layout.preferredHeight: 33
Layout.preferredWidth: Math.max(Math.min(fromTextField.contentWidth + Appearance.padding.large * 2, 550), 50)
color: DynamicColors.tPalette.m3surfaceContainerHigh
radius: Appearance.rounding.full
CustomTextField {
id: fromTextField
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 550)
text: modelData.from ?? ""
onEditingFinished: root.updateAlias(index, "from", text)
}
}
IconButton {
font.pointSize: Appearance.font.size.large
icon: "delete"
type: IconButton.Tonal
onClicked: root.removeAlias(index)
}
}
RowLayout {
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
text: qsTr("To")
}
CustomRect {
Layout.preferredHeight: 33
Layout.preferredWidth: Math.max(Math.min(toTextField.contentWidth + Appearance.padding.large * 2, 550), 50)
color: DynamicColors.tPalette.m3surfaceContainerHigh
radius: Appearance.rounding.full
CustomTextField {
id: toTextField
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 550)
text: modelData.to ?? ""
onEditingFinished: root.updateAlias(index, "to", text)
}
}
}
}
}
}
RowLayout {
Layout.fillWidth: true
IconButton {
font.pointSize: Appearance.font.size.large
icon: "add"
onClicked: root.addAlias()
}
CustomText {
Layout.fillWidth: true
text: qsTr("Add alias")
}
}
}
}
@@ -1,241 +0,0 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import QtQuick.Layouts
import QtQml.Models
import qs.Components
import qs.Config
import qs.Helpers
Item {
id: root
property bool dragActive: false
property real dragHeight: 0
property real dragStartX: 0
property real dragStartY: 0
property real dragX: 0
property real dragY: 0
property var draggedModelData: null
property string draggedUid: ""
property bool dropAnimating: false
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
required property string name
required property var object
property var pendingCommitEntries: []
required property string setting
property bool shouldBeActive: true
property int uidCounter: 0
property var visualEntries: []
function ensureVisualEntries() {
if (!root.dragActive && !root.dropAnimating)
root.rebuildVisualEntries();
}
function iconForId(id) {
switch (id) {
case "workspaces":
return "dashboard";
case "audio":
return "volume_up";
case "media":
return "play_arrow";
case "resources":
return "monitoring";
case "updates":
return "system_update";
case "dash":
return "space_dashboard";
case "spacer":
return "horizontal_rule";
case "activeWindow":
return "web_asset";
case "tray":
return "widgets";
case "upower":
return "battery_full";
case "network":
return "wifi";
case "clock":
return "schedule";
case "notifBell":
return "notifications";
default:
return "drag_indicator";
}
}
function indexForUid(uid) {
for (let i = 0; i < root.visualEntries.length; i++) {
if (root.visualEntries[i].uid === uid)
return i;
}
return -1;
}
function labelForId(id) {
switch (id) {
case "workspaces":
return qsTr("Workspaces");
case "audio":
return qsTr("Audio");
case "media":
return qsTr("Media");
case "resources":
return qsTr("Resources");
case "updates":
return qsTr("Updates");
case "dash":
return qsTr("Dash");
case "spacer":
return qsTr("Spacer");
case "activeWindow":
return qsTr("Title");
case "tray":
return qsTr("Tray");
case "upower":
return qsTr("Power");
case "network":
return qsTr("Network");
case "clock":
return qsTr("Clock");
case "notifBell":
return qsTr("Notifs");
case "hyprsunset":
return qsTr("Night light");
default:
return id;
}
}
function rebuildVisualEntries() {
const entries = root.object[root.setting] ?? [];
const next = [];
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
let existing = null;
for (let j = 0; j < root.visualEntries.length; j++) {
if (root.visualEntries[j].entry === entry) {
existing = root.visualEntries[j];
break;
}
}
if (existing)
next.push(existing);
else
next.push({
uid: `entry-${root.uidCounter++}`,
entry
});
}
root.visualEntries = next;
}
function updateEntry(index, value) {
const list = [...root.object[root.setting]];
const entry = list[index];
entry.enabled = value;
list[index] = entry;
root.object[root.setting] = list;
Config.save();
}
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: shouldBeActive ? layout.implicitHeight : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Component.onCompleted: root.rebuildVisualEntries()
CustomRect {
anchors.fill: parent
anchors.margins: -Appearance.padding.smaller
color: DynamicColors.palette.m3primaryContainer
opacity: root.highlighted ? 0.5 : 0
radius: Appearance.rounding.small
z: -1
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.normal
}
}
}
RowLayout {
id: layout
anchors.fill: parent
// spacing: Appearance.spacing.smaller
DelegateModel {
id: visualModel
delegate: entryDelegate
model: ScriptModel {
objectProp: "uid"
values: root.visualEntries
}
}
Repeater {
delegate: entryDelegate
model: visualModel
}
}
Component {
id: entryDelegate
IconButton {
required property int index
required property var modelData
Layout.fillWidth: true
Layout.preferredWidth: implicitWidth + (stateLayer.pressed ? 18 : internalChecked ? 7 : 0)
checked: modelData.entry.enabled ?? true
font: Appearance.font.family.sans
// icon: root.iconForId(modelData.entry.id)
icon: root.labelForId(modelData.entry.id)
inactiveColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2)
isToggle: true
radius: stateLayer.pressed ? 6 / 2 : internalChecked ? 6 : 8
radiusAnim.duration: MaterialEasing.expressiveEffectsTime
radiusAnim.easing.bezierCurve: MaterialEasing.expressiveEffects
visible: !["spacer", "upower", "dash", "audio"].some(prefix => modelData.entry.id.startsWith(prefix))
Behavior on Layout.preferredWidth {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
onClicked: root.updateEntry(index, internalChecked)
}
}
}
@@ -1,214 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Helpers
Item {
id: root
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
required property string name
required property var object
required property list<string> settings
property bool shouldBeActive: true
function commitChoice(choice: int, setting: string): void {
root.object[setting] = choice;
Config.save();
}
function formattedValue(setting: string): string {
const value = root.object[setting];
if (value === null || value === undefined)
return "";
return String(value);
}
function hourToAmPm(hour) {
var h = Number(hour) % 24;
var d = new Date(2000, 0, 1, h, 0, 0);
return Qt.formatTime(d, "h AP");
}
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Rectangle {
anchors.fill: parent
anchors.margins: -Appearance.padding.smaller
color: DynamicColors.palette.m3primaryContainer
opacity: root.highlighted ? 0.5 : 0
radius: Appearance.rounding.small
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.normal
}
}
}
RowLayout {
id: row
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
ColumnLayout {
Layout.fillHeight: true
Layout.fillWidth: true
CustomText {
id: text
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
CustomText {
Layout.alignment: Qt.AlignLeft
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.normal
text: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(root.hourToAmPm(root.object[root.settings[0]])).arg(root.hourToAmPm(root.object[root.settings[1]]))
}
}
ColumnLayout {
id: optionLayout
Layout.fillHeight: true
Layout.preferredWidth: 100
RowLayout {
Layout.preferredWidth: optionLayout.width
CustomText {
Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter
Layout.fillWidth: true
text: qsTr("Enabled: ")
}
CustomSwitch {
id: enabledSwitch
Layout.alignment: Qt.AlignRight | Qt.AlignHCenter
checked: root.object[root.settings[2]]
onToggled: {
root.object[root.settings[2]] = checked;
Config.save();
}
}
}
RowLayout {
Layout.preferredWidth: optionLayout.width
z: setting2.expanded ? -1 : 1
CustomText {
Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter
Layout.fillWidth: true
text: qsTr("Start: ")
}
SpinnerButton {
id: setting1
Layout.alignment: Qt.AlignRight | Qt.AlignHCenter
Layout.preferredHeight: Appearance.font.size.large + Appearance.padding.smaller * 2
Layout.preferredWidth: height * 2
currentIndex: root.object[root.settings[0]]
enabled: enabledSwitch.checked
text: root.formattedValue(root.settings[0])
menu.onItemSelected: item => {
root.commitChoice(item, root.settings[0]);
}
}
}
RowLayout {
Layout.preferredWidth: optionLayout.width
z: setting1.expanded ? -1 : 1
CustomText {
Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter
Layout.fillWidth: true
text: qsTr("End: ")
}
SpinnerButton {
id: setting2
Layout.alignment: Qt.AlignRight | Qt.AlignHCenter
Layout.preferredHeight: Appearance.font.size.large + Appearance.padding.smaller * 2
Layout.preferredWidth: height * 2
currentIndex: root.object[root.settings[1]]
enabled: enabledSwitch.checked
text: root.formattedValue(root.settings[1])
menu.onItemSelected: item => {
root.commitChoice(item, root.settings[1]);
}
}
}
RowLayout {
Layout.preferredWidth: optionLayout.width
z: -2
CustomText {
Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter
Layout.fillWidth: true
text: qsTr("Temp: ")
}
CustomRect {
id: rect
Layout.preferredHeight: 33
Layout.preferredWidth: Math.max(Math.min(textField.contentWidth + Appearance.padding.normal * 2, 200), 50)
color: DynamicColors.tPalette.m3surfaceContainerHigh
radius: Appearance.rounding.full
CustomTextField {
id: textField
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 200)
text: root.formattedValue(root.settings[3])
onEditingFinished: {
root.object[root.settings[3]] = textField.text;
Config.save();
}
}
}
}
}
}
}
@@ -1,99 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Helpers
Item {
id: root
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
required property string name
required property var object
required property string setting
property bool shouldBeActive: true
function formattedValue(): string {
const value = root.object[root.setting];
if (value === null || value === undefined)
return "";
return String(value);
}
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Rectangle {
anchors.fill: parent
anchors.margins: -Appearance.padding.smaller
color: DynamicColors.palette.m3primaryContainer
opacity: root.highlighted ? 0.5 : 0
radius: Appearance.rounding.small
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.normal
}
}
}
RowLayout {
id: row
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
CustomText {
id: text
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
CustomRect {
id: rect
Layout.preferredHeight: 33
Layout.preferredWidth: Math.max(Math.min(textField.contentWidth + Appearance.padding.large * 2, 550), 50)
color: DynamicColors.tPalette.m3surfaceContainerHigh
radius: Appearance.rounding.full
CustomTextField {
id: textField
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 550)
text: root.formattedValue()
onEditingFinished: {
root.object[root.setting] = textField.text;
Config.save();
}
}
}
}
}
-297
View File
@@ -1,297 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
Item {
id: root
required property int index
required property var modelData
property bool shouldBeActive: true
signal addActiveActionRequested
signal deleteRequested(int index)
signal fieldEdited(string key, var value)
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
CustomRect {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: -(Appearance.spacing.smaller / 2)
color: DynamicColors.tPalette.m3outlineVariant
implicitHeight: 1
visible: root.index !== 0
}
RowLayout {
id: row
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.large
Item {
id: nameCell
property string draftName: ""
property bool editing: false
function beginEdit() {
draftName = root.modelData.name ?? "";
editing = true;
nameEditor.forceActiveFocus();
}
function cancelEdit() {
draftName = root.modelData.name ?? "";
editing = false;
}
function commitEdit() {
editing = false;
if (draftName !== (root.modelData.name ?? "")) {
root.fieldEdited("name", draftName);
}
}
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillHeight: true
Layout.preferredWidth: root.width / 2
HoverHandler {
id: nameHover
}
HoverIconButton {
id: editButton
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
font.pointSize: Appearance.font.size.large
icon: "edit"
shouldBeVisible: nameHover.hovered && !nameCell.editing
onClicked: nameCell.beginEdit()
}
CustomText {
anchors.left: parent.left
anchors.leftMargin: nameHover.hovered ? editButton.width + Appearance.spacing.smaller * 2 : 0
anchors.right: deleteButton.left
anchors.rightMargin: Appearance.spacing.small
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight // enable if CustomText supports it
font.pointSize: Appearance.font.size.larger
text: root.modelData.name
visible: !nameCell.editing
Behavior on anchors.leftMargin {
Anim {
}
}
}
HoverIconButton {
id: deleteButton
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
font.pointSize: Appearance.font.size.large
icon: "delete"
shouldBeVisible: nameHover.hovered && !nameCell.editing
onClicked: root.deleteRequested(root.index)
}
CustomRect {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.tPalette.m3surface
implicitHeight: nameEditor.implicitHeight + (Appearance.padding.normal * 2)
implicitWidth: Math.min(nameEditor.contentWidth + (Appearance.padding.normal * 2), parent.width - Appearance.padding.normal)
radius: Appearance.rounding.small
visible: nameCell.editing
CustomTextField {
id: nameEditor
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, nameCell.width - Appearance.padding.normal)
text: nameCell.draftName
Keys.onEscapePressed: {
nameCell.cancelEdit();
}
onEditingFinished: {
nameCell.commitEdit();
}
onTextEdited: {
nameCell.draftName = nameEditor.text;
}
}
}
}
VSeparator {
}
ColumnLayout {
id: cLayout
Layout.fillWidth: true
RowLayout {
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
text: qsTr("Timeout")
}
CustomRect {
Layout.preferredHeight: 33
Layout.preferredWidth: Math.max(Math.min(timeField.contentWidth + Appearance.padding.normal * 3, 200), 50)
color: DynamicColors.tPalette.m3surface
radius: Appearance.rounding.small
CustomTextField {
id: timeField
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: String(root.modelData.timeout ?? "")
onEditingFinished: {
root.fieldEdited("timeout", Number(text));
}
}
}
}
Separator {
Layout.fillWidth: true
anchors.left: undefined
anchors.right: undefined
}
RowLayout {
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
text: qsTr("Idle Action")
}
CustomRect {
Layout.preferredHeight: 33
Layout.preferredWidth: Math.max(Math.min(idleField.contentWidth + Appearance.padding.normal * 3, 200), 50)
color: DynamicColors.tPalette.m3surface
radius: Appearance.rounding.small
CustomTextField {
id: idleField
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: root.modelData.idleAction ?? ""
onEditingFinished: {
root.fieldEdited("idleAction", text);
}
}
}
}
Separator {
Layout.fillWidth: true
anchors.left: undefined
anchors.right: undefined
}
Item {
Layout.fillWidth: true
implicitHeight: activeActionRow.visible ? activeActionRow.implicitHeight : addButtonRow.implicitHeight
RowLayout {
id: activeActionRow
anchors.left: parent.left
anchors.right: parent.right
visible: root.modelData.activeAction !== undefined
CustomText {
Layout.fillWidth: true
text: qsTr("Active Action")
}
CustomRect {
Layout.preferredHeight: 33
Layout.preferredWidth: Math.max(Math.min(actionField.contentWidth + Appearance.padding.normal * 3, 200), 50)
color: DynamicColors.tPalette.m3surface
radius: Appearance.rounding.small
CustomTextField {
id: actionField
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: root.modelData.activeAction ?? ""
onEditingFinished: {
root.fieldEdited("activeAction", text);
}
}
}
}
RowLayout {
id: addButtonRow
anchors.left: parent.left
anchors.right: parent.right
visible: root.modelData.activeAction === undefined
IconButton {
id: button
Layout.alignment: Qt.AlignLeft
font.pointSize: Appearance.font.size.large
icon: "add"
onClicked: root.addActiveActionRequested()
}
CustomText {
Layout.alignment: Qt.AlignLeft
text: qsTr("Add active action")
}
}
}
}
}
}
@@ -1,163 +0,0 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Components
Item {
id: root
required property string name
required property var object
property alias row: row
required property string setting
property bool shouldBeActive: true
required property list<var> stringList
signal optionSet
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: shouldBeActive ? row.height : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
RowLayout {
id: row
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
CustomText {
id: text
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
CustomClippingRect {
id: fontArea
Layout.preferredHeight: 42 * 6 + Appearance.padding.normal * 2 + Appearance.spacing.small * 5
Layout.preferredWidth: 500
color: DynamicColors.tPalette.m3surfaceContainer
radius: (21 + Appearance.padding.normal) * Appearance.rounding.scale
CustomRect {
id: searchBox
anchors.left: parent.left
anchors.margins: Appearance.padding.normal
anchors.right: parent.right
anchors.top: parent.top
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: 42
radius: Appearance.rounding.full
MaterialIcon {
id: searchIcon
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.large
anchors.top: parent.top
font.pointSize: Appearance.font.size.large
text: "search"
verticalAlignment: Text.AlignVCenter
}
CustomTextField {
id: fontSearch
anchors.left: searchIcon.right
anchors.leftMargin: Appearance.spacing.small
anchors.right: parent.right
anchors.rightMargin: Appearance.spacing.normal
anchors.verticalCenter: parent.verticalCenter
placeholderText: "Search..."
}
}
CustomClippingRect {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: Appearance.padding.normal
anchors.right: parent.right
anchors.top: searchBox.bottom
bottomLeftRadius: 21
bottomRightRadius: 21
CustomListView {
anchors.fill: parent
clip: true
spacing: Appearance.spacing.small
delegate: CustomRect {
id: fontDelegate
required property string modelData
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: 42
radius: Appearance.rounding.smallest
CustomText {
anchors.fill: parent
anchors.leftMargin: Appearance.padding.normal
text: modelData
verticalAlignment: Text.AlignVCenter
}
MaterialIcon {
anchors.fill: parent
anchors.rightMargin: Appearance.padding.normal
color: DynamicColors.palette.m3primary
font.pointSize: Appearance.font.size.large
horizontalAlignment: Text.AlignRight
text: "check_circle"
verticalAlignment: Text.AlignVCenter
visible: root.object[root.setting] === fontDelegate.modelData
}
StateLayer {
onClicked: {
root.object[root.setting] = fontDelegate.modelData;
root.optionSet();
Config.save();
}
}
}
model: ScriptModel {
values: {
const values = root.stringList;
const search = fontSearch.text;
var regex = new RegExp(search, "i");
return values.filter(n => regex.test(n));
}
}
}
}
}
}
}
@@ -1,70 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Helpers
Item {
id: root
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
required property string name
property bool shouldBeActive: true
required property string value
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Rectangle {
anchors.fill: parent
anchors.margins: -Appearance.padding.smaller
color: DynamicColors.palette.m3primaryContainer
opacity: root.highlighted ? 0.5 : 0
radius: Appearance.rounding.small
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.normal
}
}
}
RowLayout {
id: row
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
CustomText {
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
CustomText {
color: DynamicColors.palette.m3onSurfaceVariant
font.family: Appearance.font.family.mono
font.pointSize: Appearance.font.size.normal
text: root.value
}
}
}
@@ -1,81 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Helpers
Item {
id: root
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
property real max: Infinity
property real min: -Infinity
required property string name
required property var object
required property string setting
property bool shouldBeActive: true
property real step: 1
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Rectangle {
anchors.fill: parent
anchors.margins: -Appearance.padding.smaller
color: DynamicColors.palette.m3primaryContainer
opacity: root.highlighted ? 0.5 : 0
radius: Appearance.rounding.small
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.normal
}
}
}
RowLayout {
id: row
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
CustomText {
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
CustomSpinBox {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
max: root.max
min: root.min
step: root.step
value: Number(root.object[root.setting] ?? 0)
onValueModified: function (value) {
root.object[root.setting] = value;
Config.save();
}
}
}
}
@@ -1,180 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Helpers
Item {
id: root
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
required property string name
required property var object
required property list<string> settings
property bool shouldBeActive: true
function commitChoice(choice: int, setting: string): void {
root.object[setting] = choice;
Config.save();
}
function formattedValue(setting: string): string {
const value = root.object[setting];
if (value === null || value === undefined)
return "";
return String(value);
}
function hourToAmPm(hour) {
var h = Number(hour) % 24;
var d = new Date(2000, 0, 1, h, 0, 0);
return Qt.formatTime(d, "h AP");
}
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Rectangle {
anchors.fill: parent
anchors.margins: -Appearance.padding.smaller
color: DynamicColors.palette.m3primaryContainer
opacity: root.highlighted ? 0.5 : 0
radius: Appearance.rounding.small
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.normal
}
}
}
RowLayout {
id: row
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
ColumnLayout {
Layout.fillHeight: true
Layout.fillWidth: true
CustomText {
id: text
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
CustomText {
Layout.alignment: Qt.AlignLeft
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.normal
text: qsTr("Dark mode will turn on at %1, and turn off at %2.").arg(root.hourToAmPm(root.object[root.settings[0]])).arg(root.hourToAmPm(root.object[root.settings[1]]))
}
}
ColumnLayout {
id: optionLayout
Layout.fillHeight: true
Layout.preferredWidth: 100
RowLayout {
Layout.preferredWidth: optionLayout.width
CustomText {
Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter
Layout.fillWidth: true
text: qsTr("Enabled: ")
}
CustomSwitch {
id: enabledSwitch
Layout.alignment: Qt.AlignRight | Qt.AlignHCenter
checked: root.object[root.settings[2]]
onToggled: {
root.object[root.settings[2]] = checked;
Config.save();
}
}
}
RowLayout {
Layout.preferredWidth: optionLayout.width
z: setting2.expanded ? -1 : 1
CustomText {
Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter
Layout.fillWidth: true
text: qsTr("Start: ")
}
SpinnerButton {
id: setting1
Layout.alignment: Qt.AlignRight | Qt.AlignHCenter
Layout.preferredHeight: Appearance.font.size.large + Appearance.padding.smaller * 2
Layout.preferredWidth: height * 2
currentIndex: root.object[root.settings[0]]
enabled: enabledSwitch.checked
text: root.formattedValue(root.settings[0])
menu.onItemSelected: item => {
root.commitChoice(item, root.settings[0]);
}
}
}
RowLayout {
Layout.preferredWidth: optionLayout.width
z: setting1.expanded ? -1 : 1
CustomText {
Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter
Layout.fillWidth: true
text: qsTr("End: ")
}
SpinnerButton {
id: setting2
Layout.alignment: Qt.AlignRight | Qt.AlignHCenter
Layout.preferredHeight: Appearance.font.size.large + Appearance.padding.smaller * 2
Layout.preferredWidth: height * 2
currentIndex: root.object[root.settings[1]]
enabled: enabledSwitch.checked
text: root.formattedValue(root.settings[1])
menu.onItemSelected: item => {
root.commitChoice(item, root.settings[1]);
}
}
}
}
}
}
@@ -1,78 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Helpers
Item {
id: root
property string addLabel: qsTr("Add entry")
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
required property string name
required property var object
required property string setting
property bool shouldBeActive: true
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: shouldBeActive ? layout.implicitHeight : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Rectangle {
anchors.fill: parent
anchors.margins: -Appearance.padding.smaller
color: DynamicColors.palette.m3primaryContainer
opacity: root.highlighted ? 0.5 : 0
radius: Appearance.rounding.small
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.normal
}
}
}
ColumnLayout {
id: layout
anchors.left: parent.left
anchors.right: parent.right
spacing: Appearance.spacing.small
CustomText {
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
StringListEditor {
Layout.fillWidth: true
addLabel: root.addLabel
anchors.left: undefined
anchors.right: undefined
anchors.verticalCenter: undefined
values: [...(root.object[root.setting] ?? [])]
onListEdited: function (values) {
root.object[root.setting] = values;
Config.save();
}
}
}
}
@@ -1,79 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Helpers
Item {
id: root
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
required property string name
required property var object
required property string setting
property bool shouldBeActive: true
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Rectangle {
anchors.fill: parent
anchors.margins: -Appearance.padding.smaller
color: DynamicColors.palette.m3primaryContainer
opacity: root.highlighted ? 0.5 : 0
radius: Appearance.rounding.small
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.normal
}
}
}
RowLayout {
id: row
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
CustomText {
id: text
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
CustomSwitch {
id: cswitch
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
checked: root.object[root.setting]
onToggled: {
root.object[root.setting] = checked;
Config.save();
}
}
}
}
@@ -1,25 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
CustomRect {
id: root
required property string name
property bool shouldBeActive: true
implicitHeight: 60
implicitWidth: 200
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
CustomText {
anchors.fill: parent
font.bold: true
font.pointSize: Appearance.font.size.large * 2
text: root.name
verticalAlignment: Text.AlignVCenter
}
}
@@ -1,83 +0,0 @@
import Quickshell
import QtQuick
import QtQuick.Layouts
import qs.Paths
import qs.Components
import qs.Config
import qs.Helpers
Item {
id: root
property alias button: iButton
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
required property string name
property bool shouldBeActive: true
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Rectangle {
anchors.fill: parent
anchors.margins: -Appearance.padding.smaller
color: DynamicColors.palette.m3primaryContainer
opacity: root.highlighted ? 0.5 : 0
radius: Appearance.rounding.small
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.normal
}
}
}
RowLayout {
id: row
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
CustomText {
id: text
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
IconButton {
id: iButton
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
icon: "download"
onClicked: {
const lockBg = `${Paths.state}/lockscreen_bg.png`;
const scheme = `${Paths.state}/scheme.json`;
const face = `${Paths.home}/.face`;
const destination = "/etc/zshell-greeter/images";
Quickshell.execDetached(["pkexec", "sh", "-c", `mkdir -p ${destination}; cp ${lockBg} ${destination}; cp ${scheme} /etc/zshell-greeter; cp ${face} ${destination}`]);
}
}
}
}
@@ -1,76 +0,0 @@
import QtQuick
import QtQuick.Controls
import qs.Components
import qs.Config
import qs.Helpers
Item {
id: root
default property alias contentData: clayout.data
function scrollToSectionAndHighlight(sectionId: string, settingName: string): bool {
for (let i = 0; i < clayout.children.length; i++) {
const section = clayout.children[i];
if (section.sectionId === sectionId) {
const targetY = section.y - Appearance.padding.normal;
flickable.contentY = Math.max(0, Math.min(targetY, flickable.contentHeight - flickable.height));
SettingsHighlight.highlight(settingName);
return true;
}
}
return false;
}
CustomFlickable {
id: flickable
anchors.fill: parent
// for future:
// anchors.leftMargin: Appearance.padding.extraLarge
// anchors.rightMargin: Appearance.padding.extraLarge
clip: true
contentHeight: clayout.implicitHeight + clayout.anchors.margins * 2
CustomScrollBar.vertical: CustomScrollBar {
flickable: flickable
}
TapHandler {
acceptedButtons: Qt.LeftButton
onTapped: function (eventPoint) {
const menu = SettingsDropdowns.activeMenu;
if (!menu)
return;
const p = eventPoint.scenePosition;
if (SettingsDropdowns.hit(SettingsDropdowns.activeTrigger, p))
return;
if (SettingsDropdowns.hit(menu, p))
return;
SettingsDropdowns.closeActive();
}
}
Column {
id: clayout
anchors.left: parent.left
anchors.margins: Appearance.padding.extraSmall
anchors.right: parent.right
anchors.top: parent.top
spacing: Appearance.spacing.small
// move: Transition {
// Anim {
// properties: "y"
// }
// }
}
}
}
@@ -1,48 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
CustomRect {
id: root
default property alias contentData: layout.data
property real contentPadding: Appearance.padding.large
property string sectionId: ""
anchors.left: parent.left
anchors.right: parent.right
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: layout.height + contentPadding * 2
radius: Appearance.rounding.normal - Appearance.padding.extraSmall
Behavior on implicitHeight {
Anim {
}
}
Behavior on y {
Anim {
}
}
Column {
id: layout
anchors.left: parent.left
anchors.margins: root.contentPadding
anchors.right: parent.right
anchors.top: parent.top
// anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.normal
Behavior on height {
Anim {
}
}
move: Transition {
Anim {
properties: "y"
}
}
}
}
@@ -1,63 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Helpers
import qs.Config
CustomRect {
id: root
property alias currentIndex: menu.currentIndex
property bool enabled
property alias expanded: menu.expanded
property alias label: label
property alias menu: menu
property bool shouldBeActive: true
property alias text: label.text
color: enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
opacity: shouldBeActive ? 1 : 0
radius: Appearance.rounding.full
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
z: expanded ? 100 : 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
CustomText {
id: label
anchors.centerIn: parent
color: root.enabled ? DynamicColors.palette.m3onPrimary : DynamicColors.layer(DynamicColors.palette.m3onSurface, 2)
font.pointSize: Appearance.font.size.large
}
StateLayer {
visible: root.enabled
onClicked: {
SettingsDropdowns.toggle(menu, root);
}
}
PathViewMenu {
id: menu
anchors.centerIn: parent
from: 1
implicitWidth: root.width
itemHeight: root.height
to: 24
}
}
@@ -1,126 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
ColumnLayout {
id: root
property string addLabel: qsTr("Add entry")
property bool shouldBeActive: true
property var values: []
signal listEdited(var values)
function addValue() {
const list = [...root.values];
list.push("");
root.listEdited(list);
}
function removeValue(index) {
const list = [...root.values];
list.splice(index, 1);
root.listEdited(list);
}
function updateValue(index, value) {
const list = [...root.values];
list[index] = value;
root.listEdited(list);
}
anchors.left: parent.left
anchors.right: parent.right
height: shouldBeActive ? implicitHeight : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
spacing: Appearance.spacing.smaller
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Repeater {
model: [...root.values]
Item {
required property int index
required property var modelData
Layout.fillWidth: true
Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2
CustomRect {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: -(Appearance.spacing.smaller / 2)
color: DynamicColors.tPalette.m3outlineVariant
implicitHeight: 1
visible: index !== 0
}
RowLayout {
id: row
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
CustomRect {
Layout.fillWidth: true
Layout.preferredHeight: 33
color: DynamicColors.tPalette.m3surfaceContainerHigh
radius: Appearance.rounding.full
CustomTextField {
anchors.fill: parent
anchors.leftMargin: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.normal
text: String(modelData ?? "")
onEditingFinished: root.updateValue(index, text)
}
}
IconButton {
font.pointSize: Appearance.font.size.large
icon: "delete"
type: IconButton.Tonal
onClicked: root.removeValue(index)
}
}
}
}
RowLayout {
Layout.fillWidth: true
IconButton {
font.pointSize: Appearance.font.size.large
icon: "add"
onClicked: root.addValue()
}
CustomText {
Layout.fillWidth: true
text: root.addLabel
}
}
}
-661
View File
@@ -1,661 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Components
import qs.Helpers
Item {
id: root
readonly property string endTime: {
var d = new Date(0, 0, 0, 0, 0, 0, 0);
d.setMinutes(object[settings[2]]);
return Qt.formatTime(d, "hh:mm AP");
}
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
required property string name
required property var object
required property list<string> settings
property bool shouldBeActive: true
readonly property string startTime: {
var d = new Date(0, 0, 0, 0, 0, 0, 0);
d.setMinutes(object[settings[1]]);
return Qt.formatTime(d, "hh:mm AP");
}
function commitChoice(choice: int, setting: string): void {
root.object[setting] = choice;
Config.save();
ModeScheduler.checkStartup();
}
function convertHour(timeValue: int): int {
return Math.floor(timeValue / 60);
}
function convertMinute(timeValue: int): int {
return timeValue % 60;
}
function convertToMinutes(hour: int, minute: int): int {
return hour * 60 + minute;
}
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Rectangle {
anchors.fill: parent
anchors.margins: -Appearance.padding.smaller
color: DynamicColors.palette.m3primaryContainer
opacity: root.highlighted ? 0.5 : 0
radius: Appearance.rounding.small
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.normal
}
}
}
RowLayout {
id: row
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
ColumnLayout {
Layout.fillHeight: true
Layout.fillWidth: true
CustomText {
id: text
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
CustomText {
Layout.alignment: Qt.AlignLeft
Layout.preferredWidth: Math.min(contentWidth, optionLayout.x - Appearance.spacing.normal)
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.normal
text: qsTr("Dark mode will turn on at %1, and turn off at %2.").arg(root.startTime).arg(root.endTime)
wrapMode: Text.WordWrap
}
}
ColumnLayout {
id: optionLayout
Layout.fillHeight: true
Layout.fillWidth: true
RowLayout {
CustomText {
Layout.preferredWidth: spacer.x + spacer.width
text: qsTr("Start")
}
CustomText {
Layout.preferredWidth: endMinuteRect.width + endHourRect.width
text: qsTr("End")
}
CustomText {
Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter
text: qsTr("Enabled: ")
}
CustomSwitch {
id: enabledSwitch
Layout.alignment: Qt.AlignRight | Qt.AlignHCenter
checked: root.object[root.settings[0]]
onToggled: {
root.object[root.settings[0]] = checked;
Config.save();
ModeScheduler.checkStartup();
}
}
}
RowLayout {
Layout.fillHeight: true
CustomRect {
id: startHourRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: startHourField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Appearance.rounding.small
CustomRect {
anchors.fill: parent
border.color: startHourField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest
border.width: startHourField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {
}
}
}
CustomTextField {
id: startHourField
function setConfigText(setting: string): string {
var val = root.convertHour(root.object[setting]);
if (val === 0) {
return "00";
}
return String(val);
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
color: focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface
cursorHeight: height - Appearance.padding.normal * 2
font.family: "Roboto"
font.letterSpacing: -0.25
font.pixelSize: 56
font.weight: 400
horizontalAlignment: TextInput.AlignHCenter
text: setConfigText(root.settings[1])
verticalAlignment: TextInput.AlignVCenter
Keys.onPressed: event => {
if (event.key === Qt.Key_Backspace) {
event.accepted = true;
if (startHourField.text.length >= 2) {
startHourField.text = "0" + startHourField.text[0];
} else if (startHourField.text.length === 1) {
startHourField.text = "0";
}
return;
} else if (event.key === Qt.Key_Escape) {
event.accepted = true;
startHourField.text = setConfigText(root.settings[1]);
startHourField.focus = false;
} else if (event.key === Qt.Key_Return) {
startHourField.focus = false;
return;
} else if (event.key === Qt.Key_Tab) {
startMinuteField.focus = true;
} else if (event.key === Qt.Key_Backtab) {
endMinuteField.focus = true;
}
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
event.accepted = true;
var digit = event.text;
var textLen = startHourField.text.length;
if (textLen >= 2 && startHourField.text[0] !== '0') {
return;
}
var val = 0;
if (textLen === 0) {
val = parseInt(digit);
} else if (textLen === 1) {
val = parseInt(startHourField.text + digit);
} else {
val = parseInt(startHourField.text[1] + digit);
}
val = Math.max(0, Math.min(23, val));
if (textLen >= 2 && val < 10) {
startHourField.text = "0" + val;
} else {
startHourField.text = val.toString();
}
}
event.accepted = true;
}
onCursorPositionChanged: cursorPosition = 2
onEditingFinished: {
root.commitChoice(root.convertToMinutes(parseInt(startHourField.text), parseInt(startMinuteField.text)), root.settings[1]);
}
onTextEdited: {
if (startHourField.text === "")
return;
var val = parseInt(startHourField.text);
if (isNaN(val))
return;
val = Math.max(0, Math.min(23, val));
var newText = val.toString();
if (newText !== startHourField.text)
startHourField.text = newText;
}
}
}
CustomText {
id: startSeparator
font.pointSize: Appearance.font.size.extraLarge
text: ":"
}
CustomRect {
id: startMinuteRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: startMinuteField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Appearance.rounding.small
CustomRect {
anchors.fill: parent
border.color: startMinuteField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest
border.width: startMinuteField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {
}
}
}
CustomTextField {
id: startMinuteField
function setConfigText(setting: string): string {
var val = root.convertMinute(root.object[setting]);
if (val === 0) {
return "00";
}
return String(val);
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
color: focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface
cursorHeight: height - Appearance.padding.normal * 2
font.family: "Roboto"
font.letterSpacing: -0.25
font.pixelSize: 56
font.weight: 400
horizontalAlignment: TextInput.AlignHCenter
text: setConfigText(root.settings[1])
verticalAlignment: TextInput.AlignVCenter
Keys.onPressed: event => {
if (event.key === Qt.Key_Backspace) {
event.accepted = true;
if (startMinuteField.text.length >= 2) {
startMinuteField.text = "0" + startMinuteField.text[0];
} else if (startMinuteField.text.length === 1) {
startMinuteField.text = "0";
}
return;
} else if (event.key === Qt.Key_Escape) {
event.accepted = true;
startMinuteField.text = setConfigText(root.settings[1]);
startMinuteField.focus = false;
} else if (event.key === Qt.Key_Return) {
startMinuteField.focus = false;
return;
} else if (event.key === Qt.Key_Tab) {
endHourField.focus = true;
} else if (event.key === Qt.Key_Backtab) {
startHourField.focus = true;
}
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
event.accepted = true;
var digit = event.text;
var textLen = startMinuteField.text.length;
if (textLen >= 2 && startMinuteField.text[0] !== '0') {
return;
}
var val = 0;
if (textLen === 0) {
val = parseInt(digit);
} else if (textLen === 1) {
val = parseInt(startMinuteField.text + digit);
} else {
val = parseInt(startMinuteField.text[1] + digit);
}
val = Math.max(0, Math.min(59, val));
if (textLen >= 2 && val < 10) {
startMinuteField.text = "0" + val;
} else {
startMinuteField.text = val.toString();
}
}
event.accepted = true;
}
onCursorPositionChanged: cursorPosition = 2
onEditingFinished: {
root.commitChoice(root.convertToMinutes(parseInt(startHourField.text), parseInt(startMinuteField.text)), root.settings[1]);
}
onTextEdited: {
if (startMinuteField.text === "")
return;
var val = parseInt(startMinuteField.text);
if (isNaN(val))
return;
val = Math.max(0, Math.min(23, val));
var newText = val.toString();
if (newText !== startMinuteField.text)
startMinuteField.text = newText;
}
}
}
Item {
id: spacer
Layout.preferredWidth: Appearance.spacing.large
}
CustomRect {
id: endHourRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: endHourField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Appearance.rounding.small
CustomRect {
anchors.fill: parent
border.color: endHourField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest
border.width: endHourField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {
}
}
}
CustomTextField {
id: endHourField
function setConfigText(setting: string): string {
var val = root.convertHour(root.object[setting]);
if (val === 0) {
return "00";
}
return String(val);
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
color: focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface
cursorHeight: height - Appearance.padding.normal * 2
font.family: "Roboto"
font.letterSpacing: -0.25
font.pixelSize: 56
font.weight: 400
horizontalAlignment: TextInput.AlignHCenter
text: setConfigText(root.settings[2])
verticalAlignment: TextInput.AlignVCenter
Keys.onPressed: event => {
if (event.key === Qt.Key_Backspace) {
event.accepted = true;
if (endHourField.text.length >= 2) {
endHourField.text = "0" + endHourField.text[0];
} else if (endHourField.text.length === 1) {
endHourField.text = "0";
}
return;
} else if (event.key === Qt.Key_Escape) {
event.accepted = true;
endHourField.text = setConfigText(root.settings[2]);
endHourField.focus = false;
} else if (event.key === Qt.Key_Return) {
endHourField.focus = false;
return;
} else if (event.key === Qt.Key_Tab) {
endMinuteField.focus = true;
} else if (event.key === Qt.Key_Backtab) {
startMinuteField.focus = true;
}
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
event.accepted = true;
var digit = event.text;
var textLen = endHourField.text.length;
if (textLen >= 2 && endHourField.text[0] !== '0') {
return;
}
var val = 0;
if (textLen === 0) {
val = parseInt(digit);
} else if (textLen === 1) {
val = parseInt(endHourField.text + digit);
} else {
val = parseInt(endHourField.text[1] + digit);
}
val = Math.max(0, Math.min(23, val));
if (textLen >= 2 && val < 10) {
endHourField.text = "0" + val;
} else {
endHourField.text = val.toString();
}
}
event.accepted = true;
}
onCursorPositionChanged: cursorPosition = 2
onEditingFinished: {
root.commitChoice(root.convertToMinutes(parseInt(endHourField.text), parseInt(endMinuteField.text)), root.settings[2]);
}
onTextEdited: {
if (endHourField.text === "")
return;
var val = parseInt(endHourField.text);
if (isNaN(val))
return;
val = Math.max(0, Math.min(23, val));
var newText = val.toString();
if (newText !== endHourField.text)
endHourField.text = newText;
}
}
}
CustomText {
id: endSeparator
font.pointSize: Appearance.font.size.extraLarge
text: ":"
}
CustomRect {
id: endMinuteRect
Layout.preferredHeight: 72
Layout.preferredWidth: 96
color: endMinuteField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest
implicitHeight: 72
implicitWidth: 96
radius: Appearance.rounding.small
CustomRect {
anchors.fill: parent
border.color: endMinuteField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest
border.width: endMinuteField.focus ? 2 : 0
radius: parent.radius - border.width
Behavior on border.width {
Anim {
}
}
}
CustomTextField {
id: endMinuteField
function setConfigText(setting: string): string {
var val = root.convertMinute(root.object[setting]);
if (val === 0) {
return "00";
}
return String(val);
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
clip: true
color: focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface
cursorHeight: height - Appearance.padding.normal * 2
font.family: "Roboto"
font.letterSpacing: -0.25
font.pixelSize: 56
font.weight: 400
horizontalAlignment: TextInput.AlignHCenter
text: setConfigText(root.settings[2])
verticalAlignment: TextInput.AlignVCenter
Keys.onPressed: event => {
if (event.key === Qt.Key_Backspace) {
event.accepted = true;
if (endMinuteField.text.length >= 2) {
endMinuteField.text = "0" + endMinuteField.text[0];
} else if (endMinuteField.text.length === 1) {
endMinuteField.text = "0";
}
return;
} else if (event.key === Qt.Key_Escape) {
event.accepted = true;
endMinuteField.text = setConfigText(root.settings[2]);
endMinuteField.focus = false;
} else if (event.key === Qt.Key_Return) {
endMinuteField.focus = false;
return;
} else if (event.key === Qt.Key_Tab) {
startHourField.focus = true;
} else if (event.key === Qt.Key_Backtab) {
endHourField.focus = true;
}
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
event.accepted = true;
var digit = event.text;
var textLen = endMinuteField.text.length;
if (textLen >= 2 && endMinuteField.text[0] !== '0') {
return;
}
var val = 0;
if (textLen === 0) {
val = parseInt(digit);
} else if (textLen === 1) {
val = parseInt(endMinuteField.text + digit);
} else {
val = parseInt(endMinuteField.text[1] + digit);
}
val = Math.max(0, Math.min(59, val));
if (textLen >= 2 && val < 10) {
endMinuteField.text = "0" + val;
} else {
endMinuteField.text = val.toString();
}
}
event.accepted = true;
}
onCursorPositionChanged: cursorPosition = 2
onEditingFinished: {
root.commitChoice(root.convertToMinutes(parseInt(endHourField.text), parseInt(endMinuteField.text)), root.settings[2]);
}
onTextEdited: {
if (endMinuteField.text === "")
return;
var val = parseInt(endMinuteField.text);
if (isNaN(val))
return;
val = Math.max(0, Math.min(23, val));
var newText = val.toString();
if (newText !== endMinuteField.text)
endMinuteField.text = newText;
}
}
}
}
RowLayout {
Layout.fillWidth: true
CustomText {
id: startHour
Layout.preferredWidth: startSeparator.x + startSeparator.width
text: qsTr("Hour")
}
CustomText {
id: startMinute
Layout.preferredWidth: spacer.x + spacer.width - x
text: qsTr("Minute")
}
CustomText {
Layout.preferredWidth: endSeparator.x + endSeparator.width - x
text: qsTr("Hour")
}
CustomText {
text: qsTr("Minute")
}
}
}
}
}
-12
View File
@@ -1,12 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
CustomRect {
id: root
Layout.fillHeight: true
Layout.preferredWidth: 1
color: DynamicColors.tPalette.m3outlineVariant
}
@@ -1,314 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import ZShell.Internal
import qs.Config
import qs.Components
import qs.Helpers
Item {
id: wrapper
property bool changesMade: false
property bool shouldBeActive: true
signal requestCrop
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: shouldBeActive ? 400 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
IconButton {
anchors.margins: Appearance.padding.normal
anchors.right: parent.right
anchors.top: parent.top
icon: "check"
opacity: wrapper.changesMade ? 1 : 0
scale: wrapper.changesMade ? 1 : 0
z: 2
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
onClicked: {
wrapper.requestCrop();
wrapper.changesMade = false;
}
}
RowLayout {
id: root
anchors.fill: parent
spacing: Appearance.spacing.normal
Repeater {
model: ScriptModel {
values: [...Quickshell.screens].sort((a, b) => {
return a.x - b.x;
})
}
Item {
id: delegate
required property ShellScreen modelData
function applyCrop(): void {
if (!cropRectLoader.item)
return;
const cropRect = cropRectLoader.item;
// We need to calculate the exact percentage coordinates that map perfectly
// to our C++ backend, regardless of current display scaling
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 finalRect = Qt.rect(cropXPercent, cropYPercent, cropWidthPercent, cropHeightPercent);
// We just pass the percentages directly to the backend
Wallpapers.setCrop(delegate.modelData.name, finalRect, cropRect.zoom);
}
function zoomClipRect(zoom: real): void {
if (!cropRectLoader.item)
return;
const cropRect = cropRectLoader.item;
let oldCenterX = cropRect.x + cropRect.width * 0.5;
let oldCenterY = cropRect.y + cropRect.height * 0.5;
cropRect.zoom = zoom;
cropRect.x = oldCenterX - cropRect.width * 0.5;
cropRect.y = oldCenterY - cropRect.height * 0.5;
cropRect.clampToBounds();
}
Layout.fillHeight: true
Layout.fillWidth: true
Connections {
function onRequestCrop(): void {
delegate.applyCrop();
}
target: wrapper
}
RowLayout {
id: sliderLayout
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: 30
CustomSlider {
id: zoomSlider
Layout.fillWidth: true
Layout.leftMargin: Appearance.padding.normal
Layout.preferredHeight: Appearance.padding.larger * 3
Layout.rightMargin: Appearance.padding.normal
from: 1.0
implicitHeight: Appearance.padding.larger * 3
insetIcon: "crop"
to: 5.0
value: cropRectLoader.item ? cropRectLoader.item.zoom : 1.0
onInteraction: value => {
delegate.zoomClipRect(1 + (value * 4));
wrapper.changesMade = true;
}
}
}
CachingImage {
id: scaledImg
property var displayData
property real monitorScale: 1.0
anchors.bottom: sliderLayout.top
anchors.bottomMargin: Appearance.spacing.normal
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
asynchronous: true
fillMode: Image.PreserveAspectFit
retainWhileLoading: true
source: Wallpapers.current
sourceSize.height: parent.height
sourceSize.width: parent.width
onPaintedWidthChanged: {
if (paintedWidth > 0 && cropRectLoader.item) {
cropRectLoader.item.restoreFromData();
}
}
onSourceChanged: {
if (cropRectLoader.item) {
cropRectLoader.item.restoreFromData();
}
}
onStatusChanged: {
if (scaledImg.status == Image.Ready && cropRectLoader.item) {
cropRectLoader.item.restoreFromData();
}
}
CustomText {
id: monitorId
anchors.centerIn: parent
color: Qt.alpha(DynamicColors.palette.m3surface, 0.85)
font.pointSize: Appearance.font.size.large * 4
style: Text.Outline
styleColor: DynamicColors.palette.m3onSurface
text: delegate.modelData.name
}
Loader {
id: cropRectLoader
active: scaledImg.paintedWidth > 0
sourceComponent: Component {
CustomRect {
id: cropRect
property real aspectRatio: delegate.modelData.width / delegate.modelData.height
readonly property real baseHeight: baseWidth / aspectRatio
readonly property real baseWidth: {
let fittedHeight = scaledImg.paintedHeight;
let fittedWidth = fittedHeight * aspectRatio;
if (fittedWidth > scaledImg.paintedWidth) {
fittedWidth = scaledImg.paintedWidth;
fittedHeight = fittedWidth / aspectRatio;
}
return fittedWidth;
}
readonly property real imageX: (scaledImg.width - scaledImg.paintedWidth) / 2
readonly property real imageY: (scaledImg.height - scaledImg.paintedHeight) / 2
property real imgAspectRatio: scaledImg.paintedWidth / scaledImg.paintedHeight
property real zoom: 1.0
function centerInImage() {
x = imageX + (scaledImg.paintedWidth - width) / 2;
y = imageY + (scaledImg.paintedHeight - height) / 2;
}
function clampToBounds() {
x = Math.max(imageX, Math.min(x, imageX + scaledImg.paintedWidth - width));
y = Math.max(imageY, Math.min(y, imageY + scaledImg.paintedHeight - height));
}
function restoreFromData() {
let data = Wallpapers.getCrop(delegate.modelData.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: DynamicColors.palette.m3primary
border.width: 2
height: baseHeight / zoom
opacity: 1
width: baseWidth / zoom
Behavior on opacity {
Anim {
}
}
Component.onCompleted: {
restoreFromData();
}
onHeightChanged: clampToBounds()
onWidthChanged: clampToBounds()
}
}
}
MouseArea {
id: mouse
function updateCrop(mouseX, mouseY) {
if (!cropRectLoader.item)
return;
const cropRect = cropRectLoader.item;
let nx = mouseX - cropRect.width * 0.5;
let ny = mouseY - cropRect.height * 0.5;
nx = Math.max(cropRect.imageX, Math.min(nx, cropRect.imageX + scaledImg.paintedWidth - cropRect.width));
ny = Math.max(cropRect.imageY, Math.min(ny, cropRect.imageY + scaledImg.paintedHeight - cropRect.height));
cropRect.x = nx;
cropRect.y = ny;
}
anchors.fill: parent
hoverEnabled: true
preventStealing: true
onPositionChanged: mouse => {
if (pressed) {
updateCrop(mouse.x, mouse.y);
wrapper.changesMade = true;
}
}
onPressed: mouse => {
updateCrop(mouse.x, mouse.y);
wrapper.changesMade = true;
}
onReleased: {
wrapper.changesMade = true;
}
}
}
}
}
}
}
-158
View File
@@ -1,158 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import ZShell.Models
import qs.Components
import qs.Helpers
import qs.Config
GridView {
id: root
readonly property int columnsCount: Math.max(1, Math.floor(width / minCellWidth))
readonly property int minCellWidth: 200 + Appearance.spacing.normal
property bool shouldBeActive: true
anchors.left: parent.left
anchors.right: parent.right
cellHeight: 140 + Appearance.spacing.normal
cellWidth: width / columnsCount
clip: true
implicitHeight: shouldBeActive ? contentHeight : 0
interactive: false
model: Wallpapers.list
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
delegate: Item {
required property int index
readonly property bool isCurrent: modelData && modelData.path === Wallpapers.actualCurrent
readonly property real itemMargin: Appearance.spacing.normal
readonly property real itemRadius: Appearance.rounding.small
required property var modelData
height: root.cellHeight
width: root.cellWidth
CustomClippingRect {
id: image
anchors.bottomMargin: itemMargin
anchors.fill: parent
anchors.leftMargin: itemMargin
anchors.rightMargin: itemMargin
anchors.topMargin: itemMargin
antialiasing: true
color: DynamicColors.tPalette.m3surfaceContainer
radius: itemRadius
CachingImage {
id: cachingImage
anchors.fill: parent
antialiasing: true
cache: true
fillMode: Image.PreserveAspectCrop
opacity: status === Image.Ready ? 1 : 0
path: modelData.path
smooth: true
sourceSize: Qt.size(width, height)
visible: opacity > 0
Behavior on opacity {
NumberAnimation {
duration: 1000
easing.type: Easing.OutQuad
}
}
}
Image {
id: fallbackImage
anchors.fill: parent
antialiasing: true
asynchronous: true
cache: true
fillMode: Image.PreserveAspectCrop
opacity: status === Image.Ready && cachingImage.status !== Image.Ready ? 1 : 0
smooth: true
source: fallbackTimer.triggered && cachingImage.status !== Image.Ready ? modelData.path : ""
sourceSize: Qt.size(width, height)
visible: opacity > 0
Behavior on opacity {
NumberAnimation {
duration: 1000
easing.type: Easing.OutQuad
}
}
}
Rectangle {
anchors.fill: parent
antialiasing: true
border.color: DynamicColors.palette.m3primary
border.width: isCurrent ? 2 : 0
color: "transparent"
radius: itemRadius + 2
smooth: true
Behavior on border.width {
NumberAnimation {
duration: 150
easing.type: Easing.OutQuad
}
}
MaterialIcon {
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.top: parent.top
color: DynamicColors.palette.m3primary
font.pointSize: Appearance.font.size.large
text: "check_circle"
visible: isCurrent
}
}
Timer {
id: fallbackTimer
property bool triggered: false
interval: 800
running: cachingImage.status === Image.Loading || cachingImage.status === Image.Null
onTriggered: triggered = true
}
}
StateLayer {
anchors.bottomMargin: itemMargin
anchors.fill: parent
anchors.leftMargin: itemMargin
anchors.rightMargin: itemMargin
anchors.topMargin: itemMargin
radius: itemRadius
onClicked: {
Wallpapers.setWallpaper(modelData.path);
}
}
}
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
}
+46
View File
@@ -0,0 +1,46 @@
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Modules.Settings.NavPane
import qs.Config
ColumnLayout {
id: root
required property SettingsState sState
spacing: Appearance.spacing.large
SearchBar {
id: searchField
Layout.fillWidth: true
bg.border.color: DynamicColors.palette.m3outlineVariant
bg.color: DynamicColors.tPalette.m3surfaceContainerLowest
clearIcon.font.pointSize: Appearance.font.size.large
clearIcon.padding: Appearance.padding.extraSmall
font.pointSize: Appearance.font.size.normal
placeholderText: qsTr("Search settings")
searchIcon.anchors.leftMargin: Appearance.padding.largeIncreased
searchIcon.font.pointSize: Appearance.font.size.large
Behavior on bg.border.color {
CAnim {
}
}
Binding {
property: "searchOpen"
target: root.sState
value: searchField.text.length > 0
}
}
NavLocations {
Layout.bottomMargin: -bottomMargin
Layout.fillHeight: true
Layout.fillWidth: true
Layout.topMargin: -topMargin
sState: root.sState
}
}
+130
View File
@@ -0,0 +1,130 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Modules.Settings
VerticalFadeFlickable {
id: root
required property SettingsState sState
bottomMargin: Appearance.padding.large
contentHeight: content.implicitHeight
fadeAmount: 0.1
topMargin: Appearance.padding.large
TapHandler {
onTapped: root.focus = true
}
ColumnLayout {
id: content
anchors.left: parent.left
anchors.right: parent.right
spacing: Appearance.spacing.extraSmall
Repeater {
id: list
model: PageRegistry.pages
CustomRect {
id: item
required property int index
readonly property bool isCategoryEnd: index === list.model.length - 1 || PageRegistry.pages[index + 1].category !== modelData.category
readonly property bool isCategoryStart: index === 0 || PageRegistry.pages[index - 1].category !== modelData.category
readonly property bool isCurrentPage: index === root.sState.currentPageIdx
required property var modelData
Layout.fillWidth: true
Layout.topMargin: index !== 0 && isCategoryStart ? Appearance.spacing.small : 0
bottomLeftRadius: stateLayer.pressed ? Appearance.rounding.medium : isCurrentPage ? Appearance.rounding.large : isCategoryEnd ? Appearance.rounding.normal : Appearance.rounding.extraSmall
bottomRightRadius: stateLayer.pressed ? Appearance.rounding.medium : isCurrentPage ? Appearance.rounding.large : isCategoryEnd ? Appearance.rounding.normal : Appearance.rounding.extraSmall
color: isCurrentPage ? DynamicColors.palette.m3secondaryContainer : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
implicitHeight: {
const h = layout.implicitHeight + layout.anchors.margins * 2;
return h % 2 === 0 ? h : h + 1;
}
topLeftRadius: stateLayer.pressed ? Appearance.rounding.medium : isCurrentPage ? Appearance.rounding.large : isCategoryStart ? Appearance.rounding.normal : Appearance.rounding.extraSmall
topRightRadius: stateLayer.pressed ? Appearance.rounding.medium : isCurrentPage ? Appearance.rounding.large : isCategoryStart ? Appearance.rounding.normal : Appearance.rounding.extraSmall
RadiusBehavior on bottomLeftRadius {
}
RadiusBehavior on bottomRightRadius {
}
RadiusBehavior on topLeftRadius {
}
RadiusBehavior on topRightRadius {
}
StateLayer {
id: stateLayer
anchors.fill: parent
bottomLeftRadius: parent.bottomLeftRadius
bottomRightRadius: parent.bottomRightRadius
topLeftRadius: parent.topLeftRadius
topRightRadius: parent.topRightRadius
onClicked: root.sState.currentPageIdx = item.index
}
RowLayout {
id: layout
anchors.fill: parent
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.small
CustomRect {
Layout.bottomMargin: -1
Layout.fillHeight: true
Layout.topMargin: -1
color: item.isCurrentPage ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondaryContainer
implicitWidth: height
radius: Appearance.rounding.full
MaterialIcon {
anchors.centerIn: parent
anchors.verticalCenterOffset: 1
color: item.isCurrentPage ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSecondaryContainer
fill: item.modelData.noFill ? 0 : 1
font.pointSize: Appearance.font.size.large
grade: 25
text: item.modelData.icon
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
text: item.modelData.name
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurfaceVariant
elide: Text.ElideRight
text: item.modelData.description
}
}
}
}
}
}
component RadiusBehavior: Behavior {
Anim {
type: Anim.DefaultEffects
}
}
}
+197
View File
@@ -0,0 +1,197 @@
pragma Singleton
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Modules.Settings.Common
import qs.Modules.Settings.Pages
import qs.Modules.Settings.Pages.Wallpaper
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.Services
QtObject {
id: root
readonly property list<Component> pageComps: [
// Appearance
Component {
// Wallpaper & style
StackPage {
Component {
WallpaperPage {
}
}
Component {
WallpaperSelect {
}
}
}
},
Component {
// Screenshot
StackPage {
Component {
ScreenshotPage {
}
}
}
},
// Connectivity
Component {
PlaceholderComp {
}
},
Component {
PlaceholderComp {
}
},
Component {
// Audio
StackPage {
Component {
AudioPage {
}
}
Component {
AppVolumes {
}
}
}
},
// Shell
Component {
StackPage {
Component {
PanelsPage {
}
}
Component {
BarPanel {
}
}
Component {
DashboardPanel {
}
}
Component {
ResourcesPanel {
}
}
Component {
LauncherPanel {
}
}
Component {
SidebarPanel {
}
}
// Bar sub pages
Component {
BarTray {
}
}
Component {
BarStatusIcons {
}
}
Component {
BarClock {
}
}
}
},
// Apps
Component {
StackPage {
Component {
AppsPage {
}
}
Component {
AllApps {
}
}
Component {
AppInfo {
}
}
}
},
// Services
Component {
StackPage {
Component {
ServicesPage {
}
}
Component {
NotificationsPage {
}
}
}
},
// About
Component {
StackPage {
Component {
AboutPage {
}
}
}
}
]
readonly property Component placeholderComp: Component {
PlaceholderComp {
}
}
component PlaceholderComp: Item {
property SettingsState sState // To avoid the warning from non-existent property
ColumnLayout {
anchors.centerIn: parent
spacing: Appearance.padding.extraSmall
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3outlineVariant
font.pointSize: Appearance.font.size.extraLarge
text: "handyman"
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3outlineVariant
text: qsTr("Page under construction")
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3outlineVariant
text: qsTr("This page will be available in a future update.")
}
}
}
}
+70
View File
@@ -0,0 +1,70 @@
pragma Singleton
import QtQuick
import Quickshell
Singleton {
id: root
readonly property list<var> pages: [
// Appearance
{
name: qsTr("Appearance"),
icon: "colors",
description: qsTr("Colors, animations, font, wallpaper"),
category: "appearance"
},
{
name: qsTr("Screenshot"),
icon: "screenshot_region",
description: qsTr("Set screenshot effects"),
category: "appearance"
},
// Connectivity
{
name: qsTr("Network"),
icon: "wifi",
description: qsTr("Wi-Fi, ethernet"),
category: "connectivity"
},
{
name: qsTr("Connected devices"),
icon: "devices_other",
description: qsTr("Bluetooth, pairing"),
category: "connectivity"
},
{
name: qsTr("Audio"),
icon: "volume_up",
description: qsTr("App volumes, sound devices"),
category: "connectivity"
},
// Shell
{
name: qsTr("Panels"),
icon: "dock_to_bottom",
description: qsTr("Bar, dashboard, launcher, sidebar"),
category: "shell"
},
{
name: qsTr("Apps"),
icon: "apps",
description: qsTr("Default apps, favorites, hidden apps"),
category: "shell"
},
{
name: qsTr("Services"),
icon: "build",
description: qsTr("Poll intervals, audio and brightness increments"),
category: "shell"
},
{
name: qsTr("About"),
icon: "info",
description: qsTr("System information"),
category: "about"
},
]
}
+100
View File
@@ -0,0 +1,100 @@
import QtQuick
import qs.Components
import qs.Config
Item {
id: root
property int animOff
property Item currentItem
property int lastPageIdx
required property SettingsState sState
function loadPage(idx: int): void {
if (currentItem)
currentItem.destroy();
const comp = PageCompRegistry.pageComps[idx] ?? PageCompRegistry.placeholderComp;
const incubator = comp.incubateObject(container, {
sState
});
const attach = () => {
incubator.object.anchors.fill = container;
currentItem = incubator.object;
};
if (incubator.status === Component.Ready)
attach();
else
incubator.onStatusChanged = status => {
if (status === Component.Ready)
attach();
};
}
Item {
id: container
anchors.fill: parent
layer.enabled: opacity < 1
objectName: "PageContainer"
Component.onCompleted: root.loadPage(root.sState.currentPageIdx)
}
Connections {
function onCurrentPageIdxChanged(): void {
switchAnim.complete();
root.animOff = Appearance.padding.normal * (root.sState.currentPageIdx > root.lastPageIdx ? 1 : -1);
switchAnim.start();
root.lastPageIdx = root.sState.currentPageIdx;
}
target: root.sState
}
SequentialAnimation {
id: switchAnim
Anim {
property: "opacity"
target: container
to: 0
type: Anim.DefaultEffects
}
ScriptAction {
script: root.loadPage(root.sState.currentPageIdx)
}
PropertyAction {
property: "topMargin"
target: container.anchors
value: root.animOff
}
PropertyAction {
property: "bottomMargin"
target: container.anchors
value: -root.animOff
}
ParallelAnimation {
Anim {
from: 0
property: "opacity"
target: container
to: 1
type: Anim.SlowEffects
}
Anim {
properties: "topMargin,bottomMargin"
target: container.anchors
to: 0
type: Anim.SlowEffects
}
}
}
}
+117
View File
@@ -0,0 +1,117 @@
import QtQuick
import QtQuick.Layouts
import Quickshell.Io
import ZShell
import qs.Modules.Settings.Common
import qs.Config
import qs.Helpers
import qs.Components
PageBase {
id: root
property string cliVersion
readonly property int pluginCount: 0
property string quickshellVersion
title: qsTr("About")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
Process {
command: ["quickshell", "--version"]
running: true
stdout: StdioCollector {
onStreamFinished: root.quickshellVersion = text.trim().split(" ")[1] ?? ""
}
}
ConnectedRect {
Layout.fillWidth: true
first: true
implicitHeight: hero.implicitHeight + Appearance.padding.extraLarge * 2
last: true
ColumnLayout {
id: hero
anchors.centerIn: parent
spacing: Appearance.spacing.small
width: parent.width - Appearance.padding.largeIncreased * 2
CustomText {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Appearance.spacing.small
font.pointSize: Appearance.font.size.large
text: "ZShell"
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.medium
text: ZUtils.version ? `v${ZUtils.version}` : "…"
}
}
}
// System
SectionHeader {
text: qsTr("System")
}
InfoRow {
first: true
label: qsTr("Hostname")
value: SystemInfo.hostname
}
InfoRow {
label: qsTr("Device")
value: SystemInfo.device
}
InfoRow {
label: qsTr("Distro")
value: SystemInfo.osPrettyName || SystemInfo.osName
}
InfoRow {
label: qsTr("Kernel")
value: SystemInfo.kernel
}
InfoRow {
label: qsTr("Firmware")
last: true
value: SystemInfo.firmware
}
// Software
SectionHeader {
text: qsTr("Software")
}
InfoRow {
first: true
label: qsTr("Shell")
value: ZUtils.version || "…"
}
InfoRow {
label: qsTr("Quickshell")
value: root.quickshellVersion || "…"
}
InfoRow {
label: qsTr("Qt")
last: true
value: ZUtils.qtVersion || "…"
}
}
}
+100
View File
@@ -0,0 +1,100 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import qs.Components
import qs.Config
import qs.Helpers
import qs.Modules.Settings.Common
PageBase {
id: root
isSubPage: true
title: qsTr("All apps")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
Repeater {
id: list
model: [...DesktopEntries.applications.values].sort((a, b) => a.name.localeCompare(b.name))
ConnectedRect {
id: appItem
required property int index
required property DesktopEntry modelData
Layout.fillWidth: true
first: index === 0
implicitHeight: appRow.implicitHeight + appRow.anchors.margins * 2
last: index === list.count - 1
StateLayer {
onClicked: {
root.sState.selectedApp = appItem.modelData;
root.sState.openSubPage(2);
}
}
RowLayout {
id: appRow
anchors.fill: parent
anchors.leftMargin: Appearance.padding.largeIncreased
anchors.margins: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.largeIncreased
spacing: Appearance.spacing.small
IconImage {
asynchronous: true
implicitSize: Math.round(Appearance.font.size.large * 1.8)
source: Quickshell.iconPath(appItem.modelData.icon, "image-missing")
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: appItem.modelData.name
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: (appItem.modelData.comment || appItem.modelData.genericName) ?? ""
visible: text
}
}
MaterialIcon {
color: DynamicColors.palette.m3primary
fill: 1
font.pointSize: Appearance.font.size.small
text: "favorite"
visible: Strings.testRegexList(Config.launcher.favoriteApps, appItem.modelData.id)
}
MaterialIcon {
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.medium
text: "chevron_right"
}
}
}
}
}
}
+172
View File
@@ -0,0 +1,172 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import qs.Components
import qs.Config
import qs.Helpers
import qs.Modules.Settings.Common
PageBase {
id: root
readonly property DesktopEntry app: sState.selectedApp
readonly property bool favoriteByRegex: app && matchedByRegex(Config.launcher.favoriteApps, app.id)
readonly property bool hiddenByRegex: app && matchedByRegex(Config.launcher.hiddenApps, app.id)
function isRegexEntry(s: string): bool {
return /^\^.*\$$/.test(s);
}
function matchedByRegex(filterList: list<string>, id: string): bool {
return filterList.some(f => isRegexEntry(f) && new RegExp(f).test(id));
}
isSubPage: true
title: qsTr("App info")
onAppChanged: {
// Auto close when app lost
if (!app)
sState.closeSubPage();
}
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
// Header
RowLayout {
Layout.bottomMargin: Appearance.spacing.large
Layout.fillWidth: true
Layout.leftMargin: Appearance.padding.small
spacing: Appearance.spacing.large
IconImage {
asynchronous: true
implicitSize: Math.round(Appearance.font.size.large * 3)
source: Quickshell.iconPath(root.app?.icon, "image-missing")
}
ColumnLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.extraSmall / 2
CustomText {
Layout.fillWidth: true
font.pointSize: Appearance.font.size.medium
text: root.app?.name ?? ""
wrapMode: Text.WordWrap
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3outline
font.pointSize: Appearance.font.size.small
text: (root.app?.comment || root.app?.genericName) ?? ""
visible: text
wrapMode: Text.WordWrap
}
}
}
// Launcher
SectionHeader {
first: true
text: qsTr("Launcher")
}
ToggleRow {
checked: root.app && Strings.testRegexList(Config.launcher.favoriteApps, root.app.id)
enabled: !root.favoriteByRegex
first: true
subtext: root.favoriteByRegex ? qsTr("Matched by a regex in favoriteApps — edit the config file to change") : qsTr("Pin to the bottom of the launcher")
text: qsTr("Favorite")
onToggled: {
const apps = Config.launcher.favoriteApps;
Config.launcher.favoriteApps = checked ? [...apps, root.app.id] : apps.filter(a => a !== root.app.id);
}
}
ToggleRow {
checked: root.app && Strings.testRegexList(Config.launcher.hiddenApps, root.app.id)
enabled: !root.hiddenByRegex
last: true
subtext: root.hiddenByRegex ? qsTr("Matched by a regex in hiddenApps — edit the config file to change") : qsTr("Hide from the launcher")
text: qsTr("Hidden")
onToggled: {
const apps = Config.launcher.hiddenApps;
Config.launcher.hiddenApps = checked ? [...apps, root.app.id] : apps.filter(a => a !== root.app.id);
}
}
// Details
SectionHeader {
text: qsTr("Details")
}
WrapInfoRow {
id: appId
first: true
label: qsTr("App ID")
labelComp.Layout.preferredWidth: Math.max(labelComp.implicitWidth, command.labelComp.implicitWidth)
value: root.app?.id ?? ""
}
WrapInfoRow {
id: command
label: qsTr("Command")
labelComp.Layout.preferredWidth: Math.max(labelComp.implicitWidth, appId.labelComp.implicitWidth)
last: true
value: (root.app?.command ?? []).join(" ")
}
}
component WrapInfoRow: ConnectedRect {
id: row
property alias label: label.text
readonly property alias labelComp: label
property alias value: value.text
Layout.fillWidth: true
implicitHeight: rowLayout.implicitHeight + rowLayout.anchors.margins * 2
RowLayout {
id: rowLayout
anchors.fill: parent
anchors.leftMargin: Appearance.padding.largeIncreased
anchors.margins: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.largeIncreased
spacing: Appearance.spacing.small
CustomText {
id: label
Layout.alignment: Qt.AlignTop
font.pointSize: Appearance.font.size.small
}
Item {
Layout.fillWidth: true
}
CustomText {
id: value
Layout.fillWidth: true
Layout.maximumWidth: implicitWidth + 1 // Whyyyyyyyyy
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.small
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
}
}
}
}
+177
View File
@@ -0,0 +1,177 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import ZShell
import qs.Helpers
import qs.Components
import qs.Config
import qs.Modules.Settings.Common
PageBase {
id: root
title: qsTr("Apps")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
// Default applications
SectionHeader {
first: true
text: qsTr("Default applications")
}
DefaultRow {
first: true
icon: "terminal"
status: Config.general.apps.terminal.join(" ")
text: qsTr("Terminal")
onSelected: app => Config.general.apps.terminal = app.command
}
DefaultRow {
icon: "volume_up"
status: Config.general.apps.audio.join(" ")
text: qsTr("Audio")
onSelected: app => Config.general.apps.audio = app.command
}
DefaultRow {
icon: "play_circle"
status: Config.general.apps.playback.join(" ")
text: qsTr("Media playback")
onSelected: app => Config.general.apps.playback = app.command
}
DefaultRow {
icon: "folder"
last: true
status: Config.general.apps.explorer.join(" ")
text: qsTr("File manager")
onSelected: app => Config.general.apps.explorer = app.command
}
// Library
SectionHeader {
text: qsTr("Library")
}
NavRow {
first: true
icon: "apps"
last: true
status: qsTr("Browse installed apps, set favorites and hidden")
text: qsTr("All apps")
onClicked: root.sState.openSubPage(1)
}
}
component DefaultRow: PopupRow {
id: row
readonly property int popupHeight: root.flickable.height - y + root.flickable.contentY - Appearance.padding.large - Appearance.padding.extraLarge
signal selected(app: DesktopEntry)
keepPopupAsChild: {
if (root.sState.animatingContainer || root.opacity < 1)
return true;
let p = root.parent;
while (p && p.objectName !== "PageContainer")
p = p.parent;
return p?.opacity < 1;
}
popup.topMovement: Appearance.padding.large
Loader {
active: row.popup.animDriver > 0
anchors.centerIn: parent
sourceComponent: VerticalFadeListView {
id: list
fadeAmount: 0.05
implicitHeight: ZUtils.clamp(row.popupHeight, 200, 800)
implicitWidth: 300
model: {
const apps = [...DesktopEntries.applications.values];
const favorited = new Set(apps.filter(a => Strings.testRegexList(Config.launcher.favoriteApps, a.id)));
return apps.sort((a, b) => (favorited.has(b) - favorited.has(a)) || a.name.localeCompare(b.name));
}
delegate: StateLayer {
id: appItem
required property int index
required property DesktopEntry modelData
anchors.fill: undefined
anchors.left: list.contentItem.left
anchors.right: list.contentItem.right
implicitHeight: itemLayout.implicitHeight + itemLayout.anchors.margins * 2
radius: Appearance.rounding.small
onClicked: {
row.popup.open = false;
row.selected(modelData);
}
RowLayout {
id: itemLayout
anchors.fill: parent
anchors.margins: Appearance.padding.normal
spacing: Appearance.spacing.small
IconImage {
asynchronous: true
implicitSize: Math.round(Appearance.font.size.large * 1.8)
source: Quickshell.iconPath(appItem.modelData.icon, "image-missing")
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: appItem.modelData.name
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: (appItem.modelData.comment || appItem.modelData.genericName) ?? ""
visible: text
}
}
MaterialIcon {
color: DynamicColors.palette.m3primary
fill: 1
font.pointSize: Appearance.font.size.small
text: "favorite"
visible: Strings.testRegexList(Config.launcher.favoriteApps, appItem.modelData.id)
}
}
}
}
}
}
}
@@ -0,0 +1,69 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.Pipewire
import qs.Modules.Settings.Common
import qs.Helpers
import qs.Components
import qs.Config
import qs.Daemons
PageBase {
id: root
isSubPage: true
title: qsTr("App volumes")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
CustomText {
Layout.bottomMargin: Appearance.spacing.small
Layout.fillWidth: true
Layout.leftMargin: Appearance.padding.small
color: DynamicColors.palette.m3outline
font.pointSize: Appearance.font.size.small
text: qsTr("Adjust the volume of individual apps currently playing audio.")
wrapMode: Text.WordWrap
}
ItemList {
id: streamList
color: list.count === 0 ? DynamicColors.tPalette.m3surfaceContainer : "transparent"
first: true
last: true
list.spacing: Appearance.spacing.extraSmall / 2
placeholderIcon: "music_off"
placeholderText: qsTr("No apps playing audio")
showList: true
delegate: SliderRow {
id: stream
required property int index
required property PwNode modelData
anchors.left: streamList.list.contentItem.left
anchors.right: streamList.list.contentItem.right
enabled: !stream.modelData?.audio?.muted
first: index === 0
icon: Icons.getVolumeIcon(stream.modelData?.audio?.volume ?? 0, stream.modelData?.audio?.muted ?? false)
label: Audio.getStreamName(stream.modelData)
last: index === streamList.list.count - 1
value: stream.modelData?.audio?.volume ?? 0
valueLabel: Math.round(value * 100) + "%"
onMoved: v => Audio.setStreamVolume(stream.modelData, v)
}
model: ScriptModel {
values: [...Audio.streams]
}
}
}
}
+136
View File
@@ -0,0 +1,136 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Daemons
import qs.Config
import qs.Components
import qs.Modules.Settings.Common
import qs.Helpers
PageBase {
id: root
title: qsTr("Audio")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
// Output
SliderRow {
enabled: !Audio.muted
first: true
icon: Icons.getVolumeIcon(Audio.volume, Audio.muted)
label: qsTr("Output")
value: Audio.volume
valueLabel: Math.round(value * 100) + "%"
onMoved: v => Audio.setVolume(v)
}
ToggleRow {
checked: Audio.muted
text: qsTr("Muted")
onToggled: Audio.setStreamMuted(Audio.sink, checked)
}
AudioDeviceList {
currentId: Audio.sink?.id ?? -1
iconName: "speaker"
nodes: Audio.sinks
placeholderIcon: "speaker"
placeholderText: qsTr("No output devices")
onSelected: node => Audio.setAudioSink(node)
}
// Input
SliderRow {
Layout.topMargin: Appearance.spacing.large - parent.spacing
enabled: !Audio.sourceMuted
first: true
icon: Icons.getMicVolumeIcon(Audio.sourceVolume, Audio.sourceMuted)
label: qsTr("Input")
value: Audio.sourceVolume
valueLabel: Math.round(value * 100) + "%"
onMoved: v => Audio.setSourceVolume(v)
}
ToggleRow {
checked: Audio.sourceMuted
text: qsTr("Muted")
onToggled: Audio.setStreamMuted(Audio.source, checked)
}
AudioDeviceList {
currentId: Audio.source?.id ?? -1
iconName: "mic"
nodes: Audio.sources
placeholderIcon: "mic_off"
placeholderText: qsTr("No input devices")
onSelected: node => Audio.setAudioSource(node)
}
// Per-app volumes
ConnectedRect {
Layout.fillWidth: true
Layout.topMargin: Appearance.spacing.large - parent.spacing
first: true
implicitHeight: appLayout.implicitHeight + appLayout.anchors.margins * 2
last: true
StateLayer {
onClicked: root.sState.openSubPage(1)
}
RowLayout {
id: appLayout
anchors.fill: parent
anchors.leftMargin: Appearance.padding.largeIncreased
anchors.margins: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.largeIncreased
spacing: Appearance.spacing.small
MaterialIcon {
font.pointSize: Appearance.font.size.medium
text: "tune"
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: qsTr("App volumes")
}
CustomText {
Layout.fillWidth: true
animate: true
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: Audio.streams.length === 0 ? qsTr("No apps playing audio") : Audio.streams.length === 1 ? qsTr("1 app playing audio") : qsTr("%1 apps playing audio").arg(Audio.streams.length)
}
}
MaterialIcon {
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.medium
text: "chevron_right"
}
}
}
}
}
@@ -0,0 +1,61 @@
pragma ComponentBehavior: Bound
import QtQuick.Layouts
import qs.Config
import qs.Components
import qs.Modules.Settings.Common
PageBase {
id: root
readonly property list<MenuItem> clockFormats: [
MenuItem {
text: qsTr("Long format")
value: "dddd d MMM - hh:mm"
},
MenuItem {
text: qsTr("Short format")
value: "ddd d MMM - hh:mm"
},
MenuItem {
text: qsTr("Time only")
value: "hh:mm"
},
MenuItem {
text: qsTr("Long format seconds")
value: "dddd d MMM - hh:mm:ss"
},
MenuItem {
text: qsTr("Short format seconds")
value: "ddd d MMM - hh:mm:ss"
},
MenuItem {
text: qsTr("Time only seconds")
value: "hh:mm:ss"
}
]
isSubPage: true
title: qsTr("Clock")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
SelectRow {
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
active: root.clockFormats.find(item => item.value === Config.general.dateFormat)
first: true
last: true
menuItems: root.clockFormats
subtext: qsTr("Change how time is displayed in the widget")
text: qsTr("Time format")
onSelected: item => {
Config.general.dateFormat = item.value;
}
}
}
}
@@ -0,0 +1,93 @@
pragma ComponentBehavior: Bound
import QtQuick.Layouts
import qs.Config
import qs.Modules.Settings.Common
PageBase {
id: root
isSubPage: true
title: qsTr("System icons")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
// Visible icons
SectionHeader {
first: true
text: qsTr("Visible icons")
}
ToggleRow {
checked: Config.bar.tray.showAudio
first: true
text: qsTr("Speakers")
onToggled: Config.bar.tray.showAudio = checked
}
ToggleRow {
checked: Config.bar.tray.showMicrophone
text: qsTr("Microphone")
onToggled: Config.bar.tray.showMicrophone = checked
}
//////// FOR LATER:
// ToggleRow {
// checked: Config.bar.tray.showNetwork
// text: qsTr("Network")
//
// onToggled: Config.bar.tray.showNetwork = checked
// }
//
// ToggleRow {
// checked: Config.bar.tray.showWifi
// text: qsTr("Wi-Fi")
//
// onToggled: Config.bar.tray.showWifi = checked
// }
//
// ToggleRow {
// checked: Config.bar.tray.showBluetooth
// text: qsTr("Bluetooth")
//
// onToggled: Config.bar.tray.showBluetooth = checked
// }
ToggleRow {
checked: Config.bar.tray.showPower
last: true
text: qsTr("Battery")
onToggled: Config.bar.tray.showPower = checked
}
// Behaviour
SectionHeader {
text: qsTr("Behavior")
}
ToggleRow {
checked: Config.bar.popouts.audio
first: true
subtext: qsTr("Show a details popout when hovering the audio icons")
text: qsTr("Audio popout on hover")
onToggled: Config.bar.popouts.audio = checked
}
ToggleRow {
checked: Config.bar.popouts.upower
last: true
subtext: qsTr("Show a details popout when hovering the power icon")
text: qsTr("Power popout on hover")
onToggled: Config.bar.popouts.upower = checked
}
}
}
@@ -0,0 +1,31 @@
pragma ComponentBehavior: Bound
import QtQuick.Layouts
import qs.Config
import qs.Modules.Settings.Common
PageBase {
id: root
isSubPage: true
title: qsTr("Tray")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
SpinRow {
first: true
from: 12
last: true
stepSize: 1
text: qsTr("Icon size")
to: 24
value: Config.bar.tray.trayIconSize
onMoved: value => Config.bar.tray.trayIconSize = value
}
}
}
@@ -0,0 +1,66 @@
pragma ComponentBehavior: Bound
import QtQuick.Layouts
import qs.Config
import qs.Modules.Settings.Common
PageBase {
id: root
isSubPage: true
title: qsTr("Taskbar")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
// Behavior
SectionHeader {
first: true
text: qsTr("Behavior")
}
ToggleRow {
checked: Config.bar.autoHide
first: true
last: true
subtext: qsTr("Hide the bar, reveal on hover")
text: qsTr("Auto hide")
onToggled: Config.bar.autoHide = checked
}
// Components
SectionHeader {
text: qsTr("Components")
}
NavRow {
first: true
icon: "widgets"
status: qsTr("System tray icons")
text: qsTr("Tray")
onClicked: root.sState.openSubPage(6)
}
NavRow {
icon: "signal_cellular_alt"
status: qsTr("Visible indicators")
text: qsTr("Status icons")
onClicked: root.sState.openSubPage(7)
}
NavRow {
icon: "schedule"
last: true
status: qsTr("Date, icon, background")
text: qsTr("Clock")
onClicked: root.sState.openSubPage(8)
}
}
}
@@ -0,0 +1,36 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Components
import qs.Modules.Settings.Common
PageBase {
id: root
isSubPage: true
title: qsTr("Dashboard")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
// General
SectionHeader {
first: true
text: qsTr("General")
}
ToggleRow {
checked: Config.dashboard.enabled
first: true
last: true
text: qsTr("Enabled")
onToggled: Config.dashboard.enabled = checked
}
}
}
@@ -0,0 +1,131 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Components
import qs.Modules.Settings.Common
PageBase {
id: root
isSubPage: true
title: qsTr("Launcher")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
// General
SectionHeader {
first: true
text: qsTr("General")
}
ToggleRow {
checked: Config.launcher.enabled
first: true
last: true
text: qsTr("Enabled")
onToggled: Config.launcher.enabled = checked
}
// Display
SectionHeader {
text: qsTr("Display")
}
SpinRow {
first: true
from: 1
stepSize: 1
text: qsTr("Max items shown")
to: 20
value: Config.launcher.maxAppsShown
onMoved: v => Config.launcher.maxShown = v
}
SpinRow {
from: 1
stepSize: 1
text: qsTr("Max wallpapers")
to: 30
value: Config.launcher.maxWallpapers
onMoved: v => Config.launcher.maxWallpapers = v
}
SpinRow {
from: 0
last: true
stepSize: 5
subtext: qsTr("Pixels dragged before the launcher opens")
text: qsTr("Drag threshold")
to: 200
value: Config.launcher.dragThreshold
onMoved: v => Config.launcher.dragThreshold = v
}
// Behavior
SectionHeader {
text: qsTr("Behavior")
}
ToggleRow {
checked: Config.launcher.enableDangerousActions
first: true
last: true
subtext: qsTr("Allow actions that shut down or log out")
text: qsTr("Enable dangerous actions")
onToggled: Config.launcher.enableDangerousActions = checked
}
// Fuzzy search
SectionHeader {
text: qsTr("Fuzzy search")
}
ToggleRow {
checked: Config.launcher.useFuzzy.apps
first: true
text: qsTr("Apps")
onToggled: Config.launcher.useFuzzy.apps = checked
}
ToggleRow {
checked: Config.launcher.useFuzzy.actions
text: qsTr("Actions")
onToggled: Config.launcher.useFuzzy.actions = checked
}
ToggleRow {
checked: Config.launcher.useFuzzy.schemes
text: qsTr("Schemes")
onToggled: Config.launcher.useFuzzy.schemes = checked
}
ToggleRow {
checked: Config.launcher.useFuzzy.variants
text: qsTr("Variants")
onToggled: Config.launcher.useFuzzy.variants = checked
}
ToggleRow {
checked: Config.launcher.useFuzzy.wallpapers
last: true
text: qsTr("Wallpapers")
onToggled: Config.launcher.useFuzzy.wallpapers = checked
}
}
}
@@ -0,0 +1,85 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Components
import qs.Modules.Settings.Common
PageBase {
id: root
isSubPage: true
title: qsTr("Resources")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
// General
SectionHeader {
first: true
text: qsTr("General")
}
ToggleRow {
checked: Config.dashboard.performance.enabled
first: true
last: true
text: qsTr("Enabled")
onToggled: Config.dashboard.performance.enabled = checked
}
// Performance widgets
SectionHeader {
text: qsTr("Performance widgets")
}
ToggleRow {
checked: Config.dashboard.performance.showBattery
first: true
text: qsTr("Battery")
onToggled: Config.dashboard.performance.showBattery = checked
}
ToggleRow {
checked: Config.dashboard.performance.showGpu
text: qsTr("GPU")
onToggled: Config.dashboard.performance.showGpu = checked
}
ToggleRow {
checked: Config.dashboard.performance.showCpu
text: qsTr("CPU")
onToggled: Config.dashboard.performance.showCpu = checked
}
ToggleRow {
checked: Config.dashboard.performance.showMemory
text: qsTr("Memory")
onToggled: Config.dashboard.performance.showMemory = checked
}
ToggleRow {
checked: Config.dashboard.performance.showStorage
text: qsTr("Storage")
onToggled: Config.dashboard.performance.showStorage = checked
}
ToggleRow {
checked: Config.dashboard.performance.showNetwork
last: true
text: qsTr("Network")
onToggled: Config.dashboard.performance.showNetwork = checked
}
}
}
@@ -0,0 +1,46 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Components
import qs.Modules.Settings.Common
PageBase {
id: root
isSubPage: true
title: qsTr("Sidebar")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
SectionHeader {
first: true
text: qsTr("General")
}
ToggleRow {
checked: Config.sidebar.enabled
first: true
text: qsTr("Enabled")
onToggled: Config.sidebar.enabled = checked
}
SpinRow {
from: 0
last: true
stepSize: 5
subtext: qsTr("Pixels dragged before the sidebar opens")
text: qsTr("Drag threshold")
to: 200
value: Config.sidebar.dragThreshold
onMoved: v => Config.sidebar.dragThreshold = v
}
}
}
+58
View File
@@ -0,0 +1,58 @@
import QtQuick.Layouts
import qs.Config
import qs.Modules.Settings.Common
PageBase {
id: root
title: qsTr("Panels")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
NavRow {
first: true
icon: "dock_to_bottom"
status: !Config.bar.autoHide ? qsTr("Always visible") : qsTr("Reveal on hover")
text: qsTr("Bar")
onClicked: root.sState.openSubPage(1)
}
NavRow {
icon: "dashboard"
status: Config.dashboard.enabled ? qsTr("Enabled") : qsTr("Disabled")
text: qsTr("Dashboard")
onClicked: root.sState.openSubPage(2)
}
NavRow {
icon: "insert_chart"
status: Config.dashboard.performance.enabled ? qsTr("Enabled") : qsTr("Disabled")
text: qsTr("Resources")
onClicked: root.sState.openSubPage(3)
}
NavRow {
icon: "apps"
status: Config.launcher.enabled ? qsTr("Enabled") : qsTr("Disabled")
text: qsTr("Launcher")
onClicked: root.sState.openSubPage(4)
}
NavRow {
icon: "dock_to_right"
last: true
status: Config.sidebar.enabled ? qsTr("Enabled") : qsTr("Disabled")
text: qsTr("Sidebar")
onClicked: root.sState.openSubPage(5)
}
}
}
+146
View File
@@ -0,0 +1,146 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import ZShell.Components
import qs.Helpers
import qs.Components
import qs.Config
import qs.Modules.Settings
import qs.Modules.Settings.Common
PageBase {
id: root
title: qsTr("Screenshot effects")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
SectionHeader {
first: true
text: qsTr("Effects")
}
ToggleRow {
checked: Config.screenshot.enable_pp
first: true
text: qsTr("Enable effects")
onToggled: Config.screenshot.enable_pp = checked
}
SelectRow {
active: Config.screenshot.mode === "manual" ? menuItems[0] : menuItems[1]
enabled: Config.screenshot.enable_pp
last: true
subtext: qsTr("Automatic or manual effect values")
text: qsTr("Effects mode")
menuItems: [
MenuItem {
icon: "build"
text: qsTr("Manual")
value: "manual"
},
MenuItem {
icon: "rotate_auto"
text: qsTr("Auto")
value: "auto"
}
]
onSelected: item => {
Config.screenshot.mode = item.value;
Config.save();
}
}
SectionHeader {
text: qsTr("Rounded corners")
}
ToggleRow {
checked: Config.screenshot.rounding
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual"
first: true
text: qsTr("Enable rounded corners")
onToggled: Config.screenshot.rounding = checked
}
SpinRow {
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.rounding
from: 0
last: true
stepSize: 1
text: qsTr("Corner radius")
to: 50
value: Config.screenshot.radius
onMoved: value => {
const newVal = Math.floor(value);
Config.screenshot.radius = newVal;
}
}
SectionHeader {
text: qsTr("Shadow")
}
ToggleRow {
checked: Config.screenshot.shadow
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual"
first: true
text: qsTr("Enable shadow")
onToggled: Config.screenshot.shadow = checked
}
SpinRow {
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow
from: 0
stepSize: 1
text: qsTr("Shadow blur amount")
to: 100
value: Config.screenshot.shadow_blur
onMoved: value => {
const newVal = Math.floor(value);
Config.screenshot.shadow_blur = newVal;
}
}
SpinRow {
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow
from: -100
stepSize: 10
text: qsTr("Shadow horizontal offset")
to: 100
value: Config.screenshot.shadow_offset_x
onMoved: value => {
const newVal = Math.floor(value);
Config.screenshot.shadow_offset_x = newVal;
}
}
SpinRow {
enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow
from: -100
last: true
stepSize: 10
text: qsTr("Shadow vertical offset")
to: 100
value: Config.screenshot.shadow_offset_y
onMoved: value => {
const newVal = Math.floor(value);
Config.screenshot.shadow_offset_y = newVal;
}
}
}
}
@@ -0,0 +1,162 @@
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Components
import qs.Modules.Settings.Common
PageBase {
id: root
isSubPage: true
title: qsTr("Notifications")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
// Notifications
SectionHeader {
first: true
text: qsTr("Notifications")
}
ToggleRow {
checked: Config.notifs.showInFullscreen
first: true
subtext: qsTr("Whether notifications appear over fullscreen apps")
text: qsTr("Show in fullscreen")
onToggled: Config.notifs.showInFullscreen = checked
}
ToggleRow {
checked: Config.notifs.expire
subtext: qsTr("Dismiss notifications after their timeout")
text: qsTr("Expire automatically")
onToggled: Config.notifs.expire = checked
}
ToggleRow {
checked: Config.notifs.openExpanded
subtext: qsTr("Show notifications expanded by default")
text: qsTr("Open expanded")
onToggled: Config.notifs.openExpanded = checked
}
SpinRow {
from: 1000
stepSize: 500
subtext: qsTr("Time before a notification dismisses (ms)")
text: qsTr("Default timeout")
to: 60000
value: Config.notifs.defaultExpireTimeout
onMoved: v => Config.notifs.defaultExpireTimeout = Math.round(v)
}
SpinRow {
from: 1
last: true
stepSize: 1
subtext: qsTr("Notifications shown per group before collapsing")
text: qsTr("Group preview count")
to: 10
value: Config.notifs.groupPreviewNum
onMoved: v => Config.notifs.groupPreviewNum = Math.round(v)
}
// Toasts
SectionHeader {
text: qsTr("Toasts")
}
SpinRow {
first: true
from: 1
stepSize: 1
subtext: qsTr("Maximum number of toasts shown at once")
text: qsTr("Visible toasts")
to: 10
value: Config.utilities.maxToasts
onMoved: v => Config.utilities.maxToasts = Math.round(v)
}
ToggleRow {
checked: Config.utilities.toasts.chargingChanged
text: qsTr("Charging changes")
onToggled: Config.utilities.toasts.chargingChanged = checked
}
ToggleRow {
checked: Config.utilities.toasts.gameModeChanged
text: qsTr("Game mode changes")
onToggled: Config.utilities.toasts.gameModeChanged = checked
}
// ToggleRow {
// checked: Config.utilities.toasts.dndChanged
// text: qsTr("Do not disturb changes")
//
// onToggled: Config.utilities.toasts.dndChanged = checked
// }
ToggleRow {
checked: Config.utilities.toasts.audioOutputChanged
text: qsTr("Audio output changes")
onToggled: Config.utilities.toasts.audioOutputChanged = checked
}
ToggleRow {
checked: Config.utilities.toasts.audioInputChanged
last: true
text: qsTr("Audio input changes")
onToggled: Config.utilities.toasts.audioInputChanged = checked
}
// ToggleRow {
// checked: Config.utilities.toasts.capsLockChanged
// text: qsTr("Caps lock changes")
//
// onToggled: Config.utilities.toasts.capsLockChanged = checked
// }
//
// ToggleRow {
// checked: Config.utilities.toasts.numLockChanged
// text: qsTr("Num lock changes")
//
// onToggled: Config.utilities.toasts.numLockChanged = checked
// }
//
// ToggleRow {
// checked: Config.utilities.toasts.kbLayoutChanged
// text: qsTr("Keyboard layout changes")
//
// onToggled: Config.utilities.toasts.kbLayoutChanged = checked
// }
//
// ToggleRow {
// checked: Config.utilities.toasts.vpnChanged
// text: qsTr("VPN changes")
//
// onToggled: Config.utilities.toasts.vpnChanged = checked
// }
//
// ToggleRow {
// checked: Config.utilities.toasts.nowPlaying
// last: true
// text: qsTr("Now playing")
//
// onToggled: Config.utilities.toasts.nowPlaying = checked
// }
}
}
+214
View File
@@ -0,0 +1,214 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import ZShell.Services
import qs.Components
import qs.Helpers
import qs.Config
import qs.Modules.Settings.Common
PageBase {
id: root
// GPU options + the config string each maps to (see Gpu::parseType)
readonly property list<MenuItem> gpuItems: [
MenuItem {
text: qsTr("Auto")
},
MenuItem {
text: "NVIDIA"
},
MenuItem {
text: qsTr("Generic")
},
MenuItem {
text: qsTr("None")
}
]
readonly property list<string> gpuValues: ["", "NVIDIA", "GENERIC", "None"]
function gpuKeyToIndex(key: string): int {
const u = (key ?? "").trim().toUpperCase();
if (u === "")
return 0; // Auto
if (u === "NVIDIA")
return 1;
if (u === "GENERIC")
return 2;
return 3; // None
}
title: qsTr("Services")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.extraSmall / 2
width: root.cappedWidth
// Detected running players, used as default-player options
Variants {
id: playerVariants
model: [...new Set(Players.list.map(p => Players.getIdentity(p)).filter(id => id))]
MenuItem {
required property string modelData
activeIcon: "music_note"
icon: modelData === Config.services.defaultPlayer ? "check" : ""
text: modelData
}
}
// Notifications
SectionHeader {
first: true
text: qsTr("Notifications")
}
NavRow {
first: true
icon: "notifications"
last: true
status: qsTr("Notifications, toasts, timeouts")
text: qsTr("Notifications")
onClicked: root.sState.openSubPage(1)
}
// Polling
SectionHeader {
text: qsTr("Polling")
}
SpinRow {
first: true
from: 100
stepSize: 50
subtext: qsTr("How often the media position updates (ms)")
text: qsTr("Media refresh")
to: 2000
value: Config.dashboard.mediaUpdateInterval
onMoved: v => Config.dashboard.mediaUpdateInterval = v
}
SpinRow {
from: 0.5
last: true
stepSize: 0.5
subtext: qsTr("CPU, memory and GPU update interval (seconds)")
text: qsTr("System stats refresh")
to: 10
value: Config.dashboard.resourceUpdateInterval / 1000
onMoved: v => Config.dashboard.resourceUpdateInterval = Math.round(v * 1000)
}
// Media & lyrics
SectionHeader {
text: qsTr("Media")
}
SelectRow {
active: menuItems.find(i => i.text === Config.services.defaultPlayer) ?? null
fallbackIcon: "music_note"
fallbackText: Config.services.defaultPlayer || qsTr("Auto")
first: true
last: true
menuItems: playerVariants.instances
subtext: qsTr("Preferred media player when several are open")
text: qsTr("Default player")
onSelected: item => Config.services.defaultPlayer = item.text
}
// Input increments
SectionHeader {
text: qsTr("Input increments")
}
SpinRow {
first: true
from: 1
stepSize: 1
subtext: qsTr("Amount the volume changes per input (%)")
text: qsTr("Volume step")
to: 50
value: Math.round(Config.services.audioIncrement * 100)
onMoved: v => Config.services.audioIncrement = v / 100
}
SpinRow {
from: 1
stepSize: 1
subtext: qsTr("Amount the brightness changes per input (%)")
text: qsTr("Brightness step")
to: 50
value: Math.round(Config.services.brightnessIncrement * 100)
onMoved: v => Config.services.brightnessIncrement = v / 100
}
SpinRow {
from: 1
stepSize: 1
subtext: qsTr("Lowest allowed brightness (%)")
text: qsTr("Brightness minimum")
to: 20
value: Math.round(Config.services.minBrightness * 100)
onMoved: v => Config.services.minBrightness = v / 100
}
SpinRow {
from: 50
last: true
stepSize: 5
subtext: qsTr("Upper limit for output volume (%)")
text: qsTr("Max volume")
to: 200
value: Math.round(Config.services.maxVolume * 100)
onMoved: v => Config.services.maxVolume = v / 100
}
// Service tuning
SectionHeader {
text: qsTr("Service tuning")
}
SpinRow {
first: true
from: 10
stepSize: 2
subtext: qsTr("Resolution of the audio visualizer")
text: qsTr("Visualizer resolution")
to: 120
value: Config.services.visualizerBars
onMoved: v => Config.services.visualiserBars = v
}
ToggleRow {
checked: Config.general.color.smart
subtext: qsTr("Derive theme mode from the wallpaper")
text: qsTr("Smart color scheme")
onToggled: Config.general.color.smart = checked
}
SelectRow {
active: root.gpuItems[root.gpuKeyToIndex(Config.services.gpuType)]
last: true
menuItems: root.gpuItems
menuOnTop: true
subtext: Gpu.name ? qsTr("Monitoring: %1").arg(Gpu.name) : qsTr("Override for GPU type")
text: qsTr("GPU")
onSelected: item => Config.services.gpuType = root.gpuValues[root.gpuItems.indexOf(item)]
}
}
}
@@ -0,0 +1,119 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import ZShell.Models
import qs.Paths
import qs.Helpers
import qs.Components
import qs.Config
import qs.Modules.Settings.Common
PageBase {
id: root
isSubPage: true
title: qsTr("Wallpapers")
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
spacing: Appearance.spacing.small
width: root.cappedWidth
CustomText {
Layout.topMargin: Appearance.spacing.large
font.pointSize: Appearance.font.size.large
text: qsTr("Wallpapers")
}
GridLayout {
Layout.fillWidth: true
columnSpacing: Appearance.spacing.extraSmall
columns: 4
rowSpacing: Appearance.spacing.extraSmall
visible: localWalls.count > 0
Repeater {
id: localWalls
model: {
const walls = Wallpapers.list;
var baseDir = Paths.wallsdir;
const categories = {};
const list = [];
for (const w of walls) {
var parentDir = w.parentDir;
if (!parentDir.endsWith("/"))
parentDir = parentDir + "/";
if (!baseDir.endsWith("/"))
baseDir = baseDir + "/";
if (parentDir !== baseDir) {
const category = Wallpapers.getCategoryFor(w);
if (category && (!(category in categories) || categories[category].name.localeCompare(w.name) > 0))
categories[category] = w;
} else {
list.push(w);
}
}
list.push(...Object.values(categories));
list.sort((a, b) => ((a.parentDir === baseDir) - (b.parentDir === baseDir)) || a.name.localeCompare(b.name));
while (list.length < 4)
list.push(null);
return list;
}
WallItem {
required property FileSystemEntry modelData
enabled: modelData
// Empty placeholders for sizing
opacity: modelData ? 1 : 0
source: String(modelData?.path ?? "")
onClicked: {
Wallpapers.setWallpaper(modelData.path);
root.sState.closeSubPage();
}
}
}
}
Loader {
Layout.fillWidth: true
active: localWalls.count === 0
asynchronous: true
visible: active
sourceComponent: CustomRect {
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: noWallsLayout.implicitHeight + Appearance.padding.extraLarge * 3
radius: Appearance.rounding.large
ColumnLayout {
id: noWallsLayout
anchors.centerIn: parent
spacing: Appearance.spacing.extraSmall
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3outline
text: "hide_image"
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3outline
text: qsTr("No local wallpapers found")
}
}
}
}
}
}
+156
View File
@@ -0,0 +1,156 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import ZShell.Components
import qs.Helpers
import qs.Components
import qs.Config
import qs.Modules.Settings
import qs.Modules.Settings.Common
PageBase {
id: root
title: qsTr("Wallpaper & style")
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Appearance.spacing.large
width: root.cappedWidth
Item {
id: wallWrapper
Layout.alignment: Qt.AlignHCenter
implicitHeight: cropper.height
implicitWidth: root.cappedWidth
WallpaperCropper {
id: cropper
}
}
IconTextButton {
Layout.alignment: Qt.AlignHCenter
enabled: Config.background.enabled
horizontalPadding: Appearance.padding.extraLarge
icon: "wallpaper"
isRound: true
shapeMorph: true
text: qsTr("Wallpapers")
type: IconTextButton.Tonal
verticalPadding: Appearance.padding.normal
onClicked: root.sState.openSubPage(1) // Wallpaper page
}
ToggleRow {
checked: Config.background.enabled
first: true
text: qsTr("Display wallpaper")
onToggled: Config.background.enabled = checked
}
ToggleRow {
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
checked: DynamicColors.transparency.enabled
subtext: qsTr("Base %1, layers %2").arg(DynamicColors.transparency.base).arg(DynamicColors.transparency.layers)
text: qsTr("Transparency")
onToggled: Config.appearance.transparency.enabled = checked
}
ToggleRow {
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
checked: !DynamicColors.light
last: true
text: qsTr("Dark theme")
onToggled: DynamicColors.setMode(checked ? "dark" : "light")
}
OverlayRow {
id: darkMode
readonly property string endTime: {
var d = new Date(0, 0, 0, 0, 0, 0, 0);
d.setMinutes(Config.general.color.scheduleDarkEnd);
return Qt.formatTime(d, "hh:mm AP");
}
readonly property string startTime: {
var d = new Date(0, 0, 0, 0, 0, 0, 0);
d.setMinutes(Config.general.color.scheduleDarkStart);
return Qt.formatTime(d, "hh:mm AP");
}
checked: Config.general.color.scheduleDark
first: true
subtext: qsTr("Dark mode will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime)
text: qsTr("Schedule dark mode")
popup: Component {
TimeInput {
object: Config.general.color
settings: ["scheduleDark", "scheduleDarkStart", "scheduleDarkEnd"]
onApplySettings: (start, end) => {
Config.general.color.scheduleDarkStart = start;
Config.general.color.scheduleDarkEnd = end;
Config.save();
ModeScheduler.checkStartup();
PopupManager.requestClose();
}
onClose: PopupManager.requestClose()
}
}
onClicked: value => {
Config.general.color.scheduleDark = value;
}
}
OverlayRow {
id: hyprsunset
readonly property string endTime: {
var d = new Date(0, 0, 0, 0, 0, 0, 0);
d.setMinutes(Config.general.color.scheduleHyprsunsetEnd);
return Qt.formatTime(d, "hh:mm AP");
}
readonly property string startTime: {
var d = new Date(0, 0, 0, 0, 0, 0, 0);
d.setMinutes(Config.general.color.scheduleHyprsunsetStart);
return Qt.formatTime(d, "hh:mm AP");
}
Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing
checked: Config.general.color.scheduleHyprsunset
last: true
subtext: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime)
text: qsTr("Schedule hyprsunset")
popup: Component {
TimeInput {
object: Config.general.color
settings: ["scheduleHyprsunset", "scheduleHyprsunsetStart", "scheduleHyprsunsetEnd"]
onApplySettings: (start, end) => {
Config.general.color.scheduleHyprsunsetStart = start;
Config.general.color.scheduleHyprsunsetEnd = end;
Config.save();
Hyprsunset.checkStartup();
PopupManager.requestClose();
}
onClose: PopupManager.requestClose()
}
}
onClicked: value => {
Config.general.color.scheduleHyprsunset = value;
}
}
}
}
+20
View File
@@ -0,0 +1,20 @@
pragma Singleton
import Quickshell
import QtQuick
Singleton {
id: root
property bool closed: true
property Component currentPopup: null
function requestClose(): void {
closed = true;
}
function requestOpen(component: Component): void {
currentPopup = component;
closed = false;
}
}
+46
View File
@@ -0,0 +1,46 @@
import QtQuick
import qs.Config
import qs.Components
CustomRect {
id: root
readonly property bool closing: PopupManager.closed
readonly property var currentPopup: PopupManager.currentPopup
property bool shouldBeVisible: loader.status === Loader.Ready
color: Qt.alpha(DynamicColors.palette.m3shadow, 0.3)
opacity: shouldBeVisible && !closing ? 1 : 0
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
onVisibleChanged: if (!visible)
PopupManager.currentPopup = null
CustomMouseArea {
anchors.fill: parent
hoverEnabled: true
preventStealing: true
propagateComposedEvents: false
onClicked: {
const insideItemWidth = mouseX < loader.x + loader.item.width && mouseX > loader.x;
const insideItemHeight = mouseY < loader.y + loader.item.height && mouseY > loader.y;
if (insideItemHeight && insideItemWidth)
return;
PopupManager.requestClose();
}
}
Loader {
id: loader
anchors.centerIn: parent
sourceComponent: root.currentPopup
}
}
-317
View File
@@ -1,317 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import qs.Components
import qs.Config
import "../../scripts/fuzzysort.js" as Fuzzy
import "../../scripts/SettingsIndex.mjs" as SettingsIndex
Item {
id: root
property alias text: searchField.text
signal settingSelected(string category, string section, string settingName)
function close() {
searchField.text = "";
searchField.focus = false;
popup.close();
}
function search(query) {
resultsModel.clear();
if (!query || query.trim() === "") {
popup.close();
return;
}
const results = SettingsIndex.searchSettings(query, Fuzzy);
for (const result of results.slice(0, 10)) {
resultsModel.append({
name: result.name,
category: result.category,
categoryName: result.categoryName,
section: result.section,
matchType: result.matchType
});
}
if (resultsModel.count > 0) {
popup.open();
} else {
popup.close();
}
}
implicitHeight: searchContainer.implicitHeight
implicitWidth: 200
Shortcut {
sequence: "/"
onActivated: searchField.forceActiveFocus()
}
ListModel {
id: resultsModel
}
CustomRect {
id: searchContainer
anchors.fill: parent
color: DynamicColors.tPalette.m3surfaceContainerHigh
implicitHeight: searchRow.implicitHeight + Appearance.padding.small * 2
radius: Appearance.rounding.full
RowLayout {
id: searchRow
anchors.fill: parent
anchors.leftMargin: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.small
spacing: Appearance.spacing.small
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.larger
text: "search"
}
CustomTextField {
id: searchField
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
font.pointSize: Appearance.font.size.small
placeholderText: qsTr("Search settings...")
Keys.onDownPressed: {
if (popup.visible && resultsList.count > 0) {
resultsList.currentIndex = Math.min(resultsList.currentIndex + 1, resultsList.count - 1);
}
}
Keys.onEscapePressed: {
root.close();
}
Keys.onReturnPressed: {
if (popup.visible && resultsList.currentIndex >= 0) {
const item = resultsModel.get(resultsList.currentIndex);
root.settingSelected(item.category, item.section, item.name);
root.close();
}
}
Keys.onUpPressed: {
if (popup.visible && resultsList.count > 0) {
resultsList.currentIndex = Math.max(resultsList.currentIndex - 1, 0);
}
}
onTextChanged: {
searchTimer.restart();
}
}
// Clear button
IconButton {
Layout.alignment: Qt.AlignVCenter
font.pointSize: Appearance.font.size.larger
icon: "close"
opacity: searchField.text.length > 0 ? 1 : 0
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.small
}
}
onClicked: {
root.close();
}
}
}
}
// Debounce timer for search
Timer {
id: searchTimer
interval: 150
onTriggered: root.search(searchField.text)
}
// Results dropdown
Popup {
id: popup
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
implicitHeight: Math.min(resultsList.contentHeight + Appearance.padding.small * 2 + 10, (55 + Appearance.spacing.smaller) * 5 - Appearance.spacing.smaller + 10 + Appearance.padding.small * 2)
implicitWidth: root.width + Appearance.padding.small * 2
modal: false
padding: 0
x: root.width / 2 - popup.width / 2
y: searchContainer.height + Appearance.spacing.small - 5
background: Item {
}
contentItem: Item {
property alias view: resultsList
Elevation {
id: popupShadow
anchors.centerIn: parent
height: popup.implicitHeight - 10
level: 2
radius: Appearance.rounding.normal
width: popup.implicitWidth - 10
CustomRect {
anchors.fill: parent
color: DynamicColors.palette.m3surfaceContainer
radius: Appearance.rounding.normal
CustomListView {
id: resultsList
anchors.fill: parent
anchors.margins: Appearance.padding.small
clip: true
currentIndex: 0
highlightFollowsCurrentItem: false
highlightRangeMode: ListView.ApplyRange
model: resultsModel
preferredHighlightBegin: 0
preferredHighlightEnd: height
spacing: Appearance.spacing.smaller
delegate: SearchResultItem {
required property string category
required property string categoryName
required property int index
required property string matchType
required property string name
required property string section
highlighted: index === resultsList.currentIndex
width: resultsList.width
onClicked: {
root.settingSelected(category, section, name);
root.close();
}
}
highlight: CustomRect {
color: DynamicColors.palette.m3primary
implicitHeight: resultsList.currentItem?.implicitHeight ?? 0
implicitWidth: resultsList.width
radius: Appearance.rounding.normal - Appearance.padding.smaller
y: resultsList.currentItem?.y ?? 0
Behavior on y {
Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
}
}
}
}
}
}
enter: Transition {
Anim {
duration: Appearance.anim.durations.small
from: 0
property: "opacity"
to: 1
}
Anim {
duration: Appearance.anim.durations.small
from: 0.95
property: "scale"
to: 1.0
}
}
exit: Transition {
Anim {
duration: Appearance.anim.durations.smaller
from: 1
property: "opacity"
to: 0
}
}
}
// Search result item component
component SearchResultItem: CustomRect {
id: resultItem
property bool highlighted: false
signal clicked
implicitHeight: 45 + Appearance.padding.small * 2
radius: Appearance.rounding.small
ColumnLayout {
id: resultLayout
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 2
// Setting name
CustomText {
Layout.fillWidth: true
color: highlighted ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
font.weight: Font.Medium
text: resultItem.name
}
// Category and section path
RowLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.smaller
CustomText {
color: highlighted ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.smaller
opacity: 0.8
text: resultItem.categoryName
}
CustomText {
color: highlighted ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.smaller
opacity: 0.6
text: ""
}
CustomText {
Layout.fillWidth: true
color: highlighted ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurfaceVariant
elide: Text.ElideRight
font.pointSize: Appearance.font.size.smaller
opacity: 0.8
text: resultItem.section
}
}
}
StateLayer {
onClicked: resultItem.clicked()
}
}
}
+33
View File
@@ -0,0 +1,33 @@
import QtQuick
import Quickshell
import Quickshell.Bluetooth
QtObject {
id: root
property bool animatingContainer
property int currentPageIdx
property bool isWindow
property ShellScreen screen
property bool searchOpen
property DesktopEntry selectedApp
property BluetoothDevice selectedBtDevice
property string selectedWallpaperCategory
property list<int> subPageIdxStack
signal close
signal subPageClosed
signal subPageOpened(idx: int)
function closeSubPage(): void {
subPageClosed();
subPageIdxStack.pop();
}
function openSubPage(idx: int): void {
subPageIdxStack.push(idx);
subPageOpened(idx);
}
onCurrentPageIdxChanged: subPageIdxStack.length = 0
}
+5 -3
View File
@@ -13,7 +13,6 @@ Item {
readonly property bool shouldBeActive: visibilities.settings
required property PersistentProperties visibilities
anchors.topMargin: (-implicitHeight - 5) * offsetScale
implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth
opacity: 1 - offsetScale
@@ -37,8 +36,11 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
sourceComponent: Content {
screen: root.screen
visibilities: root.visibilities
sState.animatingContainer: content.opacity < 1
sState.currentPageIdx: ["wallpaper"][0]
sState.screen: root.screen
onClose: console.log("shouldclose")
}
}
}