diff --git a/Components/CustomSpinBox.qml b/Components/CustomSpinBox.qml index fe98e72..5a4245b 100644 --- a/Components/CustomSpinBox.qml +++ b/Components/CustomSpinBox.qml @@ -28,6 +28,7 @@ RowLayout { CustomTextField { id: textField + implicitHeight: upButton.implicitHeight inputMethodHints: Qt.ImhFormattedNumbersOnly leftPadding: Appearance.padding.normal padding: Appearance.padding.small @@ -37,7 +38,7 @@ RowLayout { background: CustomRect { color: DynamicColors.tPalette.m3surfaceContainerHigh implicitWidth: 100 - radius: Appearance.rounding.small + radius: Appearance.rounding.full } validator: DoubleValidator { bottom: root.min @@ -82,10 +83,12 @@ RowLayout { } CustomRect { + id: upButton + color: DynamicColors.palette.m3primary implicitHeight: upIcon.implicitHeight + Appearance.padding.small * 2 implicitWidth: implicitHeight - radius: Appearance.rounding.small + radius: Appearance.rounding.full StateLayer { id: upState @@ -119,7 +122,7 @@ RowLayout { color: DynamicColors.palette.m3primary implicitHeight: downIcon.implicitHeight + Appearance.padding.small * 2 implicitWidth: implicitHeight - radius: Appearance.rounding.small + radius: Appearance.rounding.full StateLayer { id: downState diff --git a/Components/CustomSplitButtonRow.qml b/Components/CustomSplitButtonRow.qml index a8295e9..491a8ed 100644 --- a/Components/CustomSplitButtonRow.qml +++ b/Components/CustomSplitButtonRow.qml @@ -20,7 +20,7 @@ Item { Layout.fillWidth: true Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2 clip: false - z: splitButton.menu.implicitHeight > 0 ? expandedZ : 1 + z: root.expanded ? expandedZ : -1 RowLayout { id: row @@ -36,14 +36,15 @@ Item { color: root.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSurfaceVariant font.pointSize: Appearance.font.size.larger text: root.label + z: root.expanded ? root.expandedZ : -1 } CustomSplitButton { id: splitButton enabled: root.enabled - menu.z: 1 type: CustomSplitButton.Filled + z: root.expanded ? root.expandedZ : -1 menu.onItemSelected: item => { root.selected(item); diff --git a/Config/Config.qml b/Config/Config.qml index e3b4776..b3f2bac 100644 --- a/Config/Config.qml +++ b/Config/Config.qml @@ -60,8 +60,8 @@ Singleton { } }, anim: { - mediaGifSpeedAdjustment: 300, - sessionGifSpeed: 0.7, + mediaGifSpeedAdjustment: appearance.anim.mediaGifSpeedAdjustment, + sessionGifSpeed: appearance.anim.sessionGifSpeed, durations: { scale: appearance.anim.durations.scale } @@ -189,7 +189,7 @@ Singleton { explorer: general.apps.explorer }, idle: { - timouts: general.idle.timeouts + timeouts: general.idle.timeouts } }; } diff --git a/Drawers/Interactions.qml b/Drawers/Interactions.qml index 5116422..e27a765 100644 --- a/Drawers/Interactions.qml +++ b/Drawers/Interactions.qml @@ -123,6 +123,7 @@ CustomMouseArea { root.dashboardShortcutActive = true; } + root.visibilities.settings = false; root.visibilities.sidebar = false; root.popouts.hasCurrent = false; } else { @@ -149,8 +150,10 @@ CustomMouseArea { } } - if (root.visibilities.launcher) + if (root.visibilities.launcher) { root.visibilities.dock = false; + root.visibilities.settings = false; + } } function onOsdChanged() { @@ -168,6 +171,18 @@ CustomMouseArea { if (root.visibilities.resources && root.popouts.currentName.startsWith("audio")) { root.popouts.hasCurrent = false; } + + if (root.visibilities.resources) + root.visibilities.settings = false; + } + + function onSettingsChanged() { + if (root.visibilities.settings) { + root.visibilities.resources = false; + root.visibilities.dashboard = false; + root.panels.popouts.hasCurrent = false; + root.visibilities.launcher = false; + } } function onSidebarChanged() { diff --git a/Modules/Settings/Categories.qml b/Modules/Settings/Categories.qml index bd4da4e..731428c 100644 --- a/Modules/Settings/Categories.qml +++ b/Modules/Settings/Categories.qml @@ -22,68 +22,75 @@ Item { ListElement { icon: "settings" + key: "general" name: "General" } ListElement { icon: "wallpaper" + key: "wallpaper" name: "Wallpaper" } ListElement { icon: "settop_component" + key: "bar" name: "Bar" } ListElement { icon: "lock" + key: "lockscreen" name: "Lockscreen" } ListElement { icon: "build_circle" + key: "services" name: "Services" } ListElement { icon: "notifications" + key: "notifications" name: "Notifications" } ListElement { icon: "view_sidebar" + key: "sidebar" name: "Sidebar" } ListElement { icon: "handyman" + key: "utilities" name: "Utilities" } ListElement { icon: "dashboard" + key: "dashboard" name: "Dashboard" } ListElement { icon: "colors" + key: "appearance" name: "Appearance" } ListElement { icon: "display_settings" + key: "osd" name: "On screen display" } ListElement { icon: "rocket_launch" + key: "launcher" name: "Launcher" } - - ListElement { - icon: "colors" - name: "Colors" - } } CustomClippingRect { @@ -129,6 +136,7 @@ Item { required property string icon required property int index + required property string key required property string name implicitHeight: 42 @@ -172,7 +180,7 @@ Item { id: layer onClicked: { - root.content.currentCategory = categoryItem.name.toLowerCase(); + root.content.currentCategory = categoryItem.key; clayout.currentIndex = categoryItem.index; } } diff --git a/Modules/Settings/Categories/Appearance.qml b/Modules/Settings/Categories/Appearance.qml index 1c6d237..01c88e5 100644 --- a/Modules/Settings/Categories/Appearance.qml +++ b/Modules/Settings/Categories/Appearance.qml @@ -1,141 +1,159 @@ -import Quickshell -import Quickshell.Widgets -import QtQuick -import QtQuick.Layouts -import qs.Components -import qs.Modules as Modules import qs.Modules.Settings.Controls import qs.Config -import qs.Helpers -CustomFlickable { +SettingsPage { id: root - contentHeight: clayout.implicitHeight + SettingsSection { + SettingsHeader { + name: "Scale" + } - TapHandler { - acceptedButtons: Qt.LeftButton + SettingSpinBox { + name: "Rounding scale" + object: Config.appearance.rounding + setting: "scale" + step: 0.1 + } - onTapped: function (eventPoint) { - const menu = SettingsDropdowns.activeMenu; - if (!menu) - return; + Separator { + } - const p = eventPoint.scenePosition; + SettingSpinBox { + name: "Spacing scale" + object: Config.appearance.spacing + setting: "scale" + step: 0.1 + } - if (SettingsDropdowns.hit(SettingsDropdowns.activeTrigger, p)) - return; + Separator { + } - if (SettingsDropdowns.hit(menu, p)) - return; + SettingSpinBox { + name: "Padding scale" + object: Config.appearance.padding + setting: "scale" + step: 0.1 + } - SettingsDropdowns.closeActive(); + 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 } } - ColumnLayout { - id: clayout + SettingsSection { + SettingsHeader { + name: "Fonts" + } - anchors.left: parent.left - anchors.right: parent.right + SettingInput { + name: "Sans family" + object: Config.appearance.font.family + setting: "sans" + } - CustomRect { - Layout.fillWidth: true - Layout.preferredHeight: colorLayout.implicitHeight + Appearance.padding.normal * 2 - color: DynamicColors.tPalette.m3surfaceContainer - radius: Appearance.rounding.normal - Appearance.padding.smaller + Separator { + } - ColumnLayout { - id: colorLayout + SettingInput { + name: "Monospace family" + object: Config.appearance.font.family + setting: "mono" + } - anchors.left: parent.left - anchors.margins: Appearance.padding.large - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - spacing: Appearance.spacing.normal + Separator { + } - Settings { - name: "Color" - } + SettingInput { + name: "Material family" + object: Config.appearance.font.family + setting: "material" + } - SettingSwitch { - name: "Automatic color scheme" - object: Config.general.color - setting: "schemeGeneration" - } + Separator { + } - Separator { - } - - SettingSwitch { - name: "Smart color scheme" - object: Config.general.color - setting: "smart" - } - - Separator { - } - - SettingSpinner { - name: "Schedule dark mode" - object: Config.general.color - settings: ["scheduleDarkStart", "scheduleDarkEnd"] - z: 2 - } - - Separator { - } - - CustomSplitButtonRow { - enabled: true - label: qsTr("Scheme mode") - - menuItems: [ - MenuItem { - property string val: "light" - - icon: "light_mode" - text: qsTr("Light") - }, - MenuItem { - property string val: "dark" - - icon: "dark_mode" - text: qsTr("Dark") - } - ] - - Component.onCompleted: { - if (Config.general.color.mode === "light") - active = menuItems[0]; - else - active = menuItems[1]; - } - onSelected: item => { - Config.general.color.mode = item.val; - Config.save(); - } - } - } + SettingInput { + name: "Clock family" + object: Config.appearance.font.family + setting: "clock" } } - component Settings: CustomRect { - id: settingsItem + SettingsSection { + SettingsHeader { + name: "Animation" + } - required property string name + SettingSpinBox { + name: "Media GIF speed adjustment" + object: Config.appearance.anim + setting: "mediaGifSpeedAdjustment" + step: 10 + } - Layout.preferredHeight: 60 - Layout.preferredWidth: 200 + Separator { + } - CustomText { - id: text + SettingSpinBox { + name: "Session GIF speed" + max: 5 + min: 0 + object: Config.appearance.anim + setting: "sessionGifSpeed" + step: 0.1 + } + } - anchors.fill: parent - font.bold: true - font.pointSize: Appearance.font.size.large * 2 - text: settingsItem.name - verticalAlignment: Text.AlignVCenter + SettingsSection { + SettingsHeader { + name: "Transparency" + } + + SettingSwitch { + name: "Enable transparency" + object: Config.appearance.transparency + setting: "enabled" + } + + Separator { + } + + SettingSpinBox { + name: "Base opacity" + max: 1 + min: 0 + object: Config.appearance.transparency + setting: "base" + step: 0.05 + } + + Separator { + } + + SettingSpinBox { + name: "Layer opacity" + max: 1 + min: 0 + object: Config.appearance.transparency + setting: "layers" + step: 0.05 } } } diff --git a/Modules/Settings/Categories/Background.qml b/Modules/Settings/Categories/Background.qml index 2d24d68..b7fabb9 100644 --- a/Modules/Settings/Categories/Background.qml +++ b/Modules/Settings/Categories/Background.qml @@ -1,13 +1,29 @@ -import Quickshell -import Quickshell.Widgets -import QtQuick -import QtQuick.Layouts -import qs.Components -import qs.Modules as Modules +import qs.Modules.Settings.Controls import qs.Config -import qs.Helpers -CustomRect { +SettingsPage { id: root + SettingsSection { + SettingsHeader { + name: "Wallpaper" + } + + SettingSwitch { + name: "Enable wallpaper rendering" + object: Config.background + setting: "enabled" + } + + Separator { + } + + SettingSpinBox { + name: "Fade duration" + min: 0 + object: Config.background + setting: "wallFadeDuration" + step: 50 + } + } } diff --git a/Modules/Settings/Categories/Bar.qml b/Modules/Settings/Categories/Bar.qml new file mode 100644 index 0000000..150a709 --- /dev/null +++ b/Modules/Settings/Categories/Bar.qml @@ -0,0 +1,184 @@ +import qs.Modules.Settings.Controls +import qs.Config + +SettingsPage { + SettingsSection { + SettingsHeader { + name: "Bar" + } + + SettingSwitch { + name: "Auto hide" + object: Config.barConfig + setting: "autoHide" + } + + Separator { + } + + SettingSpinBox { + name: "Height" + min: 1 + object: Config.barConfig + setting: "height" + } + + Separator { + } + + SettingSpinBox { + name: "Rounding" + min: 0 + object: Config.barConfig + setting: "rounding" + } + + Separator { + } + + SettingSpinBox { + name: "Border" + min: 0 + object: Config.barConfig + setting: "border" + } + } + + SettingsSection { + SettingsHeader { + name: "Popouts" + } + + SettingSwitch { + name: "Tray" + object: Config.barConfig.popouts + setting: "tray" + } + + Separator { + } + + SettingSwitch { + name: "Audio" + object: Config.barConfig.popouts + setting: "audio" + } + + Separator { + } + + SettingSwitch { + name: "Active window" + object: Config.barConfig.popouts + setting: "activeWindow" + } + + Separator { + } + + SettingSwitch { + name: "Resources" + object: Config.barConfig.popouts + setting: "resources" + } + + Separator { + } + + SettingSwitch { + name: "Clock" + object: Config.barConfig.popouts + setting: "clock" + } + + Separator { + } + + SettingSwitch { + name: "Network" + object: Config.barConfig.popouts + setting: "network" + } + + Separator { + } + + SettingSwitch { + name: "Power" + object: Config.barConfig.popouts + setting: "upower" + } + } + + SettingsSection { + SettingsHeader { + name: "Entries" + } + + SettingBarEntryList { + name: "Bar entries" + object: Config.barConfig + setting: "entries" + } + } + + SettingsSection { + SettingsHeader { + name: "Dock" + } + + SettingSwitch { + name: "Enable dock" + object: Config.dock + setting: "enable" + } + + Separator { + } + + SettingSpinBox { + name: "Dock height" + min: 1 + 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 { + name: "Pinned apps" + addLabel: qsTr("Add pinned app") + object: Config.dock + setting: "pinnedApps" + } + + Separator { + } + + SettingStringList { + name: "Ignored app regexes" + addLabel: qsTr("Add ignored regex") + object: Config.dock + setting: "ignoredAppRegexes" + } + } +} diff --git a/Modules/Settings/Categories/Dashboard.qml b/Modules/Settings/Categories/Dashboard.qml new file mode 100644 index 0000000..f597541 --- /dev/null +++ b/Modules/Settings/Categories/Dashboard.qml @@ -0,0 +1,212 @@ +import qs.Modules.Settings.Controls +import qs.Config + +SettingsPage { + SettingsSection { + SettingsHeader { + name: "Dashboard" + } + + SettingSwitch { + name: "Enable dashboard" + object: Config.dashboard + setting: "enabled" + } + + Separator { + } + + SettingSpinBox { + name: "Media update interval" + min: 0 + object: Config.dashboard + setting: "mediaUpdateInterval" + step: 50 + } + + Separator { + } + + SettingSpinBox { + name: "Resource update interval" + min: 0 + object: Config.dashboard + setting: "resourceUpdateInterval" + step: 50 + } + + Separator { + } + + SettingSpinBox { + name: "Drag threshold" + min: 0 + object: Config.dashboard + setting: "dragThreshold" + } + } + + SettingsSection { + 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 { + 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) + } + } +} diff --git a/Modules/Settings/Categories/General.qml b/Modules/Settings/Categories/General.qml index e35999b..0c0bf0b 100644 --- a/Modules/Settings/Categories/General.qml +++ b/Modules/Settings/Categories/General.qml @@ -1,37 +1,212 @@ -import Quickshell -import Quickshell.Widgets -import QtQuick -import QtQuick.Layouts -import qs.Components -import qs.Modules as Modules +import qs.Modules.Settings.Controls import qs.Config -import qs.Helpers +import qs.Components -CustomRect { +SettingsPage { id: root - ColumnLayout { - id: clayout + function schemeTypeItem(items, value) { + for (let i = 0; i < items.length; i++) { + const item = items[i]; + if (item.value === value) + return item; + } - anchors.fill: parent + return items[0] ?? null; } - component Settings: CustomRect { - id: settingsItem + SettingsSection { + SettingsHeader { + name: "General" + } - required property string name + SettingInput { + name: "Logo" + object: Config.general + setting: "logo" + } - Layout.preferredHeight: 60 - Layout.preferredWidth: 200 + Separator { + } - CustomText { - id: text + SettingInput { + name: "Wallpaper path" + object: Config.general + setting: "wallpaperPath" + } - anchors.fill: parent - font.bold: true - font.pointSize: Appearance.font.size.large * 2 - text: settingsItem.name - verticalAlignment: Text.AlignVCenter + Separator { + } + + SettingSwitch { + name: "Desktop icons" + object: Config.general + setting: "desktopIcons" + } + } + + SettingsSection { + SettingsHeader { + name: "Color" + } + + CustomSplitButtonRow { + active: Config.general.color.mode === "light" ? menuItems[0] : menuItems[1] + 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(); + } + } + + Separator { + } + + CustomSplitButtonRow { + id: schemeType + + active: root.schemeTypeItem(menuItems, Config.colors.schemeType) + label: qsTr("Scheme type") + z: 2 + + 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: "tonalSpot" + }, + 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: "fruitSalad" + } + ] + + onSelected: item => { + Config.colors.schemeType = item.value; + Config.save(); + } + } + + Separator { + } + + SettingSwitch { + name: "Automatic color scheme" + object: Config.general.color + setting: "schemeGeneration" + } + + Separator { + } + + SettingSwitch { + name: "Smart color scheme" + object: Config.general.color + setting: "smart" + } + + Separator { + } + + SettingSpinner { + name: "Schedule dark mode" + object: Config.general.color + settings: ["scheduleDarkStart", "scheduleDarkEnd"] + } + } + + SettingsSection { + z: -1 + + 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" } } } diff --git a/Modules/Settings/Categories/Launcher.qml b/Modules/Settings/Categories/Launcher.qml new file mode 100644 index 0000000..18cb34f --- /dev/null +++ b/Modules/Settings/Categories/Launcher.qml @@ -0,0 +1,148 @@ +import qs.Modules.Settings.Controls +import qs.Config + +SettingsPage { + SettingsSection { + SettingsHeader { + name: "Launcher" + } + + SettingSpinBox { + name: "Max apps shown" + min: 1 + object: Config.launcher + setting: "maxAppsShown" + } + + Separator { + } + + SettingSpinBox { + name: "Max wallpapers shown" + min: 1 + 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" + } + } + + SettingsSection { + 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 { + SettingsHeader { + name: "Sizes" + } + + SettingSpinBox { + name: "Item width" + min: 1 + object: Config.launcher.sizes + setting: "itemWidth" + } + + Separator { + } + + SettingSpinBox { + name: "Item height" + min: 1 + object: Config.launcher.sizes + setting: "itemHeight" + } + + Separator { + } + + SettingSpinBox { + name: "Wallpaper width" + min: 1 + object: Config.launcher.sizes + setting: "wallpaperWidth" + } + + Separator { + } + + SettingSpinBox { + name: "Wallpaper height" + min: 1 + object: Config.launcher.sizes + setting: "wallpaperHeight" + } + } + + SettingsSection { + SettingsHeader { + name: "Actions" + } + + SettingActionList { + name: "Launcher actions" + object: Config.launcher + setting: "actions" + } + } +} diff --git a/Modules/Settings/Categories/Lockscreen.qml b/Modules/Settings/Categories/Lockscreen.qml index 570aecc..7c9d2d2 100644 --- a/Modules/Settings/Categories/Lockscreen.qml +++ b/Modules/Settings/Categories/Lockscreen.qml @@ -1,77 +1,90 @@ -import Quickshell -import Quickshell.Widgets -import QtQuick -import QtQuick.Layouts -import qs.Components -import qs.Modules as Modules import qs.Modules.Settings.Categories.Lockscreen +import qs.Modules.Settings.Controls import qs.Config -import qs.Helpers -CustomFlickable { +SettingsPage { id: root - contentHeight: clayout.implicitHeight + SettingsSection { + SettingsHeader { + name: "Lockscreen" + } - TapHandler { - acceptedButtons: Qt.LeftButton + SettingSwitch { + name: "Recolor logo" + object: Config.lock + setting: "recolorLogo" + } - onTapped: function (eventPoint) { - const menu = SettingsDropdowns.activeMenu; - if (!menu) - return; + Separator { + } - const p = eventPoint.scenePosition; + SettingSwitch { + name: "Enable fingerprint" + object: Config.lock + setting: "enableFprint" + } - if (SettingsDropdowns.hit(SettingsDropdowns.activeTrigger, p)) - return; + Separator { + } - if (SettingsDropdowns.hit(menu, p)) - return; + SettingSpinBox { + name: "Max fingerprint tries" + min: 1 + object: Config.lock + setting: "maxFprintTries" + step: 1 + } - SettingsDropdowns.closeActive(); + Separator { + } + + SettingSpinBox { + name: "Blur amount" + min: 0 + object: Config.lock + setting: "blurAmount" + step: 1 + } + + Separator { + } + + SettingSpinBox { + name: "Height multiplier" + max: 2 + min: 0.1 + object: Config.lock.sizes + setting: "heightMult" + step: 0.05 + } + + Separator { + } + + SettingSpinBox { + name: "Aspect ratio" + max: 4 + min: 0.5 + object: Config.lock.sizes + setting: "ratio" + step: 0.05 + } + + Separator { + } + + SettingSpinBox { + name: "Center width" + min: 100 + object: Config.lock.sizes + setting: "centerWidth" + step: 10 } } - ColumnLayout { - id: clayout - - anchors.fill: parent - - CustomRect { - Layout.fillWidth: true - Layout.preferredHeight: idleLayout.implicitHeight + Appearance.padding.normal * 2 - color: DynamicColors.tPalette.m3surfaceContainer - radius: Appearance.rounding.normal - Appearance.padding.smaller - z: -1 - - Idle { - id: idleLayout - - anchors.left: parent.left - anchors.margins: Appearance.padding.large - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - } - } - } - - component Settings: CustomRect { - id: settingsItem - - required property string name - - Layout.preferredHeight: 60 - Layout.preferredWidth: 200 - - CustomText { - id: text - - anchors.fill: parent - font.bold: true - font.pointSize: Appearance.font.size.large * 2 - text: settingsItem.name - verticalAlignment: Text.AlignVCenter + SettingsSection { + Idle { } } } diff --git a/Modules/Settings/Categories/Notifications.qml b/Modules/Settings/Categories/Notifications.qml new file mode 100644 index 0000000..ef0486b --- /dev/null +++ b/Modules/Settings/Categories/Notifications.qml @@ -0,0 +1,112 @@ +import qs.Modules.Settings.Controls +import qs.Config + +SettingsPage { + SettingsSection { + 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 { + 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" + } + } +} diff --git a/Modules/Settings/Categories/Osd.qml b/Modules/Settings/Categories/Osd.qml new file mode 100644 index 0000000..34d5724 --- /dev/null +++ b/Modules/Settings/Categories/Osd.qml @@ -0,0 +1,77 @@ +import qs.Modules.Settings.Controls +import qs.Config + +SettingsPage { + SettingsSection { + 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 { + 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" + } + } +} diff --git a/Modules/Settings/Categories/Services.qml b/Modules/Settings/Categories/Services.qml new file mode 100644 index 0000000..bae8112 --- /dev/null +++ b/Modules/Settings/Categories/Services.qml @@ -0,0 +1,120 @@ +import qs.Modules.Settings.Controls +import qs.Config + +SettingsPage { + SettingsSection { + SettingsHeader { + name: "Services" + } + + SettingInput { + name: "Weather location" + object: Config.services + setting: "weatherLocation" + } + + 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 { + SettingsHeader { + name: "Media" + } + + SettingSpinBox { + name: "Audio increment" + max: 1 + min: 0 + object: Config.services + setting: "audioIncrement" + step: 0.05 + } + + Separator { + } + + SettingSpinBox { + name: "Brightness increment" + max: 1 + min: 0 + object: Config.services + setting: "brightnessIncrement" + step: 0.05 + } + + Separator { + } + + SettingSpinBox { + name: "Max volume" + max: 5 + min: 0 + object: Config.services + setting: "maxVolume" + step: 0.05 + } + + Separator { + } + + SettingInput { + name: "Default player" + object: Config.services + setting: "defaultPlayer" + } + + Separator { + } + + SettingSpinBox { + name: "Visualizer bars" + min: 1 + object: Config.services + setting: "visualizerBars" + step: 1 + } + + Separator { + } + + SettingAliasList { + name: "Player aliases" + object: Config.services + setting: "playerAliases" + } + } +} diff --git a/Modules/Settings/Categories/Sidebar.qml b/Modules/Settings/Categories/Sidebar.qml new file mode 100644 index 0000000..b661e04 --- /dev/null +++ b/Modules/Settings/Categories/Sidebar.qml @@ -0,0 +1,26 @@ +import qs.Modules.Settings.Controls +import qs.Config + +SettingsPage { + SettingsSection { + SettingsHeader { + name: "Sidebar" + } + + SettingSwitch { + name: "Enable sidebar" + object: Config.sidebar + setting: "enabled" + } + + Separator { + } + + SettingSpinBox { + name: "Width" + min: 1 + object: Config.sidebar.sizes + setting: "width" + } + } +} diff --git a/Modules/Settings/Categories/Utilities.qml b/Modules/Settings/Categories/Utilities.qml new file mode 100644 index 0000000..3ec1e15 --- /dev/null +++ b/Modules/Settings/Categories/Utilities.qml @@ -0,0 +1,170 @@ +import qs.Modules.Settings.Controls +import qs.Config + +SettingsPage { + SettingsSection { + SettingsHeader { + name: "Utilities" + } + + SettingSwitch { + name: "Enable utilities" + object: Config.utilities + setting: "enabled" + } + + Separator { + } + + SettingSpinBox { + name: "Max toasts" + min: 1 + object: Config.utilities + setting: "maxToasts" + } + + Separator { + } + + SettingSpinBox { + name: "Panel width" + min: 1 + object: Config.utilities.sizes + setting: "width" + } + + Separator { + } + + SettingSpinBox { + name: "Toast width" + min: 1 + object: Config.utilities.sizes + setting: "toastWidth" + } + } + + SettingsSection { + 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 { + 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" + } + } +} diff --git a/Modules/Settings/Content.qml b/Modules/Settings/Content.qml index 3bb23a2..0e47180 100644 --- a/Modules/Settings/Content.qml +++ b/Modules/Settings/Content.qml @@ -27,10 +27,26 @@ Item { stack.push(general); else if (currentCategory === "wallpaper") stack.push(background); + else if (currentCategory === "bar") + stack.push(bar); else if (currentCategory === "appearance") stack.push(appearance); else if (currentCategory === "lockscreen") stack.push(lockscreen); + else if (currentCategory === "services") + stack.push(services); + else if (currentCategory === "notifications") + stack.push(notifications); + else if (currentCategory === "sidebar") + stack.push(sidebar); + else if (currentCategory === "utilities") + stack.push(utilities); + else if (currentCategory === "dashboard") + stack.push(dashboard); + else if (currentCategory === "osd") + stack.push(osd); + else if (currentCategory === "launcher") + stack.push(launcher); } target: root @@ -101,10 +117,66 @@ Item { } } + 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 { + } + } } diff --git a/Modules/Settings/Controls/SettingActionList.qml b/Modules/Settings/Controls/SettingActionList.qml new file mode 100644 index 0000000..90ccac5 --- /dev/null +++ b/Modules/Settings/Controls/SettingActionList.qml @@ -0,0 +1,235 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config + +ColumnLayout { + id: root + + required property string name + required property var object + required property string setting + + 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(); + } + + Layout.fillWidth: true + spacing: Appearance.spacing.smaller + + 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 { + } + + 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") + values: [...(modelData.command ?? [])] + + onListEdited: function (values) { + root.updateAction(index, "command", values); + } + } + + Separator { + } + + RowLayout { + Layout.fillWidth: true + + CustomText { + Layout.fillWidth: true + text: qsTr("Enabled") + } + + CustomSwitch { + checked: modelData.enabled ?? true + + onToggled: root.updateAction(index, "enabled", checked) + } + } + + Separator { + } + + 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") + } + } +} diff --git a/Modules/Settings/Controls/SettingAliasList.qml b/Modules/Settings/Controls/SettingAliasList.qml new file mode 100644 index 0000000..f7f8959 --- /dev/null +++ b/Modules/Settings/Controls/SettingAliasList.qml @@ -0,0 +1,155 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config + +ColumnLayout { + id: root + + required property string name + required property var object + required property string setting + + 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(); + } + + Layout.fillWidth: true + 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.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: 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.fillWidth: true + Layout.preferredHeight: 33 + color: DynamicColors.tPalette.m3surface + radius: Appearance.rounding.small + + CustomTextField { + anchors.fill: parent + anchors.leftMargin: Appearance.padding.normal + anchors.rightMargin: Appearance.padding.normal + 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") + } + } +} diff --git a/Modules/Settings/Controls/SettingBarEntryList.qml b/Modules/Settings/Controls/SettingBarEntryList.qml new file mode 100644 index 0000000..f082133 --- /dev/null +++ b/Modules/Settings/Controls/SettingBarEntryList.qml @@ -0,0 +1,523 @@ +pragma ComponentBehavior: Bound + +import Quickshell +import QtQuick +import QtQuick.Layouts +import QtQml.Models +import qs.Components +import qs.Config + +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 + required property string name + required property var object + property var pendingCommitEntries: [] + required property string setting + property int uidCounter: 0 + property var visualEntries: [] + + function beginVisualDrag(uid, modelData, item) { + const pos = item.mapToItem(root, 0, 0); + + root.draggedUid = uid; + root.draggedModelData = modelData; + root.dragHeight = item.height; + root.dragStartX = pos.x; + root.dragStartY = pos.y; + root.dragX = pos.x; + root.dragY = pos.y; + root.dragActive = true; + root.dropAnimating = false; + root.pendingCommitEntries = []; + } + + function commitVisualOrder(entries) { + const list = []; + + for (let i = 0; i < entries.length; i++) + list.push(entries[i].entry); + + root.object[root.setting] = list; + Config.save(); + root.rebuildVisualEntries(); + } + + function endVisualDrag() { + const entries = root.visualEntries.slice(); + const finalIndex = root.indexForUid(root.draggedUid); + const finalItem = listView.itemAtIndex(finalIndex); + + root.dragActive = false; + + if (!finalItem) { + root.pendingCommitEntries = entries; + root.finishVisualDrag(); + return; + } + + const pos = finalItem.mapToItem(root, 0, 0); + + root.pendingCommitEntries = entries; + root.dropAnimating = true; + settleX.to = pos.x; + settleY.to = pos.y; + settleAnim.start(); + } + + function ensureVisualEntries() { + if (!root.dragActive && !root.dropAnimating) + root.rebuildVisualEntries(); + } + + function finishVisualDrag() { + const entries = root.pendingCommitEntries.slice(); + + root.dragActive = false; + root.dropAnimating = false; + root.draggedUid = ""; + root.draggedModelData = null; + root.pendingCommitEntries = []; + root.dragHeight = 0; + + root.commitVisualOrder(entries); + } + + 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("Active window"); + case "tray": + return qsTr("Tray"); + case "upower": + return qsTr("Power"); + case "network": + return qsTr("Network"); + case "clock": + return qsTr("Clock"); + case "notifBell": + return qsTr("Notification bell"); + default: + return id; + } + } + + function moveArrayItem(list, from, to) { + const next = list.slice(); + const [item] = next.splice(from, 1); + next.splice(to, 0, item); + return next; + } + + function previewVisualMove(from, hovered, before) { + let to = hovered + (before ? 0 : 1); + + if (to > from) + to -= 1; + + to = Math.max(0, Math.min(visualModel.items.count - 1, to)); + + if (from === to) + return; + + visualModel.items.move(from, to); + root.visualEntries = root.moveArrayItem(root.visualEntries, from, to); + } + + 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(); + root.ensureVisualEntries(); + } + + Layout.fillWidth: true + implicitHeight: layout.implicitHeight + + Component.onCompleted: root.rebuildVisualEntries() + + ParallelAnimation { + id: settleAnim + + onFinished: root.finishVisualDrag() + + Anim { + id: settleX + + duration: Appearance.anim.durations.normal + property: "dragX" + target: root + } + + Anim { + id: settleY + + duration: Appearance.anim.durations.normal + property: "dragY" + target: root + } + } + + ColumnLayout { + id: layout + + anchors.fill: parent + spacing: Appearance.spacing.smaller + + CustomText { + Layout.fillWidth: true + font.pointSize: Appearance.font.size.larger + text: root.name + } + + DelegateModel { + id: visualModel + + delegate: entryDelegate + + model: ScriptModel { + objectProp: "uid" + values: root.visualEntries + } + } + + ListView { + id: listView + + Layout.fillWidth: true + Layout.preferredHeight: contentHeight + boundsBehavior: Flickable.StopAtBounds + clip: false + implicitHeight: contentHeight + implicitWidth: width + interactive: !(root.dragActive || root.dropAnimating) + model: visualModel + spacing: Appearance.spacing.small + + add: Transition { + Anim { + properties: "opacity,scale" + to: 1 + } + } + addDisplaced: Transition { + Anim { + duration: Appearance.anim.durations.normal + property: "y" + } + } + displaced: Transition { + Anim { + duration: Appearance.anim.durations.normal + property: "y" + } + } + move: Transition { + Anim { + duration: Appearance.anim.durations.normal + property: "y" + } + } + removeDisplaced: Transition { + Anim { + duration: Appearance.anim.durations.normal + property: "y" + } + } + } + } + + Loader { + active: root.dragActive || root.dropAnimating + asynchronous: false + + sourceComponent: Item { + Drag.active: root.dragActive + Drag.hotSpot.x: width / 2 + Drag.hotSpot.y: height / 2 + height: proxyRect.implicitHeight + implicitHeight: proxyRect.implicitHeight + implicitWidth: listView.width + visible: root.draggedModelData !== null + width: listView.width + x: root.dragX + y: root.dragY + z: 100 + + Drag.source: QtObject { + property string uid: root.draggedUid + property int visualIndex: root.indexForUid(root.draggedUid) + } + + CustomRect { + id: proxyRect + + color: DynamicColors.tPalette.m3surface + implicitHeight: proxyRow.implicitHeight + Appearance.padding.small * 2 + implicitWidth: parent.width + opacity: 0.95 + radius: Appearance.rounding.normal + width: parent.width + + RowLayout { + id: proxyRow + + anchors.fill: parent + anchors.margins: Appearance.padding.small + spacing: Appearance.spacing.normal + + CustomRect { + color: Qt.alpha(DynamicColors.palette.m3onSurface, 0.12) + implicitHeight: 32 + implicitWidth: implicitHeight + radius: Appearance.rounding.small + + MaterialIcon { + anchors.centerIn: parent + color: DynamicColors.palette.m3onSurfaceVariant + text: "drag_indicator" + } + } + + MaterialIcon { + color: DynamicColors.palette.m3onSurfaceVariant + text: root.iconForId(root.draggedModelData?.entry?.id ?? "") + } + + CustomText { + Layout.fillWidth: true + font.pointSize: Appearance.font.size.larger + text: root.labelForId(root.draggedModelData?.entry?.id ?? "") + } + + CustomSwitch { + checked: root.draggedModelData?.entry?.enabled ?? true + enabled: false + } + } + } + } + } + + Component { + id: entryDelegate + + DropArea { + id: slot + + readonly property var entryData: modelData.entry + required property var modelData + readonly property string uid: modelData.uid + + function previewReorder(drag) { + const source = drag.source; + if (!source || !source.uid || source.uid === slot.uid) + return; + + const from = source.visualIndex; + const hovered = slot.DelegateModel.itemsIndex; + + if (from < 0 || hovered < 0) + return; + + root.previewVisualMove(from, hovered, drag.y < height / 2); + } + + height: entryRow.implicitHeight + implicitHeight: entryRow.implicitHeight + implicitWidth: listView.width + width: ListView.view ? ListView.view.width : listView.width + + onEntered: drag => previewReorder(drag) + onPositionChanged: drag => previewReorder(drag) + + CustomRect { + id: entryRow + + anchors.fill: parent + color: DynamicColors.tPalette.m3surface + implicitHeight: entryLayout.implicitHeight + Appearance.padding.small * 2 + implicitWidth: parent.width + opacity: root.draggedUid === slot.uid ? 0 : 1 + radius: Appearance.rounding.full + + Behavior on opacity { + Anim { + } + } + + RowLayout { + id: entryLayout + + anchors.fill: parent + anchors.margins: Appearance.padding.small + spacing: Appearance.spacing.normal + + CustomRect { + id: handle + + color: Qt.alpha(DynamicColors.palette.m3onSurface, handleDrag.active ? 0.12 : handleHover.hovered ? 0.09 : 0.06) + implicitHeight: 32 + implicitWidth: implicitHeight + radius: Appearance.rounding.full + + Behavior on color { + CAnim { + } + } + + MaterialIcon { + anchors.centerIn: parent + color: DynamicColors.palette.m3onSurfaceVariant + text: "drag_indicator" + } + + HoverHandler { + id: handleHover + + cursorShape: handleDrag.active ? Qt.ClosedHandCursor : Qt.OpenHandCursor + } + + DragHandler { + id: handleDrag + + enabled: true + grabPermissions: PointerHandler.CanTakeOverFromAnything + target: null + xAxis.enabled: false + yAxis.enabled: true + + onActiveChanged: { + if (active) { + root.beginVisualDrag(slot.uid, slot.modelData, entryRow); + } else if (root.draggedUid === slot.uid) { + root.endVisualDrag(); + } + } + onActiveTranslationChanged: { + if (!active || root.draggedUid !== slot.uid) + return; + + root.dragX = root.dragStartX; + root.dragY = root.dragStartY + activeTranslation.y; + } + } + } + + MaterialIcon { + color: DynamicColors.palette.m3onSurfaceVariant + text: root.iconForId(slot.entryData.id) + } + + CustomText { + Layout.fillWidth: true + font.pointSize: Appearance.font.size.larger + text: root.labelForId(slot.entryData.id) + } + + CustomSwitch { + Layout.rightMargin: Appearance.padding.small + checked: slot.entryData.enabled ?? true + + onToggled: root.updateEntry(slot.DelegateModel.itemsIndex, checked) + } + } + } + } + } +} diff --git a/Modules/Settings/Controls/SettingInput.qml b/Modules/Settings/Controls/SettingInput.qml index 58ffe24..1421664 100644 --- a/Modules/Settings/Controls/SettingInput.qml +++ b/Modules/Settings/Controls/SettingInput.qml @@ -13,7 +13,6 @@ Item { function formattedValue(): string { const value = root.object[root.setting]; - console.log(value); if (value === null || value === undefined) return ""; @@ -44,15 +43,16 @@ Item { id: rect Layout.preferredHeight: 33 - Layout.preferredWidth: Math.max(Math.min(textField.contentWidth + Appearance.padding.normal * 3, 200), 50) - color: DynamicColors.tPalette.m3surface - radius: Appearance.rounding.small + Layout.preferredWidth: Math.max(Math.min(textField.contentWidth + Appearance.padding.normal * 2, 550), 50) + color: DynamicColors.tPalette.m3surfaceContainerHigh + radius: Appearance.rounding.full CustomTextField { id: textField anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter + implicitWidth: Math.min(contentWidth, 550) text: root.formattedValue() onEditingFinished: { diff --git a/Modules/Settings/Controls/SettingList.qml b/Modules/Settings/Controls/SettingList.qml index 2a905ca..3d2dd66 100644 --- a/Modules/Settings/Controls/SettingList.qml +++ b/Modules/Settings/Controls/SettingList.qml @@ -236,8 +236,7 @@ Item { font.pointSize: Appearance.font.size.large icon: "add" - onClicked: console.log(button.width) - // onClicked: root.addActiveActionRequested() + onClicked: root.addActiveActionRequested() } CustomText { diff --git a/Modules/Settings/Controls/SettingReadOnly.qml b/Modules/Settings/Controls/SettingReadOnly.qml new file mode 100644 index 0000000..1fe580a --- /dev/null +++ b/Modules/Settings/Controls/SettingReadOnly.qml @@ -0,0 +1,36 @@ +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config + +Item { + id: root + + required property string name + required property string value + + Layout.fillWidth: true + Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2 + + RowLayout { + id: row + + anchors.left: parent.left + anchors.margins: Appearance.padding.small + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + + 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 + } + } +} diff --git a/Modules/Settings/Controls/SettingSpinBox.qml b/Modules/Settings/Controls/SettingSpinBox.qml new file mode 100644 index 0000000..0cbf240 --- /dev/null +++ b/Modules/Settings/Controls/SettingSpinBox.qml @@ -0,0 +1,47 @@ +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config + +Item { + id: root + + required property string name + required property var object + required property string setting + property real max: Infinity + property real min: -Infinity + property real step: 1 + + Layout.fillWidth: true + Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2 + + RowLayout { + id: row + + anchors.left: parent.left + anchors.margins: Appearance.padding.small + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + + 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(); + } + } + } +} diff --git a/Modules/Settings/Controls/SettingSpinner.qml b/Modules/Settings/Controls/SettingSpinner.qml index 960e5ca..2663343 100644 --- a/Modules/Settings/Controls/SettingSpinner.qml +++ b/Modules/Settings/Controls/SettingSpinner.qml @@ -18,7 +18,6 @@ Item { function formattedValue(setting: string): string { const value = root.object[setting]; - console.log(value); if (value === null || value === undefined) return ""; diff --git a/Modules/Settings/Controls/SettingStringList.qml b/Modules/Settings/Controls/SettingStringList.qml new file mode 100644 index 0000000..c78414b --- /dev/null +++ b/Modules/Settings/Controls/SettingStringList.qml @@ -0,0 +1,41 @@ +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config + +Item { + id: root + + required property string name + required property var object + required property string setting + property string addLabel: qsTr("Add entry") + + Layout.fillWidth: true + Layout.preferredHeight: layout.implicitHeight + + 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 + values: [...(root.object[root.setting] ?? [])] + + onListEdited: function (values) { + root.object[root.setting] = values; + Config.save(); + } + } + } +} diff --git a/Modules/Settings/Controls/SettingsHeader.qml b/Modules/Settings/Controls/SettingsHeader.qml new file mode 100644 index 0000000..257757a --- /dev/null +++ b/Modules/Settings/Controls/SettingsHeader.qml @@ -0,0 +1,21 @@ +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config + +CustomRect { + id: root + + required property string name + + Layout.preferredHeight: 60 + Layout.preferredWidth: 200 + + CustomText { + anchors.fill: parent + font.bold: true + font.pointSize: Appearance.font.size.large * 2 + text: root.name + verticalAlignment: Text.AlignVCenter + } +} diff --git a/Modules/Settings/Controls/SettingsPage.qml b/Modules/Settings/Controls/SettingsPage.qml new file mode 100644 index 0000000..1cfefec --- /dev/null +++ b/Modules/Settings/Controls/SettingsPage.qml @@ -0,0 +1,41 @@ +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config +import qs.Helpers + +CustomFlickable { + id: root + + default property alias contentData: clayout.data + + contentHeight: clayout.implicitHeight + + TapHandler { + acceptedButtons: Qt.LeftButton + + onTapped: function (eventPoint) { + const menu = SettingsDropdowns.activeMenu; + if (!menu) + return; + + const p = eventPoint.scenePosition; + + if (SettingsDropdowns.hit(SettingsDropdowns.activeTrigger, p)) + return; + + if (SettingsDropdowns.hit(menu, p)) + return; + + SettingsDropdowns.closeActive(); + } + } + + ColumnLayout { + id: clayout + + anchors.left: parent.left + anchors.right: parent.right + spacing: Appearance.spacing.small + } +} diff --git a/Modules/Settings/Controls/SettingsSection.qml b/Modules/Settings/Controls/SettingsSection.qml new file mode 100644 index 0000000..081447a --- /dev/null +++ b/Modules/Settings/Controls/SettingsSection.qml @@ -0,0 +1,26 @@ +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 + + Layout.fillWidth: true + Layout.preferredHeight: layout.implicitHeight + contentPadding * 2 + color: DynamicColors.tPalette.m3surfaceContainer + radius: Appearance.rounding.normal - Appearance.padding.smaller + + ColumnLayout { + id: layout + + anchors.left: parent.left + anchors.margins: root.contentPadding + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: Appearance.spacing.normal + } +} diff --git a/Modules/Settings/Controls/StringListEditor.qml b/Modules/Settings/Controls/StringListEditor.qml new file mode 100644 index 0000000..701e334 --- /dev/null +++ b/Modules/Settings/Controls/StringListEditor.qml @@ -0,0 +1,107 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config + +ColumnLayout { + id: root + + property string addLabel: qsTr("Add entry") + 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); + } + + Layout.fillWidth: true + spacing: Appearance.spacing.smaller + + 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 + } + } +} diff --git a/Modules/TrayItem.qml b/Modules/TrayItem.qml index 9580969..50fbf62 100644 --- a/Modules/TrayItem.qml +++ b/Modules/TrayItem.qml @@ -28,9 +28,10 @@ Item { root.popouts.currentName = `traymenu${root.ind}`; root.popouts.currentCenter = Qt.binding(() => root.mapToItem(root.loader, root.implicitWidth / 2, 0).x); root.popouts.hasCurrent = true; - if (visibilities.sidebar || visibilities.dashboard) { + if (visibilities.sidebar || visibilities.dashboard || visibilities.settings) { visibilities.sidebar = false; visibilities.dashboard = false; + visibilities.settings = false; } } }