diff --git a/quickshell/Modules/Dock/Dock.qml b/quickshell/Modules/Dock/Dock.qml index aaccb195..e94f3f50 100644 --- a/quickshell/Modules/Dock/Dock.qml +++ b/quickshell/Modules/Dock/Dock.qml @@ -46,14 +46,10 @@ Variants { property bool groupByApp: SettingsData.dockGroupByApp readonly property int borderThickness: SettingsData.dockBorderEnabled ? SettingsData.dockBorderThickness : 0 readonly property string connectedBarSide: SettingsData.dockPosition === SettingsData.Position.Top ? "top" : SettingsData.dockPosition === SettingsData.Position.Bottom ? "bottom" : SettingsData.dockPosition === SettingsData.Position.Left ? "left" : "right" - readonly property bool connectedBarActiveOnEdge: Theme.isConnectedEffect && !!(dock.screen || modelData) && SettingsData.getActiveBarEdgesForScreen(dock.screen || modelData).includes(connectedBarSide) - readonly property real connectedJoinInset: { - if (Theme.isConnectedEffect) - return connectedBarActiveOnEdge ? SettingsData.frameBarSize : SettingsData.frameThickness; - if (SettingsData.frameEnabled) - return SettingsData.frameEdgeInsetForSide(dock.screen || modelData, dock.connectedBarSide); - return 0; - } + readonly property bool frameDockExclusionActive: dockGeometry.frameExclusionActive + readonly property bool connectedBarActiveOnEdge: dockGeometry.connectedBarActiveOnEdge + readonly property real connectedJoinInset: dockGeometry.connectedJoinInset + readonly property real dockFrameInset: dockGeometry.frameInset readonly property real surfaceRadius: Theme.connectedSurfaceRadius readonly property color surfaceColor: Theme.isConnectedEffect ? Theme.connectedSurfaceColor : Theme.withAlpha(Theme.surfaceContainer, backgroundTransparency) readonly property color surfaceBorderColor: Theme.isConnectedEffect ? "transparent" : BlurService.borderColor @@ -68,7 +64,7 @@ Variants { readonly property int hasApps: dockApps.implicitWidth > 0 || dockApps.implicitHeight > 0 readonly property real widgetHeight: SettingsData.dockIconSize - readonly property real effectiveBarHeight: widgetHeight + SettingsData.dockSpacing * 2 + 10 + borderThickness * 2 + readonly property real effectiveBarHeight: dockGeometry.visualThickness function getBarHeight(barConfig) { if (!barConfig) return 0; @@ -137,15 +133,32 @@ Variants { readonly property real dockMargin: SettingsData.dockMargin readonly property bool effectiveBlurEnabled: Theme.connectedSurfaceBlurEnabled - readonly property real effectiveDockBottomGap: Theme.isConnectedEffect ? 0 : SettingsData.dockBottomGap - readonly property real effectiveDockMargin: Theme.isConnectedEffect ? 0 : SettingsData.dockMargin + readonly property real effectiveDockBottomGap: dockGeometry.visualOffset + readonly property real effectiveDockMargin: dockGeometry.effectiveMargin readonly property real positionSpacing: barSpacing + effectiveDockBottomGap + effectiveDockMargin - readonly property real joinedEdgeMargin: Theme.isConnectedEffect ? 0 : (barSpacing + effectiveDockMargin + 1 + dock.borderThickness) + readonly property real joinedEdgeMargin: dockGeometry.joinedEdgeMargin readonly property real _dpr: (dock.screen && dock.screen.devicePixelRatio) ? dock.screen.devicePixelRatio : 1 function px(v) { return Math.round(v * _dpr) / _dpr; } + DockGeometry { + id: dockGeometry + + screen: dock.screen || dock.modelData + edge: dock.connectedBarSide + dockVisible: dock.visible + autoHide: dock.autoHide + hasFullscreenToplevel: dock.hasFullscreenToplevel + iconSize: dock.widgetHeight + spacing: SettingsData.dockSpacing + borderThickness: dock.borderThickness + offset: SettingsData.dockBottomGap + margin: SettingsData.dockMargin + barSpacing: dock.barSpacing + dpr: dock._dpr + } + // Dock window origin in screen-relative coordinates (FrameWindow space). function _dockWindowOriginX() { if (!dock.isVertical) @@ -231,7 +244,7 @@ Variants { return false; const screenName = dock.modelData?.name ?? ""; - const dockThickness = dock.connectedJoinInset + effectiveBarHeight + SettingsData.dockSpacing + dock.effectiveDockBottomGap + dock.effectiveDockMargin; + const dockThickness = dockGeometry.motionThickness; const screenWidth = dock.screen?.width ?? 0; const screenHeight = dock.screen?.height ?? 0; @@ -434,20 +447,21 @@ Variants { } color: "transparent" + readonly property real dockReserveZone: dockGeometry.reserveZone + readonly property bool shouldReserveDockSpace: dockGeometry.shouldReserveSpace + exclusiveZone: { - if (dock.hasFullscreenToplevel) + if (!dock.shouldReserveDockSpace) return -1; - if (!SettingsData.showDock || autoHide) + if (dock.frameDockExclusionActive) return -1; - if (barSpacing > 0) - return -1; - return px(connectedJoinInset + effectiveBarHeight + SettingsData.dockSpacing + effectiveDockBottomGap + effectiveDockMargin); + return dock.dockReserveZone; } property real animationHeadroom: Math.ceil(SettingsData.dockIconSize * 0.35) - implicitWidth: isVertical ? (px(connectedJoinInset + effectiveBarHeight + SettingsData.dockSpacing + effectiveDockMargin + SettingsData.dockIconSize * 0.3) + animationHeadroom) : 0 - implicitHeight: !isVertical ? (px(connectedJoinInset + effectiveBarHeight + SettingsData.dockSpacing + effectiveDockMargin + SettingsData.dockIconSize * 0.3) + animationHeadroom) : 0 + implicitWidth: isVertical ? (px(dockGeometry.surfaceThickness + SettingsData.dockIconSize * 0.3) + animationHeadroom) : 0 + implicitHeight: !isVertical ? (px(dockGeometry.surfaceThickness + SettingsData.dockIconSize * 0.3) + animationHeadroom) : 0 Item { id: maskItem @@ -474,6 +488,28 @@ Variants { item: maskItem } + PanelWindow { + id: dockExclusion + + screen: dock.screen || dock.modelData + visible: dock.frameDockExclusionActive && dock.shouldReserveDockSpace + color: "transparent" + mask: Region {} + implicitWidth: dock.isVertical ? dock.dockReserveZone : 1 + implicitHeight: dock.isVertical ? 1 : dock.dockReserveZone + exclusiveZone: visible ? dock.dockReserveZone : -1 + + WlrLayershell.namespace: "dms:dock-exclusion" + WlrLayershell.layer: WlrLayer.Top + + anchors { + top: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Top) : true + bottom: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom) : true + left: !dock.isVertical ? true : (SettingsData.dockPosition === SettingsData.Position.Left) + right: !dock.isVertical ? true : (SettingsData.dockPosition === SettingsData.Position.Right) + } + } + property var hoveredButton: { if (!dockApps.children[0]) { return null; @@ -527,7 +563,7 @@ Variants { const screenHeight = dock.screen ? dock.screen.height : 0; const gap = Theme.spacingS; - const bgMargin = dock.joinedEdgeMargin + dock.connectedJoinInset; + const bgMargin = dockGeometry.bodyEdgeMargin; const btnW = dock.hoveredButton.width; const btnH = dock.hoveredButton.height; @@ -598,11 +634,11 @@ Variants { // Keep the taller hit area regardless of the reveal state to prevent shrinking loop return Math.min(Math.max(dockBackground.height + 64, 200), maxDockHeight); } - return dock.reveal ? px(dock.connectedJoinInset + dock.effectiveBarHeight + SettingsData.dockSpacing + dock.effectiveDockBottomGap + dock.effectiveDockMargin) : 1; + return dock.reveal ? px(dockGeometry.motionThickness) : 1; } width: { if (dock.isVertical) { - return dock.reveal ? px(dock.connectedJoinInset + dock.effectiveBarHeight + SettingsData.dockSpacing + dock.effectiveDockBottomGap + dock.effectiveDockMargin) : 1; + return dock.reveal ? px(dockGeometry.motionThickness) : 1; } // Keep the wider hit area regardless of the reveal state to prevent shrinking loop return Math.min(dockBackground.width + 8 + dock.borderThickness, maxDockWidth); @@ -648,7 +684,7 @@ Variants { const retractDist = dockBackground.width + SettingsData.dockSpacing + 10; return SettingsData.dockPosition === SettingsData.Position.Right ? retractDist : -retractDist; } - const hideDistance = dock.connectedJoinInset + dock.effectiveBarHeight + SettingsData.dockSpacing + dock.effectiveDockBottomGap + dock.effectiveDockMargin + 10; + const hideDistance = dockGeometry.motionThickness + 10; if (SettingsData.dockPosition === SettingsData.Position.Right) { return hideDistance; } else { @@ -664,7 +700,7 @@ Variants { const retractDist = dockBackground.height + SettingsData.dockSpacing + 10; return SettingsData.dockPosition === SettingsData.Position.Bottom ? retractDist : -retractDist; } - const hideDistance = dock.connectedJoinInset + dock.effectiveBarHeight + SettingsData.dockSpacing + dock.effectiveDockBottomGap + dock.effectiveDockMargin + 10; + const hideDistance = dockGeometry.motionThickness + 10; if (SettingsData.dockPosition === SettingsData.Position.Bottom) { return hideDistance; } else { @@ -709,10 +745,10 @@ Variants { right: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? parent.right : undefined) : undefined verticalCenter: dock.isVertical ? parent.verticalCenter : undefined } - anchors.topMargin: !dock.isVertical && SettingsData.dockPosition === SettingsData.Position.Top ? (dock.connectedJoinInset + dock.joinedEdgeMargin) : 0 - anchors.bottomMargin: !dock.isVertical && SettingsData.dockPosition === SettingsData.Position.Bottom ? (dock.connectedJoinInset + dock.joinedEdgeMargin) : 0 - anchors.leftMargin: dock.isVertical && SettingsData.dockPosition === SettingsData.Position.Left ? (dock.connectedJoinInset + dock.joinedEdgeMargin) : 0 - anchors.rightMargin: dock.isVertical && SettingsData.dockPosition === SettingsData.Position.Right ? (dock.connectedJoinInset + dock.joinedEdgeMargin) : 0 + anchors.topMargin: !dock.isVertical && SettingsData.dockPosition === SettingsData.Position.Top ? dockGeometry.bodyEdgeMargin : 0 + anchors.bottomMargin: !dock.isVertical && SettingsData.dockPosition === SettingsData.Position.Bottom ? dockGeometry.bodyEdgeMargin : 0 + anchors.leftMargin: dock.isVertical && SettingsData.dockPosition === SettingsData.Position.Left ? dockGeometry.bodyEdgeMargin : 0 + anchors.rightMargin: dock.isVertical && SettingsData.dockPosition === SettingsData.Position.Right ? dockGeometry.bodyEdgeMargin : 0 implicitWidth: dock.isVertical ? (dockApps.implicitHeight + SettingsData.dockSpacing * 2) : (dockApps.implicitWidth + SettingsData.dockSpacing * 2) implicitHeight: dock.isVertical ? (dockApps.implicitWidth + SettingsData.dockSpacing * 2) : (dockApps.implicitHeight + SettingsData.dockSpacing * 2) diff --git a/quickshell/Modules/Dock/DockGeometry.qml b/quickshell/Modules/Dock/DockGeometry.qml new file mode 100644 index 00000000..7c268843 --- /dev/null +++ b/quickshell/Modules/Dock/DockGeometry.qml @@ -0,0 +1,61 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import qs.Common + +QtObject { + id: root + + property var screen: null + property string edge: "bottom" + property bool dockVisible: false + property bool autoHide: false + property bool hasFullscreenToplevel: false + property real iconSize: 40 + property real spacing: 4 + property real borderThickness: 0 + property real offset: 0 + property real margin: 0 + property real barSpacing: 0 + property real dpr: 1 + + function px(value) { + return Math.round(value * dpr) / dpr; + } + + readonly property bool frameExclusionActive: SettingsData.frameEnabled && !!screen && SettingsData.isScreenInPreferences(screen, SettingsData.frameScreenPreferences) + readonly property bool connectedMode: Theme.isConnectedEffect + readonly property bool connectedBarActiveOnEdge: connectedMode && !!screen && SettingsData.getActiveBarEdgesForScreen(screen).includes(edge) + + readonly property real connectedJoinInset: { + if (connectedMode) + return connectedBarActiveOnEdge ? SettingsData.frameBarSize : SettingsData.frameThickness; + if (SettingsData.frameEnabled) + return SettingsData.frameEdgeInsetForSide(screen, edge); + return 0; + } + + readonly property real frameInset: { + if (!frameExclusionActive) + return 0; + if (connectedMode) + return connectedJoinInset; + return SettingsData.frameThickness; + } + + readonly property real effectiveMargin: connectedMode ? 0 : margin + readonly property real visualOffset: connectedMode ? 0 : offset + readonly property real reserveOffset: offset + readonly property real joinedEdgeMargin: connectedMode ? 0 : (barSpacing + effectiveMargin + 1 + borderThickness) + readonly property real bodyEdgeMargin: frameInset + joinedEdgeMargin + + readonly property real bodyThickness: iconSize + spacing * 2 + borderThickness * 2 + readonly property real visualThickness: bodyThickness + 10 + readonly property real surfaceThickness: frameInset + visualThickness + spacing + effectiveMargin + readonly property real motionThickness: surfaceThickness + visualOffset + + // Frame/bar edge exclusions already reserve the edge itself, so the dock + // reservation covers only the dock body and user offset beyond that edge. + readonly property real reserveZone: px(bodyThickness + reserveOffset + effectiveMargin) + readonly property bool shouldReserveSpace: dockVisible && !hasFullscreenToplevel && !autoHide && barSpacing <= 0 +} diff --git a/quickshell/Modules/Frame/FrameWindow.qml b/quickshell/Modules/Frame/FrameWindow.qml index 3e6195cf..54a0ac2a 100644 --- a/quickshell/Modules/Frame/FrameWindow.qml +++ b/quickshell/Modules/Frame/FrameWindow.qml @@ -91,6 +91,8 @@ PanelWindow { readonly property real _popoutFillOverlapYValue: (ConnectedModeState.popoutBarSide === "left" || ConnectedModeState.popoutBarSide === "right") ? win._seamOverlap : 0 readonly property real _dockFillOverlapXValue: win._dockHorizontal ? win._seamOverlap : 0 readonly property real _dockFillOverlapYValue: (win._dockState.barSide === "left" || win._dockState.barSide === "right") ? win._seamOverlap : 0 + readonly property real _dockJoinOverlapXValue: ConnectorGeometry.isVertical(win._dockState.barSide) ? win._seamOverlap : 0 + readonly property real _dockJoinOverlapYValue: ConnectorGeometry.isHorizontal(win._dockState.barSide) ? win._seamOverlap : 0 readonly property real _notifSideUnderlapValue: ConnectorGeometry.isVertical(win._notifState.barSide) ? win._seamOverlap : 0 readonly property real _notifStartUnderlapValue: win._notifState.omitStartConnector ? win._seamOverlap : 0 readonly property real _notifEndUnderlapValue: win._notifState.omitEndConnector ? win._seamOverlap : 0 @@ -1117,6 +1119,14 @@ PanelWindow { return ((win._dockState.barSide === "left" || win._dockState.barSide === "right") ? win._dockConnectorRadiusValue : 0) - win._dockFillOverlapYValue; } + function _dockJoinOverlapXOffset() { + return win._dockState.barSide === "left" ? -win._dockJoinOverlapXValue : 0; + } + + function _dockJoinOverlapYOffset() { + return win._dockState.barSide === "top" ? -win._dockJoinOverlapYValue : 0; + } + function _farConnectorBarSide(sourceSide, placement) { if (sourceSide === "top" || sourceSide === "bottom") return placement === "left" ? "left" : "right"; @@ -1359,10 +1369,10 @@ PanelWindow { Rectangle { id: _dockFill - x: win._dockBodyXInChrome() - y: win._dockBodyYInChrome() - width: _dockBodyBlurAnchor.width + win._dockFillOverlapXValue * 2 - height: _dockBodyBlurAnchor.height + win._dockFillOverlapYValue * 2 + x: win._dockBodyXInChrome() + win._dockJoinOverlapXOffset() + y: win._dockBodyYInChrome() + win._dockJoinOverlapYOffset() + width: _dockBodyBlurAnchor.width + win._dockFillOverlapXValue * 2 + win._dockJoinOverlapXValue + height: _dockBodyBlurAnchor.height + win._dockFillOverlapYValue * 2 + win._dockJoinOverlapYValue color: win._opaqueSurfaceColor z: 1 diff --git a/quickshell/Modules/Settings/DockTab.qml b/quickshell/Modules/Settings/DockTab.qml index 0dc37cfc..88515f86 100644 --- a/quickshell/Modules/Settings/DockTab.qml +++ b/quickshell/Modules/Settings/DockTab.qml @@ -10,6 +10,7 @@ Item { property var parentModal: null readonly property bool connectedFrameModeActive: SettingsData.connectedFrameModeActive + readonly property bool connectedPersistentDockActive: connectedFrameModeActive && SettingsData.showDock && !SettingsData.dockAutoHide && !SettingsData.dockSmartAutoHide FileBrowserModal { id: dockLogoFileBrowser @@ -611,16 +612,18 @@ Item { value: SettingsData.dockSpacing minimum: 0 maximum: 32 + unit: "px" defaultValue: 8 onSliderValueChanged: newValue => SettingsData.set("dockSpacing", newValue) } SettingsSliderRow { text: I18n.tr("Exclusive Zone Offset") - visible: !root.connectedFrameModeActive + visible: !root.connectedFrameModeActive || root.connectedPersistentDockActive value: SettingsData.dockBottomGap minimum: -100 maximum: 100 + unit: "px" defaultValue: 0 onSliderValueChanged: newValue => SettingsData.set("dockBottomGap", newValue) } @@ -631,6 +634,7 @@ Item { value: SettingsData.dockMargin minimum: 0 maximum: 100 + unit: "px" defaultValue: 0 onSliderValueChanged: newValue => SettingsData.set("dockMargin", newValue) } @@ -639,7 +643,7 @@ Item { SettingsControlledByFrame { visible: root.connectedFrameModeActive parentModal: root.parentModal - settingLabel: I18n.tr("Dock spacing, transparency, and border") + settingLabel: I18n.tr("Dock margin, transparency, and border") reason: I18n.tr("Managed by Frame in Connected Mode") }