From 860542314e96835f02c0807e541ecc56bc4dcc4b Mon Sep 17 00:00:00 2001 From: bbedward Date: Tue, 22 Sep 2026 18:11:12 -0400 Subject: [PATCH] settings: re-org launcehr tab built-in plugins, make desktop widgets sub-pages to match the pattern of bar widgets --- quickshell/Common/SettingsTabs.qml | 14 +- quickshell/Common/SettingsUiState.qml | 1 + .../Modals/Settings/SettingsContent.qml | 1 + .../Settings/DesktopWidgetGroupSection.qml | 6 +- .../Settings/DesktopWidgetInstanceCard.qml | 247 +---------- .../DesktopWidgetSettings/ClockSettings.qml | 14 - .../DesktopWidgetInstanceSettings.qml | 103 ++--- .../SystemMonitorSettings.qml | 138 +----- .../Modules/Settings/DesktopWidgetTab.qml | 189 +++++++++ .../Modules/Settings/DesktopWidgetsTab.qml | 17 +- quickshell/Modules/Settings/LauncherTab.qml | 397 +++++++----------- .../Modules/Settings/PluginSettingsPage.qml | 109 +++-- .../Settings/Widgets/SettingsColorPicker.qml | 151 +++---- .../Settings/Widgets/SettingsReorderList.qml | 2 + .../Settings/Widgets/SettingsReorderRow.qml | 4 +- .../Modules/Settings/Widgets/SettingsRow.qml | 12 +- .../translations/extract_settings_index.py | 1 + .../translations/settings_search_index.json | 123 ++++-- 18 files changed, 667 insertions(+), 862 deletions(-) create mode 100644 quickshell/Modules/Settings/DesktopWidgetTab.qml diff --git a/quickshell/Common/SettingsTabs.qml b/quickshell/Common/SettingsTabs.qml index bb123da11..aa08b073a 100644 --- a/quickshell/Common/SettingsTabs.qml +++ b/quickshell/Common/SettingsTabs.qml @@ -200,7 +200,19 @@ Singleton { "text": I18n.tr("Desktop widgets"), "icon": "widgets", "tabIndex": 27, - "hint": I18n.tr("Clocks, system monitors, plugins") + "hubHeader": "DesktopWidgetsTab", + "hint": I18n.tr("Clocks, system monitors, plugins"), + "children": [ + { + "id": "desktop_widget", + "hidden": true, + "titleFrom": "selectedWidgetTitle", + "text": I18n.tr("Widget settings"), + "icon": "tune", + "tabIndex": 63, + "hint": I18n.tr("Options of one widget instance") + } + ] }, { "id": "separator_2", diff --git a/quickshell/Common/SettingsUiState.qml b/quickshell/Common/SettingsUiState.qml index 0efb604ab..907b8239a 100644 --- a/quickshell/Common/SettingsUiState.qml +++ b/quickshell/Common/SettingsUiState.qml @@ -14,6 +14,7 @@ Singleton { // Set only while the widget settings page is showing a dock-hosted widget; empty means bar-hosted. property string selectedDockId: "" property string selectedDockWidgetId: "" + property string selectedDesktopWidgetId: "" property string selectedWidgetSection: "" property int selectedWidgetIndex: -1 property string selectedWidgetTitle: "" diff --git a/quickshell/Modals/Settings/SettingsContent.qml b/quickshell/Modals/Settings/SettingsContent.qml index 890d35f85..f33d1601b 100644 --- a/quickshell/Modals/Settings/SettingsContent.qml +++ b/quickshell/Modals/Settings/SettingsContent.qml @@ -218,6 +218,7 @@ FocusScope { "power_sleep": "PowerSleepTab.qml", "clipboard": "ClipboardTab.qml", "desktop_widgets": "DesktopWidgetsTab.qml", + "desktop_widget": "DesktopWidgetTab.qml", "audio": "AudioTab.qml", "locale": "LocaleTab.qml", "multiplexers": "MuxTab.qml", diff --git a/quickshell/Modules/Settings/DesktopWidgetGroupSection.qml b/quickshell/Modules/Settings/DesktopWidgetGroupSection.qml index bccaa2815..430eab50e 100644 --- a/quickshell/Modules/Settings/DesktopWidgetGroupSection.qml +++ b/quickshell/Modules/Settings/DesktopWidgetGroupSection.qml @@ -16,14 +16,13 @@ Column { property bool showHeader: true property bool collapsed: false property var instances: [] - property var expandedStates: ({}) readonly property string sectionKey: groupId ? groupId : "" readonly property bool dragActive: reorderGroup.active readonly property bool isDropTarget: dragActive && reorderGroup.target === cardsList signal collapseToggled(string key) - signal expandedToggled(string instanceId, bool expanded) + signal configureRequested(string instanceId, string title) signal duplicateRequested(string instanceId) signal deleteRequested(string instanceId) @@ -74,10 +73,9 @@ Column { reorderList: cardsList instanceData: modelData - isExpanded: section.expandedStates[instanceId] ?? false opacity: dragging && cardsList.crossSectionActive ? 0 : 1 - onExpandedToggled: expanded => section.expandedToggled(instanceId, expanded) + onConfigureRequested: section.configureRequested(instanceId, widgetName) onDuplicateRequested: section.duplicateRequested(instanceId) onDeleteRequested: section.deleteRequested(instanceId) } diff --git a/quickshell/Modules/Settings/DesktopWidgetInstanceCard.qml b/quickshell/Modules/Settings/DesktopWidgetInstanceCard.qml index 9c456bfc1..3eb3f51a4 100644 --- a/quickshell/Modules/Settings/DesktopWidgetInstanceCard.qml +++ b/quickshell/Modules/Settings/DesktopWidgetInstanceCard.qml @@ -2,18 +2,15 @@ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls -import Quickshell import qs.Common import qs.Services import qs.Widgets import qs.Modules.Settings.Widgets -import qs.Modules.Settings.DesktopWidgetSettings as DWS SettingsReorderRow { id: root required property var instanceData - property bool isExpanded: false property bool confirmingDelete: false readonly property string instanceId: instanceData?.id ?? "" @@ -21,34 +18,29 @@ SettingsReorderRow { readonly property var widgetDef: DesktopWidgetRegistry.getWidget(widgetType) readonly property string widgetName: instanceData?.name ?? widgetDef?.name ?? widgetType - signal expandedToggled(bool expanded) + signal configureRequested signal deleteRequested signal duplicateRequested - property Component clockSettingsComponent: Component { - DWS.ClockSettings {} - } - - property Component systemMonitorSettingsComponent: Component { - DWS.SystemMonitorSettings {} - } - - property Component pluginSettingsComponent: Component { - DWS.PluginDesktopWidgetSettings { - instanceId: root.instanceId - instanceData: root.instanceData - widgetType: root.widgetType - widgetDef: root.widgetDef - } - } - - width: parent?.width ?? 400 iconName: widgetDef?.icon ?? "widgets" title: widgetName clickable: true - onClicked: expandedToggled(!isExpanded) + onClicked: configureRequested() trailing: [ + DankIcon { + anchors.verticalCenter: parent.verticalCenter + name: "chevron_right" + size: Theme.iconSize + color: Theme.onSurfaceVariant + rotation: I18n.isRtl ? 180 : 0 + }, + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: Theme.dividerWidth + height: SettingsMetrics.splitDividerHeight + color: Theme.outlineVariant + }, DankToggle { anchors.verticalCenter: parent.verticalCenter hideText: true @@ -173,215 +165,6 @@ SettingsReorderRow { } } } - }, - DankIcon { - anchors.verticalCenter: parent.verticalCenter - name: root.isExpanded ? "expand_less" : "expand_more" - size: Theme.iconSize - color: Theme.onSurfaceVariant } ] - - body: Column { - width: parent.width - visible: root.isExpanded - height: root.isExpanded ? implicitHeight : 0 - spacing: 0 - - SettingsTextFieldRow { - leftIconName: "badge" - text: I18n.tr("Name") - value: root.widgetName - onEditingFinished: value => SettingsData.updateDesktopWidgetInstance(root.instanceId, { - name: value - }) - } - - SettingsDivider {} - - SettingsDropdownRow { - readonly property var groupsData: { - const items = [ - { - value: "", - label: I18n.tr("None") - } - ]; - for (const g of SettingsData.desktopWidgetGroups || []) { - items.push({ - value: g.id, - label: g.name - }); - } - return items; - } - - visible: (SettingsData.desktopWidgetGroups || []).length > 0 - text: I18n.tr("Group", "noun, dropdown label for a desktop widget group") - options: groupsData.map(g => g.label) - currentValue: groupsData.find(g => g.value === (root.instanceData?.group ?? ""))?.label ?? I18n.tr("None") - onValueChanged: value => SettingsData.updateDesktopWidgetInstance(root.instanceId, { - group: groupsData.find(g => g.label === value)?.value || null - }) - } - - SettingsDivider { - visible: (SettingsData.desktopWidgetGroups || []).length > 0 - } - - SettingsToggleRow { - text: I18n.tr("Show on overlay") - checked: instanceData?.config?.showOnOverlay ?? false - onToggled: isChecked => { - SettingsData.updateDesktopWidgetInstanceConfig(root.instanceId, { - showOnOverlay: isChecked - }); - } - } - - SettingsDivider { - visible: CompositorService.isNiri - } - - SettingsToggleRow { - visible: CompositorService.isNiri - text: I18n.tr("Show on overview") - checked: instanceData?.config?.showOnOverview ?? false - onToggled: isChecked => { - SettingsData.updateDesktopWidgetInstanceConfig(root.instanceId, { - showOnOverview: isChecked - }); - } - } - - SettingsDivider { - visible: CompositorService.isNiri - } - - SettingsToggleRow { - visible: CompositorService.isNiri - text: I18n.tr("Show on overview only") - checked: instanceData?.config?.showOnOverviewOnly ?? false - onToggled: isChecked => { - SettingsData.updateDesktopWidgetInstanceConfig(root.instanceId, { - showOnOverviewOnly: isChecked - }); - } - } - - SettingsDivider {} - - SettingsToggleRow { - text: I18n.tr("Click through") - checked: instanceData?.config?.clickThrough ?? false - onToggled: isChecked => { - SettingsData.updateDesktopWidgetInstanceConfig(root.instanceId, { - clickThrough: isChecked - }); - } - } - - SettingsDivider {} - - SettingsToggleRow { - text: I18n.tr("Sync position across displays") - checked: instanceData?.config?.syncPositionAcrossScreens ?? false - onToggled: isChecked => { - if (isChecked) - SessionData.syncDesktopWidgetPositionToAllScreens(root.instanceId); - SettingsData.updateDesktopWidgetInstanceConfig(root.instanceId, { - syncPositionAcrossScreens: isChecked - }); - } - } - - SettingsDivider {} - - Item { - width: parent.width - height: ipcColumn.height + Theme.spacingM * 2 - - Column { - id: ipcColumn - x: Theme.spacingM - width: parent.width - Theme.spacingM * 2 - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingXS - - StyledText { - text: I18n.tr("Command") - font.pixelSize: Theme.fontSizeMedium - color: Theme.surfaceText - width: parent.width - horizontalAlignment: Text.AlignLeft - } - - Rectangle { - width: parent.width - height: ipcText.height + Theme.spacingS * 2 - radius: Theme.cornerRadiusS - color: Theme.chipSurface - - Row { - x: Theme.spacingS - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingS - width: parent.width - Theme.spacingS * 2 - - StyledText { - id: ipcText - text: "dms ipc call desktopWidget toggleOverlay " + root.instanceId - font.pixelSize: Theme.fontSizeSmall - font.family: Theme.monoFontFamily - color: Theme.surfaceVariantText - width: parent.width - copyBtn.width - Theme.spacingS - elide: Text.ElideMiddle - anchors.verticalCenter: parent.verticalCenter - } - - DankButton { - id: copyBtn - iconName: "content_copy" - Accessible.name: I18n.tr("Copy") - backgroundColor: "transparent" - textColor: Theme.surfaceText - buttonHeight: 28 - horizontalPadding: 4 - anchors.verticalCenter: parent.verticalCenter - onClicked: { - Quickshell.execDetached(["dms", "cl", "copy", "dms ipc call desktopWidget toggleOverlay " + root.instanceId]); - ToastService.showInfo(I18n.tr("Copied to clipboard")); - } - } - } - } - } - } - - SettingsDivider {} - - Loader { - id: settingsLoader - width: parent.width - active: root.isExpanded && root.widgetType !== "" - - sourceComponent: { - switch (root.widgetType) { - case "desktopClock": - return clockSettingsComponent; - case "systemMonitor": - return systemMonitorSettingsComponent; - default: - return pluginSettingsComponent; - } - } - - onLoaded: { - if (!item) - return; - item.instanceId = root.instanceId; - item.instanceData = Qt.binding(() => root.instanceData); - } - } - } } diff --git a/quickshell/Modules/Settings/DesktopWidgetSettings/ClockSettings.qml b/quickshell/Modules/Settings/DesktopWidgetSettings/ClockSettings.qml index 1542afafd..8859b469f 100644 --- a/quickshell/Modules/Settings/DesktopWidgetSettings/ClockSettings.qml +++ b/quickshell/Modules/Settings/DesktopWidgetSettings/ClockSettings.qml @@ -34,10 +34,6 @@ DesktopWidgetInstanceSettings { } } - SettingsDivider { - visible: root.analog - } - SettingsToggleRow { visible: root.analog text: I18n.tr("Show hour numbers") @@ -45,10 +41,6 @@ DesktopWidgetInstanceSettings { onToggled: checked => root.updateConfig("showAnalogNumbers", checked) } - SettingsDivider { - visible: root.analog - } - SettingsToggleRow { visible: root.analog text: I18n.tr("Show seconds") @@ -56,10 +48,6 @@ DesktopWidgetInstanceSettings { onToggled: checked => root.updateConfig("showAnalogSeconds", checked) } - SettingsDivider { - visible: !root.analog - } - SettingsToggleRow { visible: !root.analog text: I18n.tr("Show seconds") @@ -67,8 +55,6 @@ DesktopWidgetInstanceSettings { onToggled: checked => root.updateConfig("showDigitalSeconds", checked) } - SettingsDivider {} - SettingsToggleRow { text: I18n.tr("Show date") checked: root.cfg.showDate ?? true diff --git a/quickshell/Modules/Settings/DesktopWidgetSettings/DesktopWidgetInstanceSettings.qml b/quickshell/Modules/Settings/DesktopWidgetSettings/DesktopWidgetInstanceSettings.qml index 1c69c3cb8..0f0679dca 100644 --- a/quickshell/Modules/Settings/DesktopWidgetSettings/DesktopWidgetInstanceSettings.qml +++ b/quickshell/Modules/Settings/DesktopWidgetSettings/DesktopWidgetInstanceSettings.qml @@ -1,6 +1,5 @@ import QtQuick import qs.Common -import qs.Widgets import qs.Modules.Settings.Widgets Column { @@ -11,7 +10,7 @@ Column { property bool showAppearance: true property bool showPlacement: true readonly property var cfg: instanceData?.config ?? {} - default property alias rows: rowsColumn.data + default property alias rows: optionsCard.content function updateConfig(key, value) { const updates = {}; @@ -19,78 +18,54 @@ Column { SettingsData.updateDesktopWidgetInstanceConfig(instanceId, updates); } - width: parent?.width ?? 400 - spacing: 0 + width: parent?.width ?? 0 + spacing: Theme.spacingL - Column { - id: rowsColumn - width: parent.width - spacing: 0 + SettingsCard { + id: optionsCard } - SettingsDivider { + SettingsCard { + title: I18n.tr("Appearance") visible: root.showAppearance + + SettingsSliderRow { + text: I18n.tr("Opacity") + minimum: 0 + maximum: 100 + value: Math.round((root.cfg.transparency ?? 0.8) * 100) + onSliderValueChanged: newValue => root.updateConfig("transparency", newValue / 100) + } + + SettingsColorPicker { + colorMode: root.cfg.colorMode ?? "primary" + customColor: root.cfg.customColor ?? "#ffffff" + onColorModeSelected: mode => root.updateConfig("colorMode", mode) + onCustomColorSelected: selectedColor => root.updateConfig("customColor", selectedColor.toString()) + } } - SettingsSliderRow { - visible: root.showAppearance - text: I18n.tr("Opacity") - minimum: 0 - maximum: 100 - value: Math.round((root.cfg.transparency ?? 0.8) * 100) - onSliderValueChanged: newValue => root.updateConfig("transparency", newValue / 100) - } - - SettingsDivider { - visible: root.showAppearance - } - - SettingsColorPicker { - visible: root.showAppearance - colorMode: root.cfg.colorMode ?? "primary" - customColor: root.cfg.customColor ?? "#ffffff" - onColorModeSelected: mode => root.updateConfig("colorMode", mode) - onCustomColorSelected: selectedColor => root.updateConfig("customColor", selectedColor.toString()) - } - - SettingsDivider { - visible: root.showAppearance && root.showPlacement - } - - SettingsDisplayPicker { + SettingsCard { + title: I18n.tr("Displays") visible: root.showPlacement - displayPreferences: root.cfg.displayPreferences ?? ["all"] - onPreferencesChanged: prefs => root.updateConfig("displayPreferences", prefs) - } - SettingsDivider { - visible: root.showPlacement - } + SettingsDisplayPicker { + displayPreferences: root.cfg.displayPreferences ?? ["all"] + onPreferencesChanged: prefs => root.updateConfig("displayPreferences", prefs) + } - Item { - visible: root.showPlacement - width: parent.width - height: resetRow.height + Theme.spacingM * 2 + SettingsRow { + iconName: "drag_pan" + title: I18n.tr("Reset Position") + clickable: true + onClicked: SessionData.resetDesktopWidgetInstanceGeometry(root.instanceId, ["x", "y"]) + } - Row { - id: resetRow - x: Theme.spacingM - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingM - - DankButton { - text: I18n.tr("Reset Position") - backgroundColor: Theme.chipSurface - textColor: Theme.surfaceText - onClicked: SessionData.resetDesktopWidgetInstanceGeometry(root.instanceId, ["x", "y"]) - } - - DankButton { - text: I18n.tr("Reset Size") - backgroundColor: Theme.chipSurface - textColor: Theme.surfaceText - onClicked: SessionData.resetDesktopWidgetInstanceGeometry(root.instanceId, ["width", "height"]) - } + SettingsRow { + iconName: "open_in_full" + title: I18n.tr("Reset Size") + clickable: true + onClicked: SessionData.resetDesktopWidgetInstanceGeometry(root.instanceId, ["width", "height"]) } } } diff --git a/quickshell/Modules/Settings/DesktopWidgetSettings/SystemMonitorSettings.qml b/quickshell/Modules/Settings/DesktopWidgetSettings/SystemMonitorSettings.qml index 30a19377f..440db5779 100644 --- a/quickshell/Modules/Settings/DesktopWidgetSettings/SystemMonitorSettings.qml +++ b/quickshell/Modules/Settings/DesktopWidgetSettings/SystemMonitorSettings.qml @@ -3,20 +3,20 @@ pragma ComponentBehavior: Bound import QtQuick import qs.Common import qs.Services -import qs.Widgets import qs.Modules.Settings.Widgets DesktopWidgetInstanceSettings { id: root + readonly property var gpus: DgopService.availableGpus ?? [] + readonly property var gpuLabels: gpus.map(gpu => (gpu.driver ? gpu.driver.toUpperCase() + " " : "") + (gpu.displayName ?? "")) + SettingsToggleRow { text: I18n.tr("Show header") checked: root.cfg.showHeader ?? true onToggled: checked => root.updateConfig("showHeader", checked) } - SettingsDivider {} - SettingsButtonGroupRow { readonly property var intervals: [60, 300, 600, 1800] text: I18n.tr("Graph time range") @@ -30,18 +30,12 @@ DesktopWidgetInstanceSettings { } } - SettingsDivider {} - SettingsToggleRow { text: I18n.tr("CPU") checked: root.cfg.showCpu ?? true onToggled: checked => root.updateConfig("showCpu", checked) } - SettingsDivider { - visible: root.cfg.showCpu ?? true - } - SettingsToggleRow { enabled: root.cfg.showCpu ?? true text: I18n.tr("CPU graph") @@ -49,10 +43,6 @@ DesktopWidgetInstanceSettings { onToggled: checked => root.updateConfig("showCpuGraph", checked) } - SettingsDivider { - visible: root.cfg.showCpu ?? true - } - SettingsToggleRow { enabled: root.cfg.showCpu ?? true text: I18n.tr("CPU temperature") @@ -60,112 +50,32 @@ DesktopWidgetInstanceSettings { onToggled: checked => root.updateConfig("showCpuTemp", checked) } - SettingsDivider {} - SettingsToggleRow { text: I18n.tr("GPU temperature") checked: root.cfg.showGpuTemp ?? false onToggled: checked => root.updateConfig("showGpuTemp", checked) } - SettingsDivider { - visible: (root.cfg.showGpuTemp ?? false) && DgopService.availableGpus.length > 0 - } - - Item { - width: parent.width - height: gpuSelectColumn.height + Theme.spacingM * 2 - visible: (root.cfg.showGpuTemp ?? false) && DgopService.availableGpus.length > 0 - - Column { - id: gpuSelectColumn - width: parent.width - Theme.spacingM * 2 - x: Theme.spacingM - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingS - - StyledText { - text: I18n.tr("GPU", "graphics processor label in system monitor") - font.pixelSize: Theme.fontSizeMedium - color: Theme.surfaceText - } - - Column { - width: parent.width - spacing: Theme.spacingXS - - Repeater { - model: DgopService.availableGpus - - Rectangle { - required property var modelData - - readonly property bool isSelected: (root.cfg.gpuPciId ?? "") === modelData.pciId - - width: parent.width - height: 44 - radius: Theme.cornerRadius - color: isSelected ? Theme.primarySelected : Theme.chipSurface - border.color: isSelected ? Theme.primary : Theme.withAlpha(Theme.primary, 0) - border.width: Theme.outlineWidthFocused - - Row { - anchors.fill: parent - anchors.margins: Theme.spacingS - spacing: Theme.spacingS - - DankIcon { - name: "videocam" - size: Theme.iconSizeSmall - color: isSelected ? Theme.primary : Theme.surfaceVariantText - anchors.verticalCenter: parent.verticalCenter - } - - Column { - width: parent.width - Theme.iconSizeSmall - Theme.spacingS - anchors.verticalCenter: parent.verticalCenter - spacing: 0 - - StyledText { - text: modelData.displayName || "Unknown GPU" - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceText - width: parent.width - elide: Text.ElideRight - } - - StyledText { - text: modelData.driver || "" - font.pixelSize: Theme.fontSizeSmall - 2 - color: Theme.surfaceVariantText - visible: text !== "" - } - } - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: root.updateConfig("gpuPciId", modelData.pciId) - } - } - } - } + SettingsDropdownRow { + visible: root.cfg.showGpuTemp ?? false + text: I18n.tr("GPU") + options: root.gpuLabels + emptyText: I18n.tr("No GPU detected", "empty state when no graphics card is found") + currentValue: root.gpuLabels[root.gpus.findIndex(gpu => gpu.pciId === root.cfg.gpuPciId)] ?? "" + onValueChanged: value => { + const index = root.gpuLabels.indexOf(value); + if (index < 0) + return; + root.updateConfig("gpuPciId", root.gpus[index].pciId); } } - SettingsDivider {} - SettingsToggleRow { text: I18n.tr("Memory") checked: root.cfg.showMemory ?? true onToggled: checked => root.updateConfig("showMemory", checked) } - SettingsDivider { - visible: root.cfg.showMemory ?? true - } - SettingsToggleRow { enabled: root.cfg.showMemory ?? true text: I18n.tr("Memory graph") @@ -173,18 +83,12 @@ DesktopWidgetInstanceSettings { onToggled: checked => root.updateConfig("showMemoryGraph", checked) } - SettingsDivider {} - SettingsToggleRow { text: I18n.tr("Network") checked: root.cfg.showNetwork ?? true onToggled: checked => root.updateConfig("showNetwork", checked) } - SettingsDivider { - visible: root.cfg.showNetwork ?? true - } - SettingsToggleRow { enabled: root.cfg.showNetwork ?? true text: I18n.tr("Network graph") @@ -192,26 +96,18 @@ DesktopWidgetInstanceSettings { onToggled: checked => root.updateConfig("showNetworkGraph", checked) } - SettingsDivider {} - SettingsToggleRow { text: I18n.tr("Disk") checked: root.cfg.showDisk ?? true onToggled: checked => root.updateConfig("showDisk", checked) } - SettingsDivider {} - SettingsToggleRow { text: I18n.tr("Top processes") checked: root.cfg.showTopProcesses ?? false onToggled: checked => root.updateConfig("showTopProcesses", checked) } - SettingsDivider { - visible: root.cfg.showTopProcesses ?? false - } - SettingsButtonGroupRow { readonly property var counts: [3, 5, 10] visible: root.cfg.showTopProcesses ?? false @@ -226,10 +122,6 @@ DesktopWidgetInstanceSettings { } } - SettingsDivider { - visible: root.cfg.showTopProcesses ?? false - } - SettingsButtonGroupRow { visible: root.cfg.showTopProcesses ?? false text: I18n.tr("Sort by") @@ -243,8 +135,6 @@ DesktopWidgetInstanceSettings { } } - SettingsDivider {} - SettingsDropdownRow { text: I18n.tr("Layout") options: [I18n.tr("Auto"), I18n.tr("Grid"), I18n.tr("List")] diff --git a/quickshell/Modules/Settings/DesktopWidgetTab.qml b/quickshell/Modules/Settings/DesktopWidgetTab.qml new file mode 100644 index 000000000..08f550098 --- /dev/null +++ b/quickshell/Modules/Settings/DesktopWidgetTab.qml @@ -0,0 +1,189 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell +import qs.Common +import qs.Services +import qs.Widgets +import qs.Modules.Settings.Widgets +import qs.Modules.Settings.DesktopWidgetSettings as DWS + +Item { + id: root + + readonly property string instanceId: SettingsUiState.selectedDesktopWidgetId + readonly property var instanceData: (SettingsData.desktopWidgetInstances || []).find(instance => instance.id === instanceId) ?? null + readonly property string widgetType: instanceData?.widgetType ?? "" + readonly property var widgetDef: DesktopWidgetRegistry.getWidget(widgetType) + readonly property string widgetName: instanceData?.name ?? widgetDef?.name ?? widgetType + readonly property var cfg: instanceData?.config ?? {} + readonly property string overlayCommand: "dms ipc call desktopWidget toggleOverlay " + instanceId + readonly property var groupOptions: [ + { + "value": "", + "label": I18n.tr("None") + } + ].concat((SettingsData.desktopWidgetGroups || []).map(group => ({ + "value": group.id, + "label": group.name + }))) + + function updateConfig(key, value) { + const updates = {}; + updates[key] = value; + SettingsData.updateDesktopWidgetInstanceConfig(instanceId, updates); + } + + onWidgetNameChanged: SettingsUiState.selectedWidgetTitle = widgetName + + Component { + id: clockSettings + + DWS.ClockSettings { + instanceId: root.instanceId + instanceData: root.instanceData + } + } + + Component { + id: systemMonitorSettings + + DWS.SystemMonitorSettings { + instanceId: root.instanceId + instanceData: root.instanceData + } + } + + Component { + id: pluginSettings + + DWS.PluginDesktopWidgetSettings { + instanceId: root.instanceId + instanceData: root.instanceData + widgetType: root.widgetType + widgetDef: root.widgetDef + } + } + + SettingsPage { + visible: root.instanceData !== null + + SettingsCard { + SettingsToggleRow { + iconName: root.widgetDef?.icon ?? "widgets" + text: root.widgetDef?.name ?? root.widgetType + description: root.widgetDef?.description ?? "" + checked: root.instanceData?.enabled ?? true + onToggled: checked => SettingsData.updateDesktopWidgetInstance(root.instanceId, { + "enabled": checked + }) + } + + SettingsTextFieldRow { + leftIconName: "badge" + text: I18n.tr("Name") + value: root.widgetName + onEditingFinished: value => SettingsData.updateDesktopWidgetInstance(root.instanceId, { + "name": value + }) + } + + SettingsDropdownRow { + visible: root.groupOptions.length > 1 + text: I18n.tr("Group", "noun, dropdown label for a desktop widget group") + options: root.groupOptions.map(group => group.label) + currentValue: root.groupOptions.find(group => group.value === (root.instanceData?.group ?? ""))?.label ?? I18n.tr("None") + onValueChanged: value => SettingsData.updateDesktopWidgetInstance(root.instanceId, { + "group": root.groupOptions.find(group => group.label === value)?.value || null + }) + } + } + + Loader { + width: parent.width + active: root.instanceData !== null + sourceComponent: { + switch (root.widgetType) { + case "desktopClock": + return clockSettings; + case "systemMonitor": + return systemMonitorSettings; + default: + return pluginSettings; + } + } + } + + SettingsCard { + title: I18n.tr("Behavior") + + SettingsToggleRow { + text: I18n.tr("Show on overlay") + checked: root.cfg.showOnOverlay ?? false + onToggled: checked => root.updateConfig("showOnOverlay", checked) + } + + SettingsToggleRow { + visible: CompositorService.isNiri + text: I18n.tr("Show on overview") + checked: root.cfg.showOnOverview ?? false + onToggled: checked => root.updateConfig("showOnOverview", checked) + } + + SettingsToggleRow { + visible: CompositorService.isNiri + text: I18n.tr("Show on overview only") + checked: root.cfg.showOnOverviewOnly ?? false + onToggled: checked => root.updateConfig("showOnOverviewOnly", checked) + } + + SettingsToggleRow { + text: I18n.tr("Click through") + checked: root.cfg.clickThrough ?? false + onToggled: checked => root.updateConfig("clickThrough", checked) + } + + SettingsToggleRow { + text: I18n.tr("Sync position across displays") + checked: root.cfg.syncPositionAcrossScreens ?? false + onToggled: checked => { + if (checked) + SessionData.syncDesktopWidgetPositionToAllScreens(root.instanceId); + root.updateConfig("syncPositionAcrossScreens", checked); + } + } + } + + SettingsCard { + title: I18n.tr("Command") + + SettingsRow { + body: Row { + width: parent.width + spacing: Theme.spacingS + + StyledText { + anchors.verticalCenter: parent.verticalCenter + width: parent.width - copyButton.width - parent.spacing + text: root.overlayCommand + font.pixelSize: Theme.fontSizeSmall + font.family: Theme.monoFontFamily + color: Theme.surfaceVariantText + elide: Text.ElideMiddle + } + + DankActionButton { + id: copyButton + anchors.verticalCenter: parent.verticalCenter + iconName: "content_copy" + Accessible.name: I18n.tr("Copy") + onClicked: { + Quickshell.execDetached(["dms", "cl", "copy", root.overlayCommand]); + ToastService.showInfo(I18n.tr("Copied to clipboard")); + } + } + } + } + } + } +} diff --git a/quickshell/Modules/Settings/DesktopWidgetsTab.qml b/quickshell/Modules/Settings/DesktopWidgetsTab.qml index 898c2263f..ccbc0e50e 100644 --- a/quickshell/Modules/Settings/DesktopWidgetsTab.qml +++ b/quickshell/Modules/Settings/DesktopWidgetsTab.qml @@ -13,7 +13,6 @@ Item { LayoutMirroring.enabled: I18n.isRtl LayoutMirroring.childrenInherit: true - property var expandedStates: ({}) property var groupCollapsedStates: ({}) property var parentModal: null property string editingGroupId: "" @@ -43,12 +42,10 @@ Item { groupCollapsedStates = states; } - function setExpanded(instanceId, expanded) { - if (expanded === (expandedStates[instanceId] ?? false)) - return; - var states = Object.assign({}, expandedStates); - states[instanceId] = expanded; - expandedStates = states; + function configureWidget(instanceId, title) { + SettingsUiState.selectedDesktopWidgetId = instanceId; + SettingsUiState.selectedWidgetTitle = title; + parentModal?.navigateTo("desktop_widget"); } function showWidgetBrowser() { @@ -283,11 +280,10 @@ Item { showHeader: true collapsed: root.groupCollapsedStates[modelData.id] ?? false instances: root.allInstances.filter(inst => inst.group === modelData.id) - expandedStates: root.expandedStates visible: instances.length > 0 || root.dragActive onCollapseToggled: key => root.toggleCollapsed(key) - onExpandedToggled: (instanceId, expanded) => root.setExpanded(instanceId, expanded) + onConfigureRequested: (instanceId, title) => root.configureWidget(instanceId, title) onDuplicateRequested: instanceId => SettingsData.duplicateDesktopWidgetInstance(instanceId) onDeleteRequested: instanceId => { SettingsData.removeDesktopWidgetInstance(instanceId); @@ -313,11 +309,10 @@ Item { showHeader: root.allGroups.length > 0 collapsed: root.groupCollapsedStates["_ungrouped"] ?? false instances: ungroupedInstances - expandedStates: root.expandedStates visible: ungroupedInstances.length > 0 || root.dragActive onCollapseToggled: key => root.toggleCollapsed(key) - onExpandedToggled: (instanceId, expanded) => root.setExpanded(instanceId, expanded) + onConfigureRequested: (instanceId, title) => root.configureWidget(instanceId, title) onDuplicateRequested: instanceId => SettingsData.duplicateDesktopWidgetInstance(instanceId) onDeleteRequested: instanceId => { SettingsData.removeDesktopWidgetInstance(instanceId); diff --git a/quickshell/Modules/Settings/LauncherTab.qml b/quickshell/Modules/Settings/LauncherTab.qml index 2d52990e4..7c90c8c63 100644 --- a/quickshell/Modules/Settings/LauncherTab.qml +++ b/quickshell/Modules/Settings/LauncherTab.qml @@ -14,6 +14,7 @@ Item { readonly property bool keybindsAvailable: KeybindsService.available readonly property string defaultLauncherKeybindSearch: "spotlight toggle" readonly property string spotlightBarKeybindSearch: "spotlight-bar" + readonly property var builtInPluginIds: ["dms_settings", "dms_notepad", "dms_sysmon", "dms_colorpicker", "dms_settings_search", "dms_clipboard_search", "dms_power", "dms_vpn", "dms_qr_generator"] function openKeybindsSearch(query) { if (!root.parentModal) @@ -35,6 +36,25 @@ Item { return keys.join(", "); } + function lastLaunchedText(lastUsed) { + if (!lastUsed) + return I18n.tr("Never used"); + const date = new Date(lastUsed); + const diffMs = Date.now() - date.getTime(); + const diffMins = Math.floor(diffMs / (1000 * 60)); + const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); + const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); + if (diffMins < 1) + return I18n.tr("Last launched just now"); + if (diffMins < 60) + return diffMins === 1 ? I18n.tr("Last launched %1 minute ago", "singular, app usage list in launcher settings, %1 is 1").arg(diffMins) : I18n.tr("Last launched %1 minutes ago", "plural, app usage list in launcher settings, %1 is a count").arg(diffMins); + if (diffHours < 24) + return diffHours === 1 ? I18n.tr("Last launched %1 hour ago", "singular, app usage list in launcher settings, %1 is 1").arg(diffHours) : I18n.tr("Last launched %1 hours ago", "plural, app usage list in launcher settings, %1 is a count").arg(diffHours); + if (diffDays < 7) + return diffDays === 1 ? I18n.tr("Last launched %1 day ago", "singular, app usage list in launcher settings, %1 is 1").arg(diffDays) : I18n.tr("Last launched %1 days ago", "plural, app usage list in launcher settings, %1 is a count").arg(diffDays); + return I18n.tr("Last launched %1", "app usage list in launcher settings, %1 is a localized date").arg(date.toLocaleDateString()); + } + Component.onCompleted: { if (KeybindsService.available) KeybindsService.loadBinds(false); @@ -320,78 +340,41 @@ Item { } } + Repeater { + model: hiddenAppsCard.hiddenAppsModel + + SettingsRow { + id: hiddenAppRow + + required property var modelData + + title: modelData.name + subtitle: modelData.comment || modelData.id + leading: AppIconRenderer { + width: Theme.iconSize + height: Theme.iconSize + iconValue: hiddenAppRow.modelData.icon || "application-x-executable" + iconSize: Theme.iconSize + fallbackText: (hiddenAppRow.modelData.name || "?").charAt(0).toUpperCase() + } + + DankActionButton { + iconName: "visibility" + Accessible.name: I18n.tr("Show") + iconColor: Theme.primary + onClicked: SessionData.showApp(hiddenAppRow.modelData.id) + } + } + } + SettingsRow { - body: Column { - id: hiddenAppsList + visible: hiddenAppsCard.hiddenAppsModel.length === 0 + body: StyledText { width: parent.width - spacing: Theme.spacingS - - Repeater { - model: hiddenAppsCard.hiddenAppsModel - - delegate: Rectangle { - width: hiddenAppsList.width - height: 48 - radius: Theme.cornerRadius - color: Theme.floatingWindowFieldColor - border.width: 0 - - Row { - anchors.left: parent.left - anchors.leftMargin: Theme.spacingM - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingM - - AppIconRenderer { - width: 24 - height: 24 - iconValue: modelData.icon || "application-x-executable" - iconSize: 24 - fallbackText: (modelData.name || "?").charAt(0).toUpperCase() - anchors.verticalCenter: parent.verticalCenter - } - - Column { - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingXXS - - StyledText { - text: modelData.name - font.pixelSize: Theme.fontSizeMedium - font.weight: Theme.fontWeightMedium - color: Theme.surfaceText - } - - StyledText { - text: modelData.comment || modelData.id - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceVariantText - visible: text.length > 0 - } - } - } - - DankActionButton { - anchors.right: parent.right - anchors.rightMargin: Theme.spacingM - anchors.verticalCenter: parent.verticalCenter - iconName: "visibility" - Accessible.name: I18n.tr("Show") - iconSize: 18 - iconColor: Theme.primary - onClicked: SessionData.showApp(modelData.id) - } - } - } - - StyledText { - width: parent.width - text: I18n.tr("No hidden apps.") - font.pixelSize: Theme.fontSizeMedium - color: Theme.surfaceVariantText - horizontalAlignment: Text.AlignHCenter - visible: hiddenAppsCard.hiddenAppsModel.length === 0 - } + text: I18n.tr("No hidden apps.") + font.pixelSize: Theme.fontSizeMedium + color: Theme.surfaceVariantText + horizontalAlignment: Text.AlignHCenter } } } @@ -431,77 +414,63 @@ Item { } } + Repeater { + model: appOverridesCard.overridesModel + + SettingsRow { + id: overrideRow + + required property var modelData + + title: modelData.name + subtitle: modelData.originalName !== modelData.name ? modelData.originalName : modelData.id + leading: AppIconRenderer { + width: Theme.iconSize + height: Theme.iconSize + iconValue: overrideRow.modelData.icon || "application-x-executable" + iconSize: Theme.iconSize + fallbackText: (overrideRow.modelData.name || "?").charAt(0).toUpperCase() + } + + DankActionButton { + iconName: "delete" + tooltipText: I18n.tr("Reset to default") + iconColor: Theme.error + onClicked: SessionData.clearAppOverride(overrideRow.modelData.id) + } + } + } + SettingsRow { - body: Column { - id: overridesList + visible: appOverridesCard.overridesModel.length === 0 + body: StyledText { width: parent.width - spacing: Theme.spacingS + text: I18n.tr("No app customizations.") + font.pixelSize: Theme.fontSizeMedium + color: Theme.surfaceVariantText + horizontalAlignment: Text.AlignHCenter + } + } + } - Repeater { - model: appOverridesCard.overridesModel + SettingsCard { + width: parent.width + iconName: "apps" + title: I18n.tr("Built-in apps", "launcher settings card, DMS tools listed with the installed apps") + settingKey: "launcherBuiltInApps" + tags: ["launcher", "apps", "builtin", "settings", "notepad", "system monitor", "color picker"] - delegate: Rectangle { - width: overridesList.width - height: 48 - radius: Theme.cornerRadius - color: Theme.floatingWindowFieldColor - border.width: 0 + Repeater { + model: root.builtInPluginIds.filter(id => AppSearchService.builtInPlugins[id]?.isLauncher !== true) - Row { - anchors.left: parent.left - anchors.leftMargin: Theme.spacingM - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingM + SettingsToggleRow { + required property string modelData + readonly property var plugin: AppSearchService.builtInPlugins[modelData] - AppIconRenderer { - width: 24 - height: 24 - iconValue: modelData.icon || "application-x-executable" - iconSize: 24 - fallbackText: (modelData.name || "?").charAt(0).toUpperCase() - anchors.verticalCenter: parent.verticalCenter - } - - Column { - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingXXS - - StyledText { - text: modelData.name - font.pixelSize: Theme.fontSizeMedium - font.weight: Theme.fontWeightMedium - color: Theme.surfaceText - } - - StyledText { - text: modelData.originalName !== modelData.name ? modelData.originalName : modelData.id - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceVariantText - } - } - } - - DankActionButton { - anchors.right: parent.right - anchors.rightMargin: Theme.spacingM - anchors.verticalCenter: parent.verticalCenter - iconName: "delete" - tooltipText: I18n.tr("Reset to default") - iconSize: 18 - iconColor: Theme.error - onClicked: SessionData.clearAppOverride(modelData.id) - } - } - } - - StyledText { - width: parent.width - text: I18n.tr("No app customizations.") - font.pixelSize: Theme.fontSizeMedium - color: Theme.surfaceVariantText - horizontalAlignment: Text.AlignHCenter - visible: appOverridesCard.overridesModel.length === 0 - } + iconName: plugin?.cornerIcon ?? "extension" + text: plugin?.name ?? modelData + checked: SettingsData.getBuiltInPluginSetting(modelData, "enabled", true) + onToggled: checked => SettingsData.setBuiltInPluginSetting(modelData, "enabled", checked) } } } @@ -596,10 +565,18 @@ Item { title: modelData.name subtitle: modelData.trigger ? I18n.tr("Trigger: %1", "launcher plugin subtitle, %1 is the trigger prefix text").arg(modelData.trigger) : I18n.tr("No trigger") iconName: modelData.iconType !== "unicode" ? modelData.icon : "" - trailingBadge: modelData.isBuiltIn ? "DMS" : "" textIcon: modelData.iconType === "unicode" ? modelData.icon : "" + DankBadge { + anchors.verticalCenter: parent.verticalCenter + visible: pluginRow.modelData.isBuiltIn + text: I18n.tr("Built-in", "badge on launcher plugins that ship with DMS") + color: Theme.primaryHover + textColor: Theme.primary + } + DankToggle { + anchors.verticalCenter: parent.verticalCenter hideText: true checked: { switch (pluginRow.modelData.id) { @@ -644,10 +621,10 @@ Item { iconName: "extension" title: I18n.tr("Built-in plugins") settingKey: "launcherBuiltInPlugins" - tags: ["launcher", "plugins", "trigger", "builtin", "settings", "notepad", "calculator", "clipboard", "power"] + tags: ["launcher", "plugins", "trigger", "builtin", "power", "vpn", "qr", "clipboard", "search"] Repeater { - model: ["dms_settings", "dms_notepad", "dms_sysmon", "dms_settings_search", "dms_clipboard_search", "dms_power", "dms_vpn", "dms_colorpicker", "dms_qr_generator"] + model: root.builtInPluginIds.filter(id => AppSearchService.builtInPlugins[id]?.isLauncher === true) SettingsRow { id: builtInRow @@ -657,14 +634,12 @@ Item { iconName: plugin?.cornerIcon ?? "extension" title: plugin?.name ?? modelData - subtitle: plugin?.comment ?? "" DankTextField { outlined: true leftIconName: "keyboard" labelText: I18n.tr("Trigger", "noun, launcher plugin trigger prefix text field label") width: Theme.fontSizeMedium * 6 + Theme.iconButtonSize - visible: builtInRow.plugin?.isLauncher === true anchors.verticalCenter: parent.verticalCenter onTextEdited: SettingsData.setBuiltInPluginSetting(builtInRow.modelData, "trigger", text) Component.onCompleted: text = SettingsData.getBuiltInPluginSetting(builtInRow.modelData, "trigger", builtInRow.plugin?.defaultTrigger ?? "") @@ -790,110 +765,58 @@ Item { } } - SettingsRow { - body: Column { - id: rankedAppsList - width: parent.width - spacing: Theme.spacingS + Repeater { + model: recentAppsCard.rankedAppsModel - Repeater { - model: recentAppsCard.rankedAppsModel + SettingsRow { + id: rankedAppRow - delegate: Rectangle { - width: rankedAppsList.width - height: 48 - radius: Theme.cornerRadius - color: Theme.floatingWindowFieldColor - border.width: 0 + required property var modelData + required property int index - Row { - anchors.left: parent.left - anchors.leftMargin: Theme.spacingM - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingM + title: modelData.name || I18n.tr("Unknown App") + subtitle: root.lastLaunchedText(modelData.lastUsed) + leading: [ + StyledText { + anchors.verticalCenter: parent.verticalCenter + width: Theme.iconSize + text: (rankedAppRow.index + 1).toString() + font.pixelSize: Theme.fontSizeSmall + font.weight: Theme.fontWeightMedium + color: Theme.primary + }, + AppIconRenderer { + anchors.verticalCenter: parent.verticalCenter + width: Theme.iconSize + height: Theme.iconSize + iconValue: rankedAppRow.modelData.icon || "application-x-executable" + iconSize: Theme.iconSize + fallbackText: (rankedAppRow.modelData.name || "?").charAt(0).toUpperCase() + } + ] - StyledText { - text: (index + 1).toString() - font.pixelSize: Theme.fontSizeSmall - font.weight: Theme.fontWeightMedium - color: Theme.primary - width: 20 - anchors.verticalCenter: parent.verticalCenter - } - - AppIconRenderer { - width: 24 - height: 24 - iconValue: modelData.icon || "application-x-executable" - iconSize: 24 - fallbackText: (modelData.name || "?").charAt(0).toUpperCase() - anchors.verticalCenter: parent.verticalCenter - } - - Column { - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingXXS - - StyledText { - text: modelData.name || I18n.tr("Unknown App") - font.pixelSize: Theme.fontSizeMedium - font.weight: Theme.fontWeightMedium - color: Theme.surfaceText - } - - StyledText { - text: { - if (!modelData.lastUsed) - return I18n.tr("Never used"); - var date = new Date(modelData.lastUsed); - var now = new Date(); - var diffMs = now - date; - var diffMins = Math.floor(diffMs / (1000 * 60)); - var diffHours = Math.floor(diffMs / (1000 * 60 * 60)); - var diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); - if (diffMins < 1) - return I18n.tr("Last launched just now"); - if (diffMins < 60) - return diffMins === 1 ? I18n.tr("Last launched %1 minute ago", "singular, app usage list in launcher settings, %1 is 1").arg(diffMins) : I18n.tr("Last launched %1 minutes ago", "plural, app usage list in launcher settings, %1 is a count").arg(diffMins); - if (diffHours < 24) - return diffHours === 1 ? I18n.tr("Last launched %1 hour ago", "singular, app usage list in launcher settings, %1 is 1").arg(diffHours) : I18n.tr("Last launched %1 hours ago", "plural, app usage list in launcher settings, %1 is a count").arg(diffHours); - if (diffDays < 7) - return diffDays === 1 ? I18n.tr("Last launched %1 day ago", "singular, app usage list in launcher settings, %1 is 1").arg(diffDays) : I18n.tr("Last launched %1 days ago", "plural, app usage list in launcher settings, %1 is a count").arg(diffDays); - return I18n.tr("Last launched %1", "app usage list in launcher settings, %1 is a localized date").arg(date.toLocaleDateString()); - } - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceVariantText - } - } - } - - DankActionButton { - anchors.right: parent.right - anchors.rightMargin: Theme.spacingM - anchors.verticalCenter: parent.verticalCenter - circular: true - iconName: "close" - Accessible.name: I18n.tr("Remove") - iconSize: 16 - iconColor: Theme.error - onClicked: { - var currentRanking = Object.assign({}, AppUsageHistoryData.appUsageRanking || {}); - delete currentRanking[modelData.id]; - AppUsageHistoryData.appUsageRanking = currentRanking; - AppUsageHistoryData.saveSettings(); - } - } + DankActionButton { + iconName: "close" + Accessible.name: I18n.tr("Remove") + iconColor: Theme.error + onClicked: { + const currentRanking = Object.assign({}, AppUsageHistoryData.appUsageRanking || {}); + delete currentRanking[rankedAppRow.modelData.id]; + AppUsageHistoryData.appUsageRanking = currentRanking; + AppUsageHistoryData.saveSettings(); } } + } + } - StyledText { - width: parent.width - text: I18n.tr("No apps have been launched yet.") - font.pixelSize: Theme.fontSizeMedium - color: Theme.surfaceVariantText - horizontalAlignment: Text.AlignHCenter - visible: recentAppsCard.rankedAppsModel.length === 0 - } + SettingsRow { + visible: recentAppsCard.rankedAppsModel.length === 0 + body: StyledText { + width: parent.width + text: I18n.tr("No apps have been launched yet.") + font.pixelSize: Theme.fontSizeMedium + color: Theme.surfaceVariantText + horizontalAlignment: Text.AlignHCenter } } } diff --git a/quickshell/Modules/Settings/PluginSettingsPage.qml b/quickshell/Modules/Settings/PluginSettingsPage.qml index 07cdb0a1f..dab3ae825 100644 --- a/quickshell/Modules/Settings/PluginSettingsPage.qml +++ b/quickshell/Modules/Settings/PluginSettingsPage.qml @@ -1,7 +1,6 @@ pragma ComponentBehavior: Bound import QtQuick -import QtQuick.Layouts import qs.Common import qs.Services import qs.Widgets @@ -225,80 +224,66 @@ FocusScope { } } - DankCollapsibleSection { - id: pluginDetails - width: parent.width + SettingsCard { title: I18n.tr("Plugin details", "plugin metadata and maintenance") + settingKey: "pluginDetails" visible: root.pluginData !== null + collapsible: true + expanded: false - SettingsCard { - Layout.fillWidth: true - visible: pluginDetails.expanded - settingKey: "pluginDetails" + SettingsRow { + title: [root.pluginData?.version, root.pluginData?.author].filter(Boolean).join(" · ") + } + SettingsRow { + subtitle: root.pluginData?.description || "" + visible: subtitle !== "" + } - SettingsRow { - title: [root.pluginData?.version, root.pluginData?.author].filter(Boolean).join(" · ") - } - SettingsRow { - subtitle: root.pluginData?.description || "" - visible: subtitle !== "" - } + SettingsRow { + visible: root.permissions.length > 0 + body: Flow { + width: parent.width + spacing: Theme.spacingXS - SettingsRow { - visible: root.permissions.length > 0 - body: Flow { - width: parent.width - spacing: Theme.spacingXS + Repeater { + model: root.permissions - Repeater { - model: root.permissions + DankBadge { + required property string modelData - Rectangle { - required property string modelData - - height: Theme.iconSizeMedium - width: permissionText.implicitWidth + Theme.spacingS * 2 - radius: Theme.fullRadius(width, height) - color: Theme.withAlpha(Theme.primary, Theme.tonalTintAlpha) - - StyledText { - id: permissionText - anchors.centerIn: parent - text: parent.modelData - font.pixelSize: Theme.fontSizeSmall - color: Theme.primary - } - } + text: modelData + color: Theme.primaryHover + textColor: Theme.primary } } } + } - SettingsRow { - iconName: "download" - title: I18n.tr("Update plugin") - clickable: true - visible: DMSService.dmsAvailable && root.isLoaded && root.hasUpdate && !root.isSystemPlugin - enabled: !root.operationPending - onClicked: root.requestUpdate() - } + SettingsRow { + iconName: "download" + title: I18n.tr("Update plugin") + clickable: true + visible: DMSService.dmsAvailable && root.isLoaded && root.hasUpdate && !root.isSystemPlugin + enabled: !root.operationPending + onClicked: root.requestUpdate() + } - SettingsRow { - iconName: "refresh" - title: I18n.tr("Reload plugin") - clickable: true - visible: root.isLoaded - enabled: !root.isReloading && !root.operationPending - onClicked: root.reload() - } + SettingsRow { + iconName: "refresh" + title: I18n.tr("Reload plugin") + clickable: true + visible: root.isLoaded + enabled: !root.isReloading && !root.operationPending + onClicked: root.reload() + } - SettingsRow { - iconName: "delete" - title: I18n.tr("Uninstall plugin") - clickable: true - visible: DMSService.dmsAvailable && !root.isSystemPlugin - enabled: !root.operationPending - onClicked: root.requestUninstall() - } + SettingsRow { + iconName: "delete" + title: I18n.tr("Uninstall plugin") + clickable: true + visible: DMSService.dmsAvailable && !root.isSystemPlugin + enabled: !root.operationPending + onClicked: root.requestUninstall() } } } diff --git a/quickshell/Modules/Settings/Widgets/SettingsColorPicker.qml b/quickshell/Modules/Settings/Widgets/SettingsColorPicker.qml index 887d04f8a..610b113cd 100644 --- a/quickshell/Modules/Settings/Widgets/SettingsColorPicker.qml +++ b/quickshell/Modules/Settings/Widgets/SettingsColorPicker.qml @@ -5,7 +5,7 @@ import qs.Common import qs.Services import qs.Widgets -Item { +SettingsRow { id: root property string colorMode: "primary" @@ -15,99 +15,84 @@ Item { signal colorModeSelected(string mode) signal customColorSelected(color selectedColor) - width: parent?.width ?? 0 - height: colorColumn.height + Theme.spacingM * 2 + title: I18n.tr("Color") - Column { - id: colorColumn - width: parent.width - Theme.spacingM * 2 - x: Theme.spacingM - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingM + body: Row { + width: parent.width + spacing: Theme.spacingS - StyledText { - text: I18n.tr("Color") - font.pixelSize: Theme.fontSizeMedium - color: Theme.surfaceText - } + Repeater { + model: [ + { + id: "primary", + label: I18n.tr("Primary"), + color: Theme.primary + }, + { + id: "secondary", + label: I18n.tr("Secondary"), + color: Theme.secondary + }, + { + id: "custom", + label: I18n.tr("Custom"), + color: root.customColor + } + ] - Row { - width: parent.width - spacing: Theme.spacingS + Rectangle { + required property var modelData + required property int index - Repeater { - model: [ - { - id: "primary", - label: I18n.tr("Primary"), - color: Theme.primary - }, - { - id: "secondary", - label: I18n.tr("Secondary"), - color: Theme.secondary - }, - { - id: "custom", - label: I18n.tr("Custom"), - color: root.customColor - } - ] + width: (parent.width - Theme.spacingS * 2) / 3 + height: Theme.listItemHeight + Theme.spacingXS + radius: Theme.cornerRadius + color: root.colorMode === modelData.id ? Theme.primarySelected : Theme.chipSurface + border.color: root.colorMode === modelData.id ? Theme.primary : Theme.withAlpha(Theme.primary, 0) + border.width: Theme.outlineWidthFocused - Rectangle { - required property var modelData - required property int index + Column { + anchors.centerIn: parent + spacing: Theme.spacingXS - width: (parent.width - Theme.spacingS * 2) / 3 - height: Theme.listItemHeight + Theme.spacingXS - radius: Theme.cornerRadius - color: root.colorMode === modelData.id ? Theme.primarySelected : Theme.chipSurface - border.color: root.colorMode === modelData.id ? Theme.primary : Theme.withAlpha(Theme.primary, 0) - border.width: Theme.outlineWidthFocused + DankColorSwatch { + width: Theme.iconSize + height: Theme.iconSize + swatchColor: modelData.color + anchors.horizontalCenter: parent.horizontalCenter - Column { - anchors.centerIn: parent - spacing: Theme.spacingXS - - DankColorSwatch { - width: Theme.iconSize - height: Theme.iconSize - swatchColor: modelData.color - anchors.horizontalCenter: parent.horizontalCenter - - DankIcon { - visible: modelData.id === "custom" - anchors.centerIn: parent - name: "colorize" - size: Theme.iconSizeSmall - color: Theme.background - } - } - - StyledText { - text: modelData.label - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceText - anchors.horizontalCenter: parent.horizontalCenter + DankIcon { + visible: modelData.id === "custom" + anchors.centerIn: parent + name: "colorize" + size: Theme.iconSizeSmall + color: Theme.background } } - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: { - if (modelData.id !== "custom") { - root.colorModeSelected(modelData.id); - return; - } - PopoutService.colorPickerModal.selectedColor = root.customColor; - PopoutService.colorPickerModal.pickerTitle = root.pickerTitle; - PopoutService.colorPickerModal.onColorSelectedCallback = function (selectedColor) { - root.customColorSelected(selectedColor); - root.colorModeSelected("custom"); - }; - PopoutService.colorPickerModal.show(); + StyledText { + text: modelData.label + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + anchors.horizontalCenter: parent.horizontalCenter + } + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + if (modelData.id !== "custom") { + root.colorModeSelected(modelData.id); + return; } + PopoutService.colorPickerModal.selectedColor = root.customColor; + PopoutService.colorPickerModal.pickerTitle = root.pickerTitle; + PopoutService.colorPickerModal.onColorSelectedCallback = function (selectedColor) { + root.customColorSelected(selectedColor); + root.colorModeSelected("custom"); + }; + PopoutService.colorPickerModal.show(); } } } diff --git a/quickshell/Modules/Settings/Widgets/SettingsReorderList.qml b/quickshell/Modules/Settings/Widgets/SettingsReorderList.qml index 0d39cd091..3f47abbc4 100644 --- a/quickshell/Modules/Settings/Widgets/SettingsReorderList.qml +++ b/quickshell/Modules/Settings/Widgets/SettingsReorderList.qml @@ -7,5 +7,7 @@ DankReorderList { readonly property bool isSettingsRow: true readonly property bool transparentSlot: true + readonly property bool firstInGroup: parent?.isSettingsGroupHost ? parent.isEdge(root, true) : true + readonly property bool lastInGroup: parent?.isSettingsGroupHost ? parent.isEdge(root, false) : true Component.onCompleted: flickable = QmlUtils.findParentFlickable(root.parent) } diff --git a/quickshell/Modules/Settings/Widgets/SettingsReorderRow.qml b/quickshell/Modules/Settings/Widgets/SettingsReorderRow.qml index 2085ddd0e..c8f1e50b4 100644 --- a/quickshell/Modules/Settings/Widgets/SettingsReorderRow.qml +++ b/quickshell/Modules/Settings/Widgets/SettingsReorderRow.qml @@ -14,8 +14,8 @@ SettingsRow { paintBackground: true rowColor: dragging ? Theme.blend(SettingsMetrics.rowColor, Theme.onSurface, Theme.stateLayerDrag) : SettingsMetrics.rowColor - topRadius: dragging || position === 0 ? Theme.groupedListOuterRadius : Theme.groupedListInnerRadius - bottomRadius: dragging || position === reorderList.count - 1 ? Theme.groupedListOuterRadius : Theme.groupedListInnerRadius + topRadius: dragging || (position === 0 && reorderList.firstInGroup) ? Theme.groupedListOuterRadius : Theme.groupedListInnerRadius + bottomRadius: dragging || (position === reorderList.count - 1 && reorderList.lastInGroup) ? Theme.groupedListOuterRadius : Theme.groupedListInnerRadius z: dragging ? 100 : 0 function focusHandle(reason) { diff --git a/quickshell/Modules/Settings/Widgets/SettingsRow.qml b/quickshell/Modules/Settings/Widgets/SettingsRow.qml index 2a4551743..b171530e8 100644 --- a/quickshell/Modules/Settings/Widgets/SettingsRow.qml +++ b/quickshell/Modules/Settings/Widgets/SettingsRow.qml @@ -86,8 +86,16 @@ T.Control { } function _edge(first) { - if (groupItem.parent?.isSettingsGroupHost) - return groupItem.parent.isEdge(groupItem, first); + const container = groupItem.parent; + if (container?.isSettingsGroupHost) + return container.isEdge(groupItem, first); + if (!_edgeInContainer(first)) + return false; + const host = container?.parent; + return host?.isSettingsGroupHost ? host.isEdge(container, first) : true; + } + + function _edgeInContainer(first) { const siblings = groupItem.parent?.visibleChildren; if (!siblings) return true; diff --git a/quickshell/translations/extract_settings_index.py b/quickshell/translations/extract_settings_index.py index 3d132ff28..ed09f32a5 100644 --- a/quickshell/translations/extract_settings_index.py +++ b/quickshell/translations/extract_settings_index.py @@ -136,6 +136,7 @@ TAB_INDEX_MAP = { "GammaControlTab.qml": 25, "DisplayWidgetsTab.qml": 26, "DesktopWidgetsTab.qml": 27, + "DesktopWidgetTab.qml": 63, "AudioTab.qml": 29, "LocaleTab.qml": 30, "GreeterTab.qml": 31, diff --git a/quickshell/translations/settings_search_index.json b/quickshell/translations/settings_search_index.json index 9a0a9068b..50b4d217e 100644 --- a/quickshell/translations/settings_search_index.json +++ b/quickshell/translations/settings_search_index.json @@ -4443,6 +4443,30 @@ ], "icon": "border_style" }, + { + "section": "launcherBuiltInApps", + "label": "Built-in apps", + "tabIndex": 9, + "category": "Launcher", + "keywords": [ + "app drawer", + "app menu", + "applications", + "apps", + "built", + "builtin", + "color picker", + "drawer", + "launcher", + "menu", + "notepad", + "settings", + "start", + "start menu", + "system monitor" + ], + "icon": "apps" + }, { "section": "launcherBuiltInPlugins", "label": "Built-in plugins", @@ -4455,19 +4479,19 @@ "applications", "built", "builtin", - "calculator", "clipboard", "drawer", "extensions", "launcher", "menu", - "notepad", "plugins", "power", - "settings", + "qr", + "search", "start", "start menu", "trigger", + "vpn", "widgets" ], "icon": "extension" @@ -9490,6 +9514,32 @@ ], "icon": "add" }, + { + "section": "_hub_desktop_widgets", + "label": "Desktop widgets", + "tabIndex": 27, + "page": "desktop_widgets", + "category": "Desktop widgets", + "keywords": [ + "addons", + "clocks", + "components", + "conky", + "desktop", + "desktop clock", + "extensions", + "modules", + "monitors", + "plugins", + "settings", + "system", + "time", + "watch", + "widget", + "widgets" + ], + "icon": "widgets" + }, { "section": "_tab_27", "label": "Desktop widgets", @@ -14858,6 +14908,29 @@ ], "parentLabel": "Interface style" }, + { + "section": "_tab_54", + "label": "Bar widgets: Widget settings", + "tabIndex": 54, + "category": "Widget settings", + "keywords": [ + "bar", + "components", + "instance", + "modules", + "options", + "panel", + "settings", + "statusbar", + "taskbar", + "topbar", + "widget", + "widgets" + ], + "icon": "tune", + "parentLabel": "Bar widgets", + "description": "Options of one widget instance" + }, { "section": "barWidgetGeneral", "label": "Show in dock", @@ -14880,29 +14953,6 @@ "parentLabel": "Bar widgets", "icon": "SettingsUiState.selectedWidgetIcon" }, - { - "section": "_tab_54", - "label": "Widget settings", - "tabIndex": 54, - "category": "Widget settings", - "keywords": [ - "bar", - "components", - "instance", - "modules", - "options", - "panel", - "settings", - "statusbar", - "taskbar", - "topbar", - "widget", - "widgets" - ], - "icon": "tune", - "parentLabel": "Bar widgets", - "description": "Options of one widget instance" - }, { "section": "mutedApps", "label": "Muted apps", @@ -15593,5 +15643,26 @@ "parentLabel": "Security", "description": "DMS removes its managed block from /etc/pam.d/greetd and stops write services", "conditionKey": "greeterAvailable" + }, + { + "section": "_tab_63", + "label": "Desktop widgets: Widget settings", + "tabIndex": 63, + "category": "Widget settings", + "keywords": [ + "components", + "conky", + "desktop", + "desktop clock", + "instance", + "modules", + "options", + "settings", + "widget", + "widgets" + ], + "icon": "tune", + "parentLabel": "Desktop widgets", + "description": "Options of one widget instance" } ]