mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
Merge pull request #1892 from tibssy/feat/dock-launcher-icon
Feat/dock launcher icon
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
"open-display-settings": "Display settings",
|
||||
"open-launcher": "Open launcher",
|
||||
"open-settings": "Open settings",
|
||||
"dock-settings": "Dock settings",
|
||||
"raise-to-top": "Raise to top",
|
||||
"random-wallpaper": "Random wallpaper",
|
||||
"run-custom-command": "Run custom command",
|
||||
@@ -1039,6 +1040,12 @@
|
||||
"appearance-border-radius-label": "Border radius",
|
||||
"appearance-colorize-icons-description": "Apply theme colors to dock app icons (non-focused apps only).",
|
||||
"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.",
|
||||
|
||||
@@ -331,6 +331,8 @@
|
||||
"monitors": [],
|
||||
"pinnedApps": [],
|
||||
"colorizeIcons": false,
|
||||
"showLauncherIcon": false,
|
||||
"launcherPosition": "end",
|
||||
"pinnedStatic": false,
|
||||
"inactiveIndicators": false,
|
||||
"deadOpacity": 0.6,
|
||||
|
||||
@@ -532,6 +532,8 @@ Singleton {
|
||||
property list<string> monitors: [] // holds dock visibility per monitor
|
||||
property list<string> 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
|
||||
|
||||
@@ -132,6 +132,195 @@ 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 string launcherWidgetSection: {
|
||||
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 sections[i];
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
readonly property int launcherWidgetIndex: {
|
||||
const widgetsBySection = screenName ? Settings.getBarWidgetsForScreen(screenName) : Settings.data.bar.widgets;
|
||||
if (!widgetsBySection)
|
||||
return -1;
|
||||
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 j;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
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 | Qt.RightButton
|
||||
|
||||
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 => {
|
||||
const targetScreen = dockRoot.modelData || dockRoot.screen || null;
|
||||
if (!targetScreen) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
if (dockRoot.currentContextMenu === launcherContextMenu && launcherContextMenu.visible) {
|
||||
dockRoot.closeAllContextMenus();
|
||||
return;
|
||||
}
|
||||
dockRoot.closeAllContextMenus();
|
||||
TooltipService.hideImmediately();
|
||||
launcherContextMenu.show(launcherButton, null, targetScreen);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mouse.button === Qt.LeftButton || mouse.button === Qt.MiddleButton) {
|
||||
dockRoot.closeAllContextMenus();
|
||||
PanelService.toggleLauncher(targetScreen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DockMenu {
|
||||
id: launcherContextMenu
|
||||
dockPosition: dockRoot.dockPosition
|
||||
menuMode: "launcher"
|
||||
launcherWidgetSection: launcherButton.launcherWidgetSection
|
||||
launcherWidgetIndex: launcherButton.launcherWidgetIndex
|
||||
launcherWidgetSettings: launcherButton.launcherWidgetSettings
|
||||
|
||||
onHoveredChanged: {
|
||||
if (dockRoot.currentContextMenu === launcherContextMenu && launcherContextMenu.visible) {
|
||||
dockRoot.menuHovered = hovered;
|
||||
} else {
|
||||
dockRoot.menuHovered = false;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: launcherContextMenu
|
||||
function onRequestClose() {
|
||||
dockRoot.currentContextMenu = null;
|
||||
dockRoot.hideTimer.stop();
|
||||
launcherContextMenu.hide();
|
||||
dockRoot.menuHovered = false;
|
||||
dockRoot.anyAppHovered = false;
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
dockRoot.currentContextMenu = launcherContextMenu;
|
||||
} else if (dockRoot.currentContextMenu === launcherContextMenu) {
|
||||
dockRoot.currentContextMenu = null;
|
||||
dockRoot.hideTimer.stop();
|
||||
dockRoot.menuHovered = false;
|
||||
if (dockRoot.autoHide && !dockRoot.dockHovered && !dockRoot.anyAppHovered && !dockRoot.peekHovered && !dockRoot.menuHovered) {
|
||||
dockRoot.hideTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -422,6 +611,7 @@ Item {
|
||||
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
const targetScreen = dockRoot.modelData || dockRoot.screen || null;
|
||||
// If right-clicking on the same app with an open context menu, close it
|
||||
if (dockRoot.currentContextMenu === contextMenu && contextMenu.visible) {
|
||||
dockRoot.closeAllContextMenus();
|
||||
@@ -431,7 +621,7 @@ Item {
|
||||
dockRoot.closeAllContextMenus();
|
||||
// Hide tooltip when showing context menu
|
||||
TooltipService.hideImmediately();
|
||||
contextMenu.show(appButton, modelData.toplevel || modelData);
|
||||
contextMenu.show(appButton, modelData.toplevel || modelData, targetScreen);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -521,6 +711,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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Commons
|
||||
import qs.Modules.Panels.Settings
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
@@ -13,6 +14,12 @@ PopupWindow {
|
||||
|
||||
property var toplevel: null
|
||||
property Item anchorItem: null
|
||||
property ShellScreen targetScreen: null
|
||||
|
||||
property string menuMode: "app" // "app" or "launcher"
|
||||
property string launcherWidgetSection: ""
|
||||
property int launcherWidgetIndex: -1
|
||||
property var launcherWidgetSettings: ({})
|
||||
|
||||
property bool hovered: menuMouseArea.containsMouse
|
||||
property var onAppClosed: null // Callback function for when an app is closed
|
||||
@@ -72,6 +79,34 @@ PopupWindow {
|
||||
}
|
||||
|
||||
function initItems() {
|
||||
if (menuMode === "launcher") {
|
||||
root.items = [
|
||||
{
|
||||
"icon": "adjustments",
|
||||
"text": I18n.tr("actions.dock-settings"),
|
||||
"action": function () {
|
||||
handleDockSettings();
|
||||
}
|
||||
},
|
||||
{
|
||||
"icon": "adjustments",
|
||||
"text": I18n.tr("actions.launcher-settings"),
|
||||
"action": function () {
|
||||
handleLauncherSettings();
|
||||
}
|
||||
},
|
||||
{
|
||||
"icon": "settings",
|
||||
"text": I18n.tr("actions.widget-settings"),
|
||||
"action": function () {
|
||||
handleLauncherWidgetSettings();
|
||||
}
|
||||
}
|
||||
];
|
||||
calculateMenuWidth();
|
||||
return;
|
||||
}
|
||||
|
||||
// Is this a running app?
|
||||
const isRunning = root.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(root.toplevel);
|
||||
|
||||
@@ -249,7 +284,7 @@ PopupWindow {
|
||||
}
|
||||
}
|
||||
|
||||
function show(item, toplevelData) {
|
||||
function show(item, toplevelData, screen) {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
@@ -260,6 +295,7 @@ PopupWindow {
|
||||
// Then set up new data
|
||||
anchorItem = item;
|
||||
toplevel = toplevelData;
|
||||
targetScreen = screen || null;
|
||||
initItems();
|
||||
|
||||
visible = true;
|
||||
@@ -315,6 +351,31 @@ PopupWindow {
|
||||
root.requestClose();
|
||||
}
|
||||
|
||||
function handleLauncherSettings() {
|
||||
if (targetScreen) {
|
||||
var panel = PanelService.getPanel("settingsPanel", targetScreen);
|
||||
panel.requestedTab = SettingsPanel.Tab.Launcher;
|
||||
panel.toggle();
|
||||
}
|
||||
root.requestClose();
|
||||
}
|
||||
|
||||
function handleDockSettings() {
|
||||
if (targetScreen) {
|
||||
var panel = PanelService.getPanel("settingsPanel", targetScreen);
|
||||
panel.requestedTab = SettingsPanel.Tab.Dock;
|
||||
panel.toggle();
|
||||
}
|
||||
root.requestClose();
|
||||
}
|
||||
|
||||
function handleLauncherWidgetSettings() {
|
||||
if (targetScreen && launcherWidgetSection && launcherWidgetIndex >= 0) {
|
||||
BarService.openWidgetSettings(targetScreen, launcherWidgetSection, launcherWidgetIndex, "Launcher", launcherWidgetSettings || {});
|
||||
}
|
||||
root.requestClose();
|
||||
}
|
||||
|
||||
// Short delay to ignore spurious events
|
||||
Timer {
|
||||
id: gracePeriodTimer
|
||||
|
||||
@@ -218,5 +218,34 @@ ColumnLayout {
|
||||
defaultValue: Settings.getDefaultValue("dock.colorizeIcons")
|
||||
onToggled: checked => Settings.data.dock.colorizeIcons = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.dock.appearance-show-launcher-icon-label")
|
||||
description: I18n.tr("panels.dock.appearance-show-launcher-icon-description")
|
||||
checked: Settings.data.dock.showLauncherIcon
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user