import QtQuick import QtQuick.Controls import QtQuick.Layouts import QtQuick.Effects import Quickshell import Quickshell.Wayland import Quickshell.Widgets import qs.Commons import qs.Services import qs.Widgets Variants { model: Quickshell.screens delegate: Loader { required property ShellScreen modelData property real scaling: ScalingService.getScreenScale(modelData) Connections { target: ScalingService function onScaleChanged(screenName, scale) { if (screenName === modelData.name) { scaling = scale } } } active: Settings.isLoaded && modelData ? Settings.data.dock.monitors.includes(modelData.name) : false sourceComponent: PanelWindow { id: dockWindow screen: modelData property bool autoHide: Settings.data.dock.autoHide property bool hidden: autoHide property int hideDelay: 500 property int showDelay: 100 property int hideAnimationDuration: Style.animationFast property int showAnimationDuration: Style.animationFast property int peekHeight: 7 * scaling property int fullHeight: dockContainer.height property int iconSize: 36 // Bar positioning properties property bool barAtBottom: Settings.data.bar.position === "bottom" property int barHeight: barAtBottom ? (Settings.data.bar.height || 30) * scaling : 0 property int dockSpacing: 4 * scaling // Space between dock and bar // Track hover state property bool dockHovered: false property bool anyAppHovered: false // Dock is positioned at the bottom anchors.bottom: true // Dock should be above bar but not create its own exclusion zone exclusionMode: ExclusionMode.Ignore focusable: false // Make the window transparent color: Color.transparent // Set the window size - always include space for peek area when auto-hide is enabled implicitWidth: dockContainer.width implicitHeight: fullHeight + (barAtBottom ? barHeight + dockSpacing : 0) // Position the entire window above the bar when bar is at bottom margins.bottom: barAtBottom ? barHeight : 0 // Watch for autoHide setting changes onAutoHideChanged: { if (!autoHide) { // If auto-hide is disabled, show the dock hidden = false hideTimer.stop() showTimer.stop() } else { // If auto-hide is enabled, start hidden hidden = true } } // Timer for auto-hide delay Timer { id: hideTimer interval: hideDelay onTriggered: { if (autoHide && !dockHovered && !anyAppHovered && !peekArea.containsMouse) { hidden = true } } } // Timer for show delay Timer { id: showTimer interval: showDelay onTriggered: { if (autoHide) { hidden = false } } } // Peek area that remains visible when dock is hidden MouseArea { id: peekArea anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right height: peekHeight + dockSpacing hoverEnabled: autoHide visible: autoHide onEntered: { if (autoHide && hidden) { showTimer.start() } } onExited: { if (autoHide && !hidden && !dockHovered && !anyAppHovered) { hideTimer.restart() } } } Rectangle { id: dockContainer width: dock.width + 48 * scaling height: iconSize * 1.4 * scaling color: Color.mSurface anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom anchors.bottomMargin: dockSpacing topLeftRadius: Style.radiusL * scaling topRightRadius: Style.radiusL * scaling // Animate the dock sliding up and down transform: Translate { y: hidden ? (fullHeight - peekHeight) : 0 Behavior on y { NumberAnimation { duration: hidden ? hideAnimationDuration : showAnimationDuration easing.type: Easing.InOutQuad } } } MouseArea { id: dockMouseArea anchors.fill: parent hoverEnabled: true onEntered: { dockHovered = true if (autoHide) { showTimer.stop() hideTimer.stop() if (hidden) { hidden = false } } } onExited: { dockHovered = false // Only start hide timer if we're not hovering over any app or the peek area if (autoHide && !anyAppHovered && !peekArea.containsMouse) { hideTimer.restart() } } } Item { id: dock width: runningAppsRow.width height: parent.height - (20 * scaling) anchors.centerIn: parent NTooltip { id: appTooltip visible: false positionAbove: true } function getAppIcon(toplevel: Toplevel): string { if (!toplevel) return "" return Icons.iconForAppId(toplevel.appId?.toLowerCase()) } Row { id: runningAppsRow spacing: Style.marginL * scaling height: parent.height anchors.centerIn: parent Repeater { model: ToplevelManager ? ToplevelManager.toplevels : null delegate: Rectangle { id: appButton width: iconSize * scaling height: iconSize * scaling color: Color.transparent radius: Style.radiusM * scaling 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 : "" // The icon Image { id: appIcon width: iconSize * scaling height: iconSize * scaling anchors.centerIn: parent source: dock.getAppIcon(modelData) visible: source.toString() !== "" smooth: true mipmap: false antialiasing: false fillMode: Image.PreserveAspectFit scale: appButton.hovered ? 1.1 : 1.0 Behavior on scale { NumberAnimation { duration: Style.animationFast easing.type: Easing.OutBack } } } // Fall back if no icon NText { anchors.centerIn: parent visible: !appIcon.visible text: "question_mark" font.family: "Material Symbols Rounded" font.pointSize: iconSize * 0.7 * scaling color: appButton.isActive ? Color.mPrimary : Color.mOnSurfaceVariant scale: appButton.hovered ? 1.1 : 1.0 Behavior on scale { NumberAnimation { duration: Style.animationFast easing.type: Easing.OutBack } } } MouseArea { id: appMouseArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor acceptedButtons: Qt.LeftButton | Qt.MiddleButton onEntered: { anyAppHovered = true const appName = appButton.appTitle || appButton.appId || "Unknown" appTooltip.text = appName.length > 40 ? appName.substring(0, 37) + "..." : appName appTooltip.target = appButton appTooltip.isVisible = true if (autoHide) { showTimer.stop() hideTimer.stop() if (hidden) { hidden = false } } } onExited: { anyAppHovered = false appTooltip.hide() // Only start hide timer if we're not hovering over the dock or peek area if (autoHide && !dockHovered && !peekArea.containsMouse) { hideTimer.restart() } } onClicked: function (mouse) { if (mouse.button === Qt.MiddleButton && modelData?.close) { modelData.close() } if (mouse.button === Qt.LeftButton && modelData?.activate) { modelData.activate() } } } Rectangle { visible: isActive width: iconSize * 0.25 height: 4 * scaling color: Color.mPrimary radius: Style.radiusXS anchors.top: parent.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.topMargin: Style.marginXXS * scaling } } } } } } } } }