diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index f17cf9552..c04d7d790 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -23,7 +23,7 @@ Singleton { id: root readonly property var log: Log.scoped("SettingsData") - readonly property int settingsConfigVersion: 41 + readonly property int settingsConfigVersion: 42 readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true" diff --git a/quickshell/Common/settings/BarWidgetDefaults.js b/quickshell/Common/settings/BarWidgetDefaults.js index aa96d2c68..20d41caff 100644 --- a/quickshell/Common/settings/BarWidgetDefaults.js +++ b/quickshell/Common/settings/BarWidgetDefaults.js @@ -151,6 +151,13 @@ var DEFAULTS = { barMaxVisibleApps: 0, barMaxVisibleRunningApps: 0, barShowOverflowBadge: true, + launcherEnabled: false, + launcherLogoMode: "os", + launcherLogoCustomPath: "", + launcherLogoColorOverride: "", + launcherLogoSizeOffset: 0, + launcherLogoBrightness: 0.5, + launcherLogoContrast: 1, runningAppsCompactMode: true, runningAppsCurrentWorkspace: true, appsDockHideIndicators: true, diff --git a/quickshell/Common/settings/SettingsStore.js b/quickshell/Common/settings/SettingsStore.js index 54499be43..5143b3d98 100644 --- a/quickshell/Common/settings/SettingsStore.js +++ b/quickshell/Common/settings/SettingsStore.js @@ -747,9 +747,36 @@ function migrateToVersion(obj, targetVersion) { settings.configVersion = 41; } + if (currentVersion < 42 && targetVersion >= 42) { + migrateDockAppsSizing(settings); + settings.configVersion = 42; + } + return settings; } +// v42: dock apps follow the dock's icon size and magnification instead of their own copies +function migrateDockAppsSizing(settings) { + if (!Array.isArray(settings.dockConfigs)) + return; + for (const dock of settings.dockConfigs) { + for (const widget of Array.isArray(dock?.widgets) ? dock.widgets : []) { + if (!widget || typeof widget !== "object") + continue; + if (widget.appsDockEnlargeOnHover === true && dock.magnification !== true) { + dock.magnification = true; + dock.magnificationScale = Math.max(105, Math.min(160, Number(widget.appsDockEnlargePercentage) || 125)); + } + const percentage = Number(widget.appsDockIconSizePercentage); + if (percentage > 0 && percentage !== 100) + dock.iconSize = Math.round((Number(dock.iconSize) || DockConfig.create("", "").iconSize) * percentage / 100); + delete widget.appsDockEnlargeOnHover; + delete widget.appsDockEnlargePercentage; + delete widget.appsDockIconSizePercentage; + } + } +} + // v41: footer entries became a grid row under the rest, keeping their order and widths function migrateControlCenterFooter(settings) { delete settings.controlCenterFooterPosition; diff --git a/quickshell/Modules/DBar/Widgets/AppsDock.qml b/quickshell/Modules/DBar/Widgets/AppsDock.qml index a4e7e9e66..df29ff0b5 100644 --- a/quickshell/Modules/DBar/Widgets/AppsDock.qml +++ b/quickshell/Modules/DBar/Widgets/AppsDock.qml @@ -30,7 +30,16 @@ BasePill { itemSpacing: appOption("appsDockSpacing", 4), maxVisibleApps: appOption("barMaxVisibleApps", defaults.maxVisibleApps), maxVisibleRunningApps: appOption("barMaxVisibleRunningApps", defaults.maxVisibleRunningApps), - showOverflowBadge: appOption("barShowOverflowBadge", defaults.showOverflowBadge) + showOverflowBadge: appOption("barShowOverflowBadge", defaults.showOverflowBadge), + launcherEnabled: appOption("launcherEnabled"), + launcherLogoMode: appOption("launcherLogoMode"), + launcherLogoCustomPath: appOption("launcherLogoCustomPath"), + launcherLogoColorOverride: appOption("launcherLogoColorOverride"), + launcherLogoSizeOffset: appOption("launcherLogoSizeOffset"), + launcherLogoBrightness: appOption("launcherLogoBrightness"), + launcherLogoContrast: appOption("launcherLogoContrast"), + enlargeOnHover: appOption("appsDockEnlargeOnHover"), + enlargePercentage: appOption("appsDockEnlargePercentage") }); return Object.assign(options, { iconSize: Math.min(options.iconSize, surfaceContext?.widgetThickness ?? options.iconSize), @@ -39,13 +48,11 @@ BasePill { hideIndicators: appOption("appsDockHideIndicators", false), colorizeActive: appOption("appsDockColorizeActive", false), activeColorMode: appOption("appsDockActiveColorMode", "primary"), - enlargeOnHover: appOption("appsDockEnlargeOnHover", false), - enlargePercentage: appOption("appsDockEnlargePercentage", 125), magnification: dockHosted && (surfaceContext?.config?.magnification ?? false), magnificationScale: dockHosted ? (surfaceContext?.config?.magnificationScale ?? 130) : 130, magnificationProfile: dockHosted ? (surfaceContext?.config?.magnificationProfile ?? "parabolic") : "parabolic", magnificationExpand: dockHosted && (surfaceContext?.config?.magnificationExpand ?? false), - iconSizePercentage: appOption("appsDockIconSizePercentage", 100) + iconSizePercentage: dockHosted ? 100 : appOption("appsDockIconSizePercentage") }); } property bool renderItems: true diff --git a/quickshell/Modules/Dock/DockAppButton.qml b/quickshell/Modules/Dock/DockAppButton.qml index e5524137d..9d1c0b192 100644 --- a/quickshell/Modules/Dock/DockAppButton.qml +++ b/quickshell/Modules/Dock/DockAppButton.qml @@ -447,7 +447,7 @@ Item { id: iconSlot anchors.fill: parent - scale: !root.options?.magnification && root.options?.enlargeOnHover && root.isHovered ? (root.options?.enlargePercentage ?? 125) / 100 : 1 + scale: root.options?.enlargeOnHover && root.isHovered ? (root.options?.enlargePercentage ?? 125) / 100 : 1 AppIconRenderer { id: coreIcon diff --git a/quickshell/Modules/Dock/DockLauncherButton.qml b/quickshell/Modules/Dock/DockLauncherButton.qml index 672d5edcb..ef30b48cf 100644 --- a/quickshell/Modules/Dock/DockLauncherButton.qml +++ b/quickshell/Modules/Dock/DockLauncherButton.qml @@ -64,7 +64,7 @@ Item { anchors.centerIn: parent width: actualIconSize height: actualIconSize - scale: !root.options?.magnification && root.options?.enlargeOnHover && root.isHovered ? (root.options?.enlargePercentage ?? 125) / 100 : 1 + scale: root.options?.enlargeOnHover && root.isHovered ? (root.options?.enlargePercentage ?? 125) / 100 : 1 LauncherLogo { anchors.centerIn: parent diff --git a/quickshell/Modules/Dock/DockTrashButton.qml b/quickshell/Modules/Dock/DockTrashButton.qml index 8c56b41f1..9d91b5ffa 100644 --- a/quickshell/Modules/Dock/DockTrashButton.qml +++ b/quickshell/Modules/Dock/DockTrashButton.qml @@ -67,7 +67,7 @@ Item { anchors.centerIn: parent width: actualIconSize - 4 height: actualIconSize - 4 - scale: !root.options?.magnification && root.options?.enlargeOnHover && root.isHovered ? (root.options?.enlargePercentage ?? 125) / 100 : 1 + scale: root.options?.enlargeOnHover && root.isHovered ? (root.options?.enlargePercentage ?? 125) / 100 : 1 readonly property string iconPath: Paths.resolveIconPath(TrashService.isEmpty ? "user-trash" : "user-trash-full") diff --git a/quickshell/Modules/Settings/BarWidgetOptions/AppsDockOptions.qml b/quickshell/Modules/Settings/BarWidgetOptions/AppsDockOptions.qml index c15dd44e2..2d9094f45 100644 --- a/quickshell/Modules/Settings/BarWidgetOptions/AppsDockOptions.qml +++ b/quickshell/Modules/Settings/BarWidgetOptions/AppsDockOptions.qml @@ -119,7 +119,6 @@ Column { SettingsCard { title: I18n.tr("Launcher button") settingKey: "dockLauncher" - visible: root.dockHosted SettingsToggleRow { text: I18n.tr("Show") @@ -268,6 +267,7 @@ Column { SettingsCard { title: I18n.tr("Visual effects") settingKey: "barWidgetAppsDockEffects" + visible: !root.dockHosted SettingsToggleRow { text: I18n.tr("Indicators") @@ -275,22 +275,6 @@ Column { onToggled: checked => root.page.set("appsDockHideIndicators", !checked) } - SettingsButtonGroupRow { - resetStore: root.apps - resetKeys: ["indicatorStyle"] - text: I18n.tr("Indicator style") - visible: root.dockHosted - enabled: !root.page.value("appsDockHideIndicators") - model: [I18n.tr("Circle", "dock indicator style option"), I18n.tr("Line", "dock indicator style option")] - buttonPadding: Theme.spacingS - currentIndex: root.apps.get("indicatorStyle") === "circle" ? 0 : 1 - onSelectionChanged: (index, selected) => { - if (!selected) - return; - root.apps.set("indicatorStyle", index === 0 ? "circle" : "line"); - } - } - SettingsToggleRow { text: I18n.tr("Colorize active") checked: root.page.value("appsDockColorizeActive") @@ -310,7 +294,7 @@ Column { SettingsToggleSliderRow { resetStore: root.page valueKeys: ["appsDockEnlargePercentage"] - text: I18n.tr("Enlarge on hover") + text: I18n.tr("Magnification", "dock setting: enlarge icons near the cursor") checked: root.page.value("appsDockEnlargeOnHover") value: root.page.value("appsDockEnlargePercentage") minimum: 100 @@ -336,7 +320,6 @@ Column { resetStore: root.page resetKeys: ["appsDockSpacing"] text: I18n.tr("Spacing") - visible: !root.dockHosted value: root.page.value("appsDockSpacing") minimum: 0 maximum: 32 diff --git a/quickshell/Modules/Settings/BarWidgetTab.qml b/quickshell/Modules/Settings/BarWidgetTab.qml index ee4f71385..59261e44f 100644 --- a/quickshell/Modules/Settings/BarWidgetTab.qml +++ b/quickshell/Modules/Settings/BarWidgetTab.qml @@ -77,8 +77,7 @@ Item { } readonly property var _dockAppsDefaults: ({ - "appsDockHideIndicators": false, - "appsDockIconSizePercentage": 100 + "appsDockHideIndicators": false }) function defaultOption(key) { diff --git a/quickshell/Modules/Settings/DockAppearanceTab.qml b/quickshell/Modules/Settings/DockAppearanceTab.qml index 9b2a64f8f..0c089b0f2 100644 --- a/quickshell/Modules/Settings/DockAppearanceTab.qml +++ b/quickshell/Modules/Settings/DockAppearanceTab.qml @@ -135,12 +135,65 @@ Item { } } - SettingsControlledBy { - visible: dock.hasConfig && !dock.connectedFrameModeActive - target: "surfaces" - parentModal: root.parentModal - section: "surfaceOpacity_dock_" + dock.selectedDockId - settingLabel: I18n.tr("Opacity") + SettingsCard { + width: parent.width + visible: dock.appsEntry !== null + iconName: "auto_awesome" + title: I18n.tr("Visual effects") + settingKey: "dockVisualEffects" + tags: ["dock", "indicator", "running", "active", "colorize", "highlight"] + + SettingsToggleRow { + text: I18n.tr("Indicators") + checked: !dock.appsValue("appsDockHideIndicators") + onToggled: checked => dock.setAppsOption("appsDockHideIndicators", !checked) + } + + SettingsButtonGroupRow { + resetStore: dock + resetKeys: ["indicatorStyle"] + text: I18n.tr("Indicator style") + enabled: !dock.appsValue("appsDockHideIndicators") + model: [I18n.tr("Circle", "dock indicator style option"), I18n.tr("Line", "dock indicator style option")] + buttonPadding: Theme.spacingS + currentIndex: dock.config?.indicatorStyle === "circle" ? 0 : 1 + onSelectionChanged: (index, selected) => { + if (!selected) + return; + dock.setOption("indicatorStyle", index === 0 ? "circle" : "line"); + } + } + + SettingsToggleRow { + text: I18n.tr("Colorize active") + checked: dock.appsValue("appsDockColorizeActive") + onToggled: checked => dock.setAppsOption("appsDockColorizeActive", checked) + } + + ColorDropdownRow { + resetStore: dock.apps + resetKeys: ["appsDockActiveColorMode"] + text: I18n.tr("Active color") + enabled: dock.appsValue("appsDockColorizeActive") + options: [({ + "value": "primary", + "label": I18n.tr("Primary") + }), ({ + "value": "secondary", + "label": I18n.tr("Secondary") + }), ({ + "value": "primaryContainer", + "label": I18n.tr("Primary Container") + }), ({ + "value": "error", + "label": I18n.tr("Error") + }), ({ + "value": "success", + "label": I18n.tr("Success", "noun, theme color name in active color dropdown") + })] + currentMode: dock.appsValue("appsDockActiveColorMode") + onModeSelected: mode => dock.setAppsOption("appsDockActiveColorMode", mode) + } } SettingsToggleCard { @@ -193,6 +246,14 @@ Item { } } + SettingsControlledBy { + visible: dock.hasConfig && !dock.connectedFrameModeActive + target: "surfaces" + parentModal: root.parentModal + section: "surfaceOpacity_dock_" + dock.selectedDockId + settingLabel: I18n.tr("Opacity") + } + SettingsControlledBy { visible: dock.connectedFrameModeActive parentModal: root.parentModal diff --git a/quickshell/Modules/Settings/DockWidgetsTab.qml b/quickshell/Modules/Settings/DockWidgetsTab.qml index 6fa2f1c59..8f9ab73eb 100644 --- a/quickshell/Modules/Settings/DockWidgetsTab.qml +++ b/quickshell/Modules/Settings/DockWidgetsTab.qml @@ -13,49 +13,28 @@ Item { property int catalogRevision: 0 readonly property bool dockHosted: true - readonly property var appsEntry: dock.config?.widgets.find(item => item.widgetId === "appsDock") ?? null + readonly property var appsEntry: dock.appsEntry readonly property var appStore: ({ "get": key => dock.config?.[key], "set": (key, value) => dock.setOption(key, value), "isDefault": keys => dock.isDefault(keys), "resetToDefault": keys => dock.resetToDefault(keys) }) - readonly property var _dockAppsDefaults: ({ - "appsDockHideIndicators": false, - "appsDockIconSizePercentage": 100, - "runningAppsCurrentWorkspace": false - }) - - function defaultOption(key) { - if (key in _dockAppsDefaults) - return _dockAppsDefaults[key]; - return SettingsData.widgetDefaults("appsDock")[key]; - } function value(key) { - if (key in _dockAppsDefaults) - return appsEntry?.[key] ?? _dockAppsDefaults[key]; - return SettingsData.widgetOption("appsDock", appsEntry, key); - } - - function isDefault(keys) { - return keys.every(key => defaultOption(key) === undefined || JSON.stringify(value(key)) === JSON.stringify(defaultOption(key))); - } - - function resetToDefault(keys) { - for (const key of keys) { - const fallback = defaultOption(key); - if (fallback !== undefined) - set(key, fallback); - } + return dock.appsValue(key); } function set(key, value) { - if (!appsEntry) - return; - SettingsData.updateDockWidget(dock.selectedDockId, appsEntry.id, { - [key]: value - }); + dock.setAppsOption(key, value); + } + + function isDefault(keys) { + return dock.apps.isDefault(keys); + } + + function resetToDefault(keys) { + dock.apps.resetToDefault(keys); } DockSelectionState { diff --git a/quickshell/Modules/Settings/Widgets/DockSelectionState.qml b/quickshell/Modules/Settings/Widgets/DockSelectionState.qml index 88da873db..01a1a20b4 100644 --- a/quickshell/Modules/Settings/Widgets/DockSelectionState.qml +++ b/quickshell/Modules/Settings/Widgets/DockSelectionState.qml @@ -105,6 +105,39 @@ Item { return SettingsData.dockAssignmentConflict(selectedDockId, screenPreferences, config.position); } + // The apps strip keeps its own options on its widget entry rather than on the dock. + readonly property var appsEntry: config?.widgets.find(item => item.widgetId === "appsDock") ?? null + readonly property var _appsDefaults: ({ + "appsDockHideIndicators": false, + "runningAppsCurrentWorkspace": false + }) + readonly property var apps: ({ + "get": key => root.appsValue(key), + "set": (key, value) => root.setAppsOption(key, value), + "isDefault": keys => keys.every(key => root.appsDefault(key) === undefined || JSON.stringify(root.appsValue(key)) === JSON.stringify(root.appsDefault(key))), + "resetToDefault": keys => keys.filter(key => root.appsDefault(key) !== undefined).forEach(key => root.setAppsOption(key, root.appsDefault(key))) + }) + + function appsDefault(key) { + if (key in _appsDefaults) + return _appsDefaults[key]; + return SettingsData.widgetDefaults("appsDock")[key]; + } + + function appsValue(key) { + if (key in _appsDefaults) + return appsEntry?.[key] ?? _appsDefaults[key]; + return SettingsData.widgetOption("appsDock", appsEntry, key); + } + + function setAppsOption(key, value) { + if (!appsEntry) + return; + SettingsData.updateDockWidget(selectedDockId, appsEntry.id, { + [key]: value + }); + } + function isDefault(keys) { if (!config) return true; diff --git a/quickshell/tests/settings-dock-magnification.test.mjs b/quickshell/tests/settings-dock-magnification.test.mjs new file mode 100644 index 000000000..f24c85068 --- /dev/null +++ b/quickshell/tests/settings-dock-magnification.test.mjs @@ -0,0 +1,31 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { loadScript } from "./qml-script.mjs"; + +const store = loadScript(new URL("../Common/settings/SettingsStore.js", import.meta.url)); + +function migrate(dock, widget) { + const settings = { configVersion: 41, dockConfigs: [Object.assign({ id: "dock", widgets: [Object.assign({ id: "dock_apps", widgetId: "appsDock" }, widget)] }, dock)] }; + return JSON.parse(JSON.stringify(store.migrateToVersion(settings, 42))).dockConfigs[0]; +} + +test("dock apps sizing folds into the dock and the widget copies are dropped", () => { + const cases = [ + { dock: {}, widget: { appsDockEnlargeOnHover: true, appsDockEnlargePercentage: 140 }, magnification: true, scale: 140 }, + { dock: {}, widget: { appsDockEnlargeOnHover: true, appsDockEnlargePercentage: 100 }, magnification: true, scale: 105 }, + { dock: { magnification: true, magnificationScale: 150 }, widget: { appsDockEnlargeOnHover: true, appsDockEnlargePercentage: 110 }, magnification: true, scale: 150 }, + { dock: { magnification: false, magnificationScale: 130 }, widget: { appsDockEnlargeOnHover: false, appsDockEnlargePercentage: 140 }, magnification: false, scale: 130 }, + { dock: { iconSize: 40 }, widget: { appsDockIconSizePercentage: 150 }, iconSize: 60 }, + { dock: { iconSize: 48 }, widget: { appsDockIconSizePercentage: 100 }, iconSize: 48 } + ]; + for (const { dock, widget, magnification, scale, iconSize } of cases) { + const migrated = migrate(dock, widget); + if (magnification !== undefined) { + assert.equal(migrated.magnification === true, magnification); + assert.equal(migrated.magnificationScale, scale); + } + if (iconSize !== undefined) + assert.equal(migrated.iconSize, iconSize); + assert.deepEqual(migrated.widgets, [{ id: "dock_apps", widgetId: "appsDock" }]); + } +}); diff --git a/quickshell/translations/settings_search_index.json b/quickshell/translations/settings_search_index.json index c6f9a8daa..988925422 100644 --- a/quickshell/translations/settings_search_index.json +++ b/quickshell/translations/settings_search_index.json @@ -9753,7 +9753,7 @@ "parentLabel": "Power & battery" }, { - "section": "barWidgetOption:AppsDockOptions.qml:24", + "section": "barWidgetOption:AppsDockOptions.qml:23", "label": "Active color", "tabIndex": 22, "keywords": [], @@ -9936,7 +9936,7 @@ "optionFile": "LauncherButtonOptions.qml" }, { - "section": "barWidgetOption:AppsDockOptions.qml:23", + "section": "barWidgetOption:AppsDockOptions.qml:22", "label": "Colorize active", "tabIndex": 22, "keywords": [], @@ -10103,14 +10103,6 @@ "runtimeType": "barWidgetOption", "optionFile": "WorkspaceSwitcherOptions.qml" }, - { - "section": "barWidgetOption:AppsDockOptions.qml:25", - "label": "Enlarge on hover", - "tabIndex": 22, - "keywords": [], - "runtimeType": "barWidgetOption", - "optionFile": "AppsDockOptions.qml" - }, { "section": "barWidgetOption:IslandOptions.qml:28", "label": "Expand by default", @@ -10256,7 +10248,7 @@ "optionFile": "LauncherButtonOptions.qml" }, { - "section": "barWidgetOption:AppsDockOptions.qml:26", + "section": "barWidgetOption:AppsDockOptions.qml:25", "label": "Icon size", "tabIndex": 22, "keywords": [], @@ -10287,14 +10279,6 @@ "runtimeType": "barWidgetOption", "optionFile": "WorkspaceSwitcherOptions.qml" }, - { - "section": "barWidgetOption:AppsDockOptions.qml:22", - "label": "Indicator style", - "tabIndex": 22, - "keywords": [], - "runtimeType": "barWidgetOption", - "optionFile": "AppsDockOptions.qml" - }, { "section": "barWidgetOption:AppsDockOptions.qml:21", "label": "Indicators", @@ -10351,6 +10335,14 @@ "runtimeType": "barWidgetOption", "optionFile": "MusicOptions.qml" }, + { + "section": "barWidgetOption:AppsDockOptions.qml:24", + "label": "Magnification", + "tabIndex": 22, + "keywords": [], + "runtimeType": "barWidgetOption", + "optionFile": "AppsDockOptions.qml" + }, { "section": "barWidgetOption:WorkspaceSwitcherOptions.qml:8", "label": "Max apps to show", @@ -10736,7 +10728,7 @@ "optionFile": "SystemUpdateOptions.qml" }, { - "section": "barWidgetOption:AppsDockOptions.qml:27", + "section": "barWidgetOption:AppsDockOptions.qml:26", "label": "Spacing", "tabIndex": 22, "keywords": [], @@ -17044,6 +17036,28 @@ ], "parentLabel": "Dock" }, + { + "section": "dockVisualEffects", + "label": "Visual effects", + "tabIndex": 58, + "category": "Appearance", + "keywords": [ + "active", + "appearance", + "colorize", + "dock", + "effects", + "highlight", + "indicator", + "launcher bar", + "panel", + "running", + "taskbar", + "visual" + ], + "parentLabel": "Dock", + "icon": "auto_awesome" + }, { "section": "_tab_59", "label": "Advanced",