From 6c555770cd6f4d105875874389f44a97046e3fb5 Mon Sep 17 00:00:00 2001 From: tibssy Date: Thu, 19 Feb 2026 03:33:38 +0000 Subject: [PATCH] feat(dock): implement application launcher icon with configurable position --- Assets/Translations/en.json | 4 + Assets/settings-default.json | 1 + Commons/Settings.qml | 1 + Modules/Dock/DockContent.qml | 122 ++++++++++++++++++ .../Settings/Tabs/Dock/AppearanceSubTab.qml | 20 +++ 5 files changed, 148 insertions(+) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 06d49a36a..8d8892540 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1041,6 +1041,10 @@ "appearance-colorize-icons-label": "Colorize icons", "appearance-show-launcher-icon-description": "Show the application launcher icon in the dock.", "appearance-show-launcher-icon-label": "Show App Launcher", + "appearance-launcher-position-description": "Choose where the launcher icon appears in the dock.", + "appearance-launcher-position-label": "Launcher position", + "appearance-launcher-position-start": "Start", + "appearance-launcher-position-end": "End", "appearance-dead-opacity-description": "Adjust the opacity of app icons that are not running.", "appearance-dead-opacity-label": "Dead opacity", "appearance-desc": "Customize the dock's behavior and appearance.", diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 5992f64b6..84231c18b 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -332,6 +332,7 @@ "pinnedApps": [], "colorizeIcons": false, "showLauncherIcon": false, + "launcherPosition": "end", "pinnedStatic": false, "inactiveIndicators": false, "deadOpacity": 0.6, diff --git a/Commons/Settings.qml b/Commons/Settings.qml index b06928c0a..1aab04bd7 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -533,6 +533,7 @@ Singleton { property list pinnedApps: [] // Desktop entry IDs pinned to the dock (e.g., "org.kde.konsole", "firefox.desktop") property bool colorizeIcons: false property bool showLauncherIcon: false + property string launcherPosition: "end" // "start", "end" property bool pinnedStatic: false property bool inactiveIndicators: false diff --git a/Modules/Dock/DockContent.qml b/Modules/Dock/DockContent.qml index 4e6e11f48..9e07e83b9 100644 --- a/Modules/Dock/DockContent.qml +++ b/Modules/Dock/DockContent.qml @@ -132,6 +132,113 @@ Item { width: implicitWidth height: implicitHeight + Component { + id: launcherButtonComponent + + Item { + id: launcherButton + anchors.fill: parent + readonly property string screenName: dockRoot.modelData ? dockRoot.modelData.name : (dockRoot.screen ? dockRoot.screen.name : "") + readonly property var launcherWidgetSettings: { + const widgetsBySection = screenName ? Settings.getBarWidgetsForScreen(screenName) : Settings.data.bar.widgets; + if (!widgetsBySection) + return {}; + const sections = ["left", "center", "right"]; + for (let i = 0; i < sections.length; i++) { + const sectionWidgets = widgetsBySection[sections[i]] || []; + for (let j = 0; j < sectionWidgets.length; j++) { + const widget = sectionWidgets[j]; + if (widget && widget.id === "Launcher") + return widget; + } + } + return {}; + } + readonly property var launcherMetadata: BarWidgetRegistry.widgetMetadata["Launcher"] + readonly property string launcherIcon: launcherWidgetSettings.icon || (launcherMetadata && launcherMetadata.icon ? launcherMetadata.icon : "search") + readonly property string launcherIconColorKey: launcherWidgetSettings.iconColor !== undefined + ? launcherWidgetSettings.iconColor + : (launcherMetadata && launcherMetadata.iconColor !== undefined ? launcherMetadata.iconColor : "none") + + Item { + id: launcherIconContainer + width: dockRoot.iconSize + height: dockRoot.iconSize + anchors.centerIn: parent + + scale: launcherMouseArea.containsMouse ? 1.15 : 1.0 + Behavior on scale { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutBack + easing.overshoot: 1.2 + } + } + + NIcon { + anchors.centerIn: parent + icon: launcherButton.launcherIcon + pointSize: dockRoot.iconSize * 0.7 + color: Color.resolveColorKey(launcherButton.launcherIconColorKey) + } + } + + MouseArea { + id: launcherMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton | Qt.MiddleButton + + onEntered: { + dockRoot.anyAppHovered = true; + TooltipService.show(launcherButton, I18n.tr("actions.open-launcher"), "top"); + if (dockRoot.autoHide) { + dockRoot.showTimer.stop(); + dockRoot.hideTimer.stop(); + dockRoot.unloadTimer.stop(); + dockRoot.hidden = false; + } + } + + onExited: { + dockRoot.anyAppHovered = false; + TooltipService.hide(); + if (dockRoot.autoHide && !dockRoot.dockHovered && !dockRoot.peekHovered && !dockRoot.menuHovered && dockRoot.dragSourceIndex === -1) { + dockRoot.hideTimer.restart(); + } + } + + onClicked: mouse => { + if (mouse.button !== Qt.LeftButton && mouse.button !== Qt.MiddleButton) { + return; + } + const targetScreen = dockRoot.modelData || dockRoot.screen || null; + if (!targetScreen) { + return; + } + dockRoot.closeAllContextMenus(); + PanelService.toggleLauncher(targetScreen); + } + } + } + } + + Loader { + id: launcherButtonStart + active: Settings.data.dock.showLauncherIcon && Settings.data.dock.launcherPosition === "start" + visible: active + sourceComponent: launcherButtonComponent + readonly property real indicatorMargin: Math.max(3, Math.round(dockRoot.iconSize * 0.18)) + Layout.preferredWidth: active ? (dockRoot.isVertical ? dockRoot.iconSize + indicatorMargin * 2 : dockRoot.iconSize) : 0 + Layout.preferredHeight: active ? (dockRoot.isVertical ? dockRoot.iconSize : dockRoot.iconSize + indicatorMargin * 2) : 0 + Layout.minimumWidth: active ? Layout.preferredWidth : 0 + Layout.minimumHeight: active ? Layout.preferredHeight : 0 + Layout.maximumWidth: active ? Layout.preferredWidth : 0 + Layout.maximumHeight: active ? Layout.preferredHeight : 0 + Layout.alignment: Qt.AlignCenter + } + Repeater { model: dockRoot.dockApps @@ -521,6 +628,21 @@ Item { } } } + + Loader { + id: launcherButtonEnd + active: Settings.data.dock.showLauncherIcon && Settings.data.dock.launcherPosition === "end" + visible: active + sourceComponent: launcherButtonComponent + readonly property real indicatorMargin: Math.max(3, Math.round(dockRoot.iconSize * 0.18)) + Layout.preferredWidth: active ? (dockRoot.isVertical ? dockRoot.iconSize + indicatorMargin * 2 : dockRoot.iconSize) : 0 + Layout.preferredHeight: active ? (dockRoot.isVertical ? dockRoot.iconSize : dockRoot.iconSize + indicatorMargin * 2) : 0 + Layout.minimumWidth: active ? Layout.preferredWidth : 0 + Layout.minimumHeight: active ? Layout.preferredHeight : 0 + Layout.maximumWidth: active ? Layout.preferredWidth : 0 + Layout.maximumHeight: active ? Layout.preferredHeight : 0 + Layout.alignment: Qt.AlignCenter + } } } } diff --git a/Modules/Panels/Settings/Tabs/Dock/AppearanceSubTab.qml b/Modules/Panels/Settings/Tabs/Dock/AppearanceSubTab.qml index 6e8ad6c70..9a2784d4e 100644 --- a/Modules/Panels/Settings/Tabs/Dock/AppearanceSubTab.qml +++ b/Modules/Panels/Settings/Tabs/Dock/AppearanceSubTab.qml @@ -227,5 +227,25 @@ ColumnLayout { defaultValue: Settings.getDefaultValue("dock.showLauncherIcon") onToggled: checked => Settings.data.dock.showLauncherIcon = checked } + + NComboBox { + Layout.fillWidth: true + visible: Settings.data.dock.showLauncherIcon + label: I18n.tr("panels.dock.appearance-launcher-position-label") + description: I18n.tr("panels.dock.appearance-launcher-position-description") + model: [ + { + "key": "start", + "name": I18n.tr("panels.dock.appearance-launcher-position-start") + }, + { + "key": "end", + "name": I18n.tr("panels.dock.appearance-launcher-position-end") + } + ] + currentKey: Settings.data.dock.launcherPosition + defaultValue: Settings.getDefaultValue("dock.launcherPosition") + onSelected: key => Settings.data.dock.launcherPosition = key + } } }