diff --git a/Widgets/Dock.qml b/Widgets/Dock.qml index 578a97a..a806346 100644 --- a/Widgets/Dock.qml +++ b/Widgets/Dock.qml @@ -6,345 +6,356 @@ import Quickshell.Widgets import qs.Settings import qs.Components -PanelWindow { - id: taskbarWindow - visible: Settings.settings.showDock && - (Settings.settings.dockMonitors.includes(modelData.name) || - (Settings.settings.dockMonitors.length === 0)) - screen: (typeof modelData !== 'undefined' ? modelData : null) - exclusionMode: ExclusionMode.Ignore - anchors.bottom: true - anchors.left: true - anchors.right: true - focusable: false - color: "transparent" - implicitHeight: 43 - // Auto-hide properties - property bool autoHide: true - property bool hidden: true - property int hideDelay: 500 - property int showDelay: 100 - property int hideAnimationDuration: 200 - property int showAnimationDuration: 150 - property int peekHeight: 2 - property int fullHeight: taskbarContainer.height +Variants { + model: Quickshell.screens - // Track hover state - property bool dockHovered: false - property bool anyAppHovered: false + Item { + property var modelData - // Context menu properties - property bool contextMenuVisible: false - property var contextMenuTarget: null - property var contextMenuToplevel: null + // Auto-hide properties + property bool autoHide: true + property bool hidden: true + property int hideDelay: 500 + property int showDelay: 100 + property int hideAnimationDuration: 200 + property int showAnimationDuration: 150 + property int peekHeight: 2 + property int fullHeight: dockContainer.height - // Timer for auto-hide delay - Timer { - id: hideTimer - interval: hideDelay - onTriggered: if (autoHide && !dockHovered && !anyAppHovered && !contextMenuVisible) hidden = true - } + // Track hover state + property bool dockHovered: false + property bool anyAppHovered: false - // Timer for show delay - Timer { - id: showTimer - interval: showDelay - onTriggered: hidden = false - } + // Context menu properties + property bool contextMenuVisible: false + property var contextMenuTarget: null + property var contextMenuToplevel: null - // Behavior for smooth hide/show animations - Behavior on margins.bottom { - NumberAnimation { - duration: hidden ? hideAnimationDuration : showAnimationDuration - easing.type: Easing.InOutQuad - } - } + PanelWindow { - // Mouse area at screen bottom to detect entry and keep dock visible - MouseArea { - id: screenEdgeMouseArea - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - height: 10 - hoverEnabled: true - propagateComposedEvents: true + id: dockWindow + visible: Settings.settings.showDock && + (Settings.settings.dockMonitors.includes(modelData.name) || + (Settings.settings.dockMonitors.length === 0)) + screen: modelData + exclusionMode: ExclusionMode.Ignore + anchors.bottom: true + anchors.left: true + anchors.right: true + focusable: false + color: "transparent" + implicitHeight: 43 - onEntered: if (autoHide && hidden) showTimer.start() - onExited: if (autoHide && !hidden && !dockHovered && !anyAppHovered && !contextMenuVisible) hideTimer.start() - } + // Timer for auto-hide delay + Timer { + id: hideTimer + interval: hideDelay + onTriggered: if (autoHide && !dockHovered && !anyAppHovered && !contextMenuVisible) hidden = true + } - margins.bottom: hidden ? -(fullHeight - peekHeight) : 0 + // Timer for show delay + Timer { + id: showTimer + interval: showDelay + onTriggered: hidden = false + } - Rectangle { - id: taskbarContainer - width: taskbar.width + 40 - height: Settings.settings.taskbarIconSize + 20 - topLeftRadius: 16 - topRightRadius: 16 - color: Theme.backgroundSecondary - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - - MouseArea { - id: dockMouseArea - anchors.fill: parent - hoverEnabled: true - propagateComposedEvents: true - - onEntered: { - dockHovered = true - if (autoHide) { - showTimer.stop() - hideTimer.stop() - hidden = false + // Behavior for smooth hide/show animations + Behavior on margins.bottom { + NumberAnimation { + duration: hidden ? hideAnimationDuration : showAnimationDuration + easing.type: Easing.InOutQuad } } - onExited: { - dockHovered = false - if (autoHide && !anyAppHovered && !contextMenuVisible) hideTimer.start() - } - } - Item { - id: taskbar - width: runningAppsRow.width - height: parent.height - 10 - anchors.centerIn: parent + // Mouse area at screen bottom to detect entry and keep dock visible + MouseArea { + id: screenEdgeMouseArea + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: 10 + hoverEnabled: true + propagateComposedEvents: true - StyledTooltip { id: styledTooltip } - - function getAppIcon(toplevel: Toplevel): string { - if (!toplevel) return ""; - let icon = Quickshell.iconPath(toplevel.appId?.toLowerCase(), true); - if (!icon) icon = Quickshell.iconPath(toplevel.appId, true); - if (!icon) icon = Quickshell.iconPath(toplevel.title?.toLowerCase(), true); - if (!icon) icon = Quickshell.iconPath(toplevel.title, true); - return icon || Quickshell.iconPath("application-x-executable", true); + onEntered: if (autoHide && hidden) showTimer.start() + onExited: if (autoHide && !hidden && !dockHovered && !anyAppHovered && !contextMenuVisible) hideTimer.start() } - Row { - id: runningAppsRow - spacing: 12 - height: parent.height - anchors.centerIn: parent + margins.bottom: hidden ? -(fullHeight - peekHeight) : 0 - Repeater { - model: ToplevelManager ? ToplevelManager.toplevels : null + Rectangle { + id: dockContainer + width: dock.width + 40 + height: Settings.settings.taskbarIconSize + 20 + topLeftRadius: 16 + topRightRadius: 16 + color: Theme.backgroundSecondary + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom - delegate: Rectangle { - id: appButton - width: Settings.settings.taskbarIconSize + 8 - height: Settings.settings.taskbarIconSize + 8 - radius: Math.max(6, Settings.settings.taskbarIconSize * 0.3) - color: isActive ? Theme.accentPrimary : (hovered ? Theme.surfaceVariant : "transparent") - border.color: isActive ? Qt.darker(Theme.accentPrimary, 1.2) : "transparent" - border.width: 1 + MouseArea { + id: dockMouseArea + anchors.fill: parent + hoverEnabled: true + propagateComposedEvents: true - property bool isActive: ToplevelManager.activeToplevel && ToplevelManager.activeToplevel === modelData - property bool hovered: appMouseArea.containsMouse - property string appId: modelData ? modelData.appId : "" - property string appTitle: modelData ? modelData.title : "" - - Behavior on color { ColorAnimation { duration: 150 } } - Behavior on border.color { ColorAnimation { duration: 150 } } - - IconImage { - id: appIcon - width: Math.max(20, Settings.settings.taskbarIconSize * 0.75) - height: Math.max(20, Settings.settings.taskbarIconSize * 0.75) - anchors.centerIn: parent - source: taskbar.getAppIcon(modelData) - visible: source.toString() !== "" + onEntered: { + dockHovered = true + if (autoHide) { + showTimer.stop() + hideTimer.stop() + hidden = false } + } + onExited: { + dockHovered = false + if (autoHide && !anyAppHovered && !contextMenuVisible) hideTimer.start() + } + } - Text { - anchors.centerIn: parent - visible: !appIcon.visible - text: appButton.appId ? appButton.appId.charAt(0).toUpperCase() : "?" - font.family: Theme.fontFamily - font.pixelSize: Math.max(14, Settings.settings.taskbarIconSize * 0.5) - font.bold: true - color: appButton.isActive ? Theme.onAccent : Theme.textPrimary - } + Item { + id: dock + width: runningAppsRow.width + height: parent.height - 10 + anchors.centerIn: parent - MouseArea { - id: appMouseArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + StyledTooltip { id: styledTooltip } - onEntered: { - anyAppHovered = true - if (!contextMenuVisible) { - styledTooltip.text = appTitle || appId; - styledTooltip.targetItem = appButton; - styledTooltip.positionAbove = true; - styledTooltip.tooltipVisible = true; + function getAppIcon(toplevel: Toplevel): string { + if (!toplevel) return ""; + let icon = Quickshell.iconPath(toplevel.appId?.toLowerCase(), true); + if (!icon) icon = Quickshell.iconPath(toplevel.appId, true); + if (!icon) icon = Quickshell.iconPath(toplevel.title?.toLowerCase(), true); + if (!icon) icon = Quickshell.iconPath(toplevel.title, true); + return icon || Quickshell.iconPath("application-x-executable", true); + } + + Row { + id: runningAppsRow + spacing: 12 + height: parent.height + anchors.centerIn: parent + + Repeater { + model: ToplevelManager ? ToplevelManager.toplevels : null + + delegate: Rectangle { + id: appButton + width: Settings.settings.taskbarIconSize + 8 + height: Settings.settings.taskbarIconSize + 8 + radius: Math.max(6, Settings.settings.taskbarIconSize * 0.3) + color: isActive ? Theme.accentPrimary : (hovered ? Theme.surfaceVariant : "transparent") + border.color: isActive ? Qt.darker(Theme.accentPrimary, 1.2) : "transparent" + border.width: 1 + + property bool isActive: ToplevelManager.activeToplevel && ToplevelManager.activeToplevel === modelData + property bool hovered: appMouseArea.containsMouse + property string appId: modelData ? modelData.appId : "" + property string appTitle: modelData ? modelData.title : "" + + Behavior on color { ColorAnimation { duration: 150 } } + Behavior on border.color { ColorAnimation { duration: 150 } } + + IconImage { + id: appIcon + width: Math.max(20, Settings.settings.taskbarIconSize * 0.75) + height: Math.max(20, Settings.settings.taskbarIconSize * 0.75) + anchors.centerIn: parent + source: dock.getAppIcon(modelData) + visible: source.toString() !== "" } - if (autoHide) { - showTimer.stop() - hideTimer.stop() - hidden = false + + Text { + anchors.centerIn: parent + visible: !appIcon.visible + text: appButton.appId ? appButton.appId.charAt(0).toUpperCase() : "?" + font.family: Theme.fontFamily + font.pixelSize: Math.max(14, Settings.settings.taskbarIconSize * 0.5) + font.bold: true + color: appButton.isActive ? Theme.onAccent : Theme.textPrimary + } + + MouseArea { + id: appMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + + onEntered: { + anyAppHovered = true + if (!contextMenuVisible) { + styledTooltip.text = appTitle || appId; + styledTooltip.targetItem = appButton; + styledTooltip.positionAbove = true; + styledTooltip.tooltipVisible = true; + } + if (autoHide) { + showTimer.stop() + hideTimer.stop() + hidden = false + } + } + onExited: { + anyAppHovered = false + if (!contextMenuVisible) { + styledTooltip.tooltipVisible = false; + } + if (autoHide && !dockHovered && !contextMenuVisible) hideTimer.start() + } + onClicked: function(mouse) { + if (mouse.button === Qt.MiddleButton && modelData?.close) { + modelData.close(); + } + if (mouse.button === Qt.LeftButton && modelData?.activate) { + modelData.activate(); + } + if (mouse.button === Qt.RightButton) { + styledTooltip.tooltipVisible = false; + contextMenuTarget = appButton; + contextMenuToplevel = modelData; + contextMenuVisible = true; + } + } + } + + Rectangle { + visible: isActive + width: 6 + height: 6 + radius: 3 + color: Theme.onAccent + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottomMargin: -8 } } - onExited: { - anyAppHovered = false - if (!contextMenuVisible) { - styledTooltip.tooltipVisible = false; - } - if (autoHide && !dockHovered && !contextMenuVisible) hideTimer.start() - } - onClicked: function(mouse) { - if (mouse.button === Qt.MiddleButton && modelData?.close) { - modelData.close(); - } - if (mouse.button === Qt.LeftButton && modelData?.activate) { - modelData.activate(); - } - if (mouse.button === Qt.RightButton) { - styledTooltip.tooltipVisible = false; - contextMenuTarget = appButton; - contextMenuToplevel = modelData; - contextMenuVisible = true; - } - } - } - - Rectangle { - visible: isActive - width: 6 - height: 6 - radius: 3 - color: Theme.onAccent - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottomMargin: -8 } } } } - } - } - // Context Menu - PanelWindow { - id: contextMenuWindow - visible: contextMenuVisible - screen: taskbarWindow.screen - exclusionMode: ExclusionMode.Ignore - anchors.bottom: true - anchors.left: true - anchors.right: true - color: "transparent" - focusable: false - - MouseArea { - anchors.fill: parent - onClicked: { - contextMenuVisible = false; - contextMenuTarget = null; - contextMenuToplevel = null; - hidden = true; // Hide dock when context menu closes - } - } - - Rectangle { - id: contextMenuContainer - width: 80 - height: contextMenuColumn.height + 0 - radius: 16 - color: Theme.backgroundPrimary - border.color: Theme.outline - border.width: 1 - - x: { - if (!contextMenuTarget) return 0; - // Get position relative to screen - const pos = contextMenuTarget.mapToItem(null, 0, 0); - // Center horizontally above the icon - let xPos = pos.x + (contextMenuTarget.width - width) / 2; - // Constrain to screen edges - return Math.max(0, Math.min(xPos, taskbarWindow.width - width)); - } - - y: { - if (!contextMenuTarget) return 0; - // Position above the dock - const pos = contextMenuTarget.mapToItem(null, 0, 0); - return pos.y - height + 32; - } - - Column { - id: contextMenuColumn - anchors.centerIn: parent - spacing: 4 - width: parent.width + // Context Menu + PanelWindow { + id: contextMenuWindow + visible: contextMenuVisible + screen: dockWindow.screen + exclusionMode: ExclusionMode.Ignore + anchors.bottom: true + anchors.left: true + anchors.right: true + color: "transparent" + focusable: false + MouseArea { + anchors.fill: parent + onClicked: { + contextMenuVisible = false; + contextMenuTarget = null; + contextMenuToplevel = null; + hidden = true; // Hide dock when context menu closes + } + } Rectangle { - width: parent.width - height: 32 + id: contextMenuContainer + width: 80 + height: contextMenuColumn.height + 0 radius: 16 - color: closeMouseArea.containsMouse ? Theme.surfaceVariant : "transparent" + color: Theme.backgroundPrimary border.color: Theme.outline border.width: 1 - Row { - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.verticalCenter: parent.verticalCenter + x: { + if (!contextMenuTarget) return 0; + // Get position relative to screen + const pos = contextMenuTarget.mapToItem(null, 0, 0); + // Center horizontally above the icon + let xPos = pos.x + (contextMenuTarget.width - width) / 2; + // Constrain to screen edges + return Math.max(0, Math.min(xPos, dockWindow.width - width)); + } + + y: { + if (!contextMenuTarget) return 0; + // Position above the dock + const pos = contextMenuTarget.mapToItem(null, 0, 0); + return pos.y - height + 32; + } + + Column { + id: contextMenuColumn + anchors.centerIn: parent spacing: 4 + width: parent.width - Text { - anchors.verticalCenter: parent.verticalCenter - text: "close" - font.family: "Material Symbols Outlined" - font.pixelSize: 14 * Theme.scale(Screen) - color: Theme.textPrimary - } - Text { - anchors.verticalCenter: parent.verticalCenter - text: "Close" - font.family: Theme.fontFamily - font.pixelSize: 14 * Theme.scale(Screen) - color: Theme.textPrimary + Rectangle { + width: parent.width + height: 32 + radius: 16 + color: closeMouseArea.containsMouse ? Theme.surfaceVariant : "transparent" + border.color: Theme.outline + border.width: 1 + + Row { + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.verticalCenter: parent.verticalCenter + spacing: 4 + + Text { + anchors.verticalCenter: parent.verticalCenter + text: "close" + font.family: "Material Symbols Outlined" + font.pixelSize: 14 * Theme.scale(Screen) + color: Theme.textPrimary + } + + Text { + anchors.verticalCenter: parent.verticalCenter + text: "Close" + font.family: Theme.fontFamily + font.pixelSize: 14 * Theme.scale(Screen) + color: Theme.textPrimary + } + } + + MouseArea { + id: closeMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + + onClicked: { + if (contextMenuToplevel?.close) contextMenuToplevel.close(); + contextMenuVisible = false; + hidden = true; + } + } } } - MouseArea { - id: closeMouseArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor + // Animation + scale: contextMenuVisible ? 1 : 0.9 + opacity: contextMenuVisible ? 1 : 0 + transformOrigin: Item.Bottom - onClicked: { - if (contextMenuToplevel?.close) contextMenuToplevel.close(); - contextMenuVisible = false; - hidden = true; + Behavior on scale { + NumberAnimation { + duration: 150 + easing.type: Easing.OutBack } } + + Behavior on opacity { + NumberAnimation { duration: 100 } + } } } - - // Animation - scale: contextMenuVisible ? 1 : 0.9 - opacity: contextMenuVisible ? 1 : 0 - transformOrigin: Item.Bottom - - Behavior on scale { - NumberAnimation { - duration: 150 - easing.type: Easing.OutBack - } - } - - Behavior on opacity { - NumberAnimation { duration: 100 } - } } + } } diff --git a/shell.qml b/shell.qml index 899dedd..26f9726 100644 --- a/shell.qml +++ b/shell.qml @@ -16,7 +16,6 @@ import qs.Helpers Scope { id: root - property alias appLauncherPanel: appLauncherPanel property var notificationHistoryWin: notificationHistoryLoader.active ? notificationHistoryLoader.item : null property var settingsWindow: null property bool pendingReload: false @@ -59,19 +58,9 @@ Scope { Bar { id: bar - shell: root property var notificationHistoryWin: notificationHistoryLoader.active ? notificationHistoryLoader.item : null } - Variants { - model: Quickshell.screens - - Dock { - id: dock - property var modelData - } - } - Dock { id: dock }