From 50685937da2b25d645b42f661b02fd6a964c0dbd Mon Sep 17 00:00:00 2001 From: Lemmy Date: Mon, 29 Dec 2025 22:08:48 -0500 Subject: [PATCH] Bar: Improve centering by computing pixel perfect coordinates. All basic widgets + workspace. --- Commons/Style.qml | 7 ++++- Modules/Bar/Bar.qml | 12 ++++---- Modules/Bar/Widgets/Workspace.qml | 47 ++++++++++++++--------------- Modules/MainScreen/MainScreen.qml | 13 ++++---- Modules/MainScreen/SmartPanel.qml | 50 +++++++++++++++++++------------ 5 files changed, 71 insertions(+), 58 deletions(-) diff --git a/Commons/Style.qml b/Commons/Style.qml index d35f2011d..b829c2dc8 100644 --- a/Commons/Style.qml +++ b/Commons/Style.qml @@ -90,7 +90,7 @@ Singleton { let h; switch (Settings.data.bar.density) { case "mini": - h = (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 22 : 20; + h = (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 23 : 21; break; case "compact": h = (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 27 : 25; @@ -136,4 +136,9 @@ Singleton { function toOdd(n) { return Math.floor(n / 2) * 2 + 1; } + + // Ensures a number is always even (rounds down to nearest even) + function toEven(n) { + return Math.floor(n / 2) * 2; + } } diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index 59c6a314e..1bda2aafb 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -211,7 +211,7 @@ Item { // Top section (left widgets) ColumnLayout { - anchors.horizontalCenter: parent.horizontalCenter + x: Style.pixelAlignCenter(parent.width, width) anchors.top: parent.top anchors.topMargin: Style.marginM spacing: Style.marginS @@ -238,7 +238,7 @@ Item { // Center section (center widgets) ColumnLayout { - anchors.horizontalCenter: parent.horizontalCenter + x: Style.pixelAlignCenter(parent.width, width) anchors.verticalCenter: parent.verticalCenter spacing: Style.marginS @@ -264,7 +264,7 @@ Item { // Bottom section (right widgets) ColumnLayout { - anchors.horizontalCenter: parent.horizontalCenter + x: Style.pixelAlignCenter(parent.width, width) anchors.bottom: parent.bottom anchors.bottomMargin: Style.marginM spacing: Style.marginS @@ -304,7 +304,7 @@ Item { objectName: "leftSection" anchors.left: parent.left anchors.leftMargin: Style.marginS - anchors.verticalCenter: parent.verticalCenter + y: Style.pixelAlignCenter(parent.height, height) spacing: Style.marginS Repeater { @@ -332,7 +332,7 @@ Item { id: centerSection objectName: "centerSection" anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter + y: Style.pixelAlignCenter(parent.height, height) spacing: Style.marginS Repeater { @@ -361,7 +361,7 @@ Item { objectName: "rightSection" anchors.right: parent.right anchors.rightMargin: Style.marginS - anchors.verticalCenter: parent.verticalCenter + y: Style.pixelAlignCenter(parent.height, height) spacing: Style.marginS Repeater { diff --git a/Modules/Bar/Widgets/Workspace.qml b/Modules/Bar/Widgets/Workspace.qml index 21b9b483b..25dfb6fe3 100644 --- a/Modules/Bar/Widgets/Workspace.qml +++ b/Modules/Bar/Widgets/Workspace.qml @@ -58,7 +58,7 @@ Item { readonly property real groupedBorderOpacity: (widgetSettings.groupedBorderOpacity !== undefined) ? widgetSettings.groupedBorderOpacity : widgetMetadata.groupedBorderOpacity readonly property bool enableScrollWheel: (widgetSettings.enableScrollWheel !== undefined) ? widgetSettings.enableScrollWheel : widgetMetadata.enableScrollWheel - readonly property int itemSize: Math.round(Style.capsuleHeight * 0.8) + readonly property int itemSize: Style.toOdd(Style.capsuleHeight * 0.8) // Context menu state for grouped mode - store IDs instead of object references to avoid stale references property string selectedWindowId: "" @@ -123,13 +123,13 @@ Item { const textWidth = displayText.length * (d * 0.4); // Approximate width per character const padding = d * 0.6; - return Math.round(Math.max(d * factor, textWidth + padding)); + return Style.toOdd(Math.max(d * factor, textWidth + padding)); } function getWorkspaceHeight(ws) { const d = Math.round(Style.capsuleHeight * root.baseDimensionRatio); const factor = ws.isActive ? 2.2 : 1; - return Math.round(d * factor); + return Style.toOdd(d * factor); } function computeWidth() { @@ -140,7 +140,7 @@ Item { } total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills; total += horizontalPadding * 2; - return Math.round(total); + return Style.toOdd(total); } function computeHeight() { @@ -151,7 +151,7 @@ Item { } total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills; total += horizontalPadding * 2; - return Math.round(total); + return Style.toOdd(total); } function getFocusedLocalIndex() { @@ -410,8 +410,8 @@ Item { border.color: Style.capsuleBorderColor border.width: Style.capsuleBorderWidth - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter + x: isVertical ? Style.pixelAlignCenter(parent.width, width) : 0 + y: isVertical ? 0 : Style.pixelAlignCenter(parent.height, height) MouseArea { anchors.fill: parent @@ -473,8 +473,8 @@ Item { Row { id: pillRow spacing: spacingBetweenPills - anchors.verticalCenter: workspaceBackground.verticalCenter x: horizontalPadding + y: workspaceBackground.y + Style.pixelAlignCenter(workspaceBackground.height, height) visible: !isVertical && !showApplications Repeater { @@ -483,7 +483,7 @@ Item { Item { id: workspacePillContainer width: root.getWorkspaceWidth(model) - height: Style.capsuleHeight * root.baseDimensionRatio + height: Style.toOdd(Style.capsuleHeight * root.baseDimensionRatio) Rectangle { id: pill @@ -493,8 +493,8 @@ Item { active: (labelMode !== "none") && (!root.showLabelsOnlyWhenOccupied || model.isOccupied || model.isFocused) sourceComponent: Component { NText { - x: (pill.width - width) / 2 - y: (pill.height - height) / 2 + (height - contentHeight) / 2 + x: Style.pixelAlignCenter(pill.width, width) + y: Style.pixelAlignCenter(pill.height, height) text: { if (model.name && model.name.length > 0) { if (root.labelMode === "name") { @@ -621,7 +621,7 @@ Item { Column { id: pillColumn spacing: spacingBetweenPills - anchors.horizontalCenter: workspaceBackground.horizontalCenter + x: workspaceBackground.x + Style.pixelAlignCenter(workspaceBackground.width, width) y: horizontalPadding visible: isVertical && !showApplications @@ -630,7 +630,7 @@ Item { model: localWorkspaces Item { id: workspacePillContainerVertical - width: Style.capsuleHeight * root.baseDimensionRatio + width: Style.toOdd(Style.capsuleHeight * root.baseDimensionRatio) height: root.getWorkspaceHeight(model) Rectangle { @@ -641,8 +641,8 @@ Item { active: (labelMode !== "none") && (!root.showLabelsOnlyWhenOccupied || model.isOccupied || model.isFocused) sourceComponent: Component { NText { - x: (pillVertical.width - width) / 2 - y: (pillVertical.height - height) / 2 + (height - contentHeight) / 2 + x: Style.pixelAlignCenter(pillVertical.width, width) + y: Style.pixelAlignCenter(pillVertical.height, height) text: { if (model.name && model.name.length > 0) { if (root.labelMode === "name") { @@ -779,8 +779,8 @@ Item { property var workspaceModel: model property bool hasWindows: (workspaceModel?.windows?.count ?? 0) > 0 - width: (hasWindows ? groupedIconsFlow.implicitWidth : root.itemSize) + (root.isVertical ? Style.marginXS : Style.marginXL) - height: (hasWindows ? groupedIconsFlow.implicitHeight : root.itemSize) + (root.isVertical ? Style.marginL : Style.marginXS) + width: Style.toOdd((hasWindows ? groupedIconsFlow.implicitWidth : root.itemSize) + (root.isVertical ? Style.marginXS : Style.marginXL)) + height: Style.toOdd((hasWindows ? groupedIconsFlow.implicitHeight : root.itemSize) + (root.isVertical ? Style.marginL : Style.marginXS)) color: Style.capsuleColor radius: Style.radiusS border.color: Settings.data.bar.showOutline ? Style.capsuleBorderColor : Qt.alpha((workspaceModel.isFocused ? Color.mPrimary : Color.mOutline), root.groupedBorderOpacity) @@ -812,7 +812,8 @@ Item { Flow { id: groupedIconsFlow - anchors.centerIn: parent + x: Style.pixelAlignCenter(parent.width, width) + y: Style.pixelAlignCenter(parent.height, height) spacing: 4 flow: root.isVertical ? Flow.TopToBottom : Flow.LeftToRight @@ -1019,15 +1020,11 @@ Item { id: groupedGrid visible: showApplications - anchors.verticalCenter: isVertical ? undefined : parent.verticalCenter - anchors.left: isVertical ? undefined : parent.left - anchors.leftMargin: isVertical ? 0 : Style.marginM - anchors.horizontalCenter: isVertical ? parent.horizontalCenter : undefined - anchors.top: isVertical ? parent.top : undefined - anchors.topMargin: isVertical ? Style.marginM : 0 + x: root.isVertical ? Style.pixelAlignCenter(parent.width, width) : Style.marginM + y: root.isVertical ? Style.marginM : Style.pixelAlignCenter(parent.height, height) spacing: Style.marginS - flow: isVertical ? Flow.TopToBottom : Flow.LeftToRight + flow: root.isVertical ? Flow.TopToBottom : Flow.LeftToRight Repeater { model: showApplications ? localWorkspaces : null diff --git a/Modules/MainScreen/MainScreen.qml b/Modules/MainScreen/MainScreen.qml index 3b1f79195..19916bf25 100644 --- a/Modules/MainScreen/MainScreen.qml +++ b/Modules/MainScreen/MainScreen.qml @@ -294,25 +294,24 @@ PanelWindow { readonly property string barPosition: Settings.data.bar.position || "top" readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" readonly property bool barFloating: Settings.data.bar.floating || false - readonly property real barMarginH: barFloating ? Math.ceil(Settings.data.bar.marginHorizontal * Style.marginXL) : 0 - readonly property real barMarginV: barFloating ? Math.ceil(Settings.data.bar.marginVertical * Style.marginXL) : 0 - readonly property real attachmentOverlap: 1 // Attachment overlap to fix hairline gap with fractional scaling + readonly property real barMarginH: barFloating ? Math.floor(Settings.data.bar.marginHorizontal * Style.marginXL) : 0 + readonly property real barMarginV: barFloating ? Math.floor(Settings.data.bar.marginVertical * Style.marginXL) : 0 // Expose bar dimensions directly on this Item for BarBackground // Use screen dimensions directly x: { if (barPosition === "right") - return screen.width - Style.barHeight - barMarginH - attachmentOverlap; // Extend left towards panels + return screen.width - Style.barHeight - barMarginH; return barMarginH; } y: { if (barPosition === "bottom") - return screen.height - Style.barHeight - barMarginV - attachmentOverlap; + return screen.height - Style.barHeight - barMarginV; return barMarginV; } width: { if (barIsVertical) { - return Style.barHeight + attachmentOverlap; + return Style.barHeight; } return screen.width - barMarginH * 2; } @@ -320,7 +319,7 @@ PanelWindow { if (barIsVertical) { return screen.height - barMarginV * 2; } - return Style.barHeight + attachmentOverlap; + return Style.barHeight; } // Corner states (same as Bar.qml) diff --git a/Modules/MainScreen/SmartPanel.qml b/Modules/MainScreen/SmartPanel.qml index 0d7ff228a..e84cdd146 100644 --- a/Modules/MainScreen/SmartPanel.qml +++ b/Modules/MainScreen/SmartPanel.qml @@ -93,6 +93,7 @@ Item { readonly property bool barFloating: Settings.data.bar.floating readonly property real barMarginH: barFloating ? Math.ceil(Settings.data.bar.marginHorizontal * Style.marginXL) : 0 readonly property real barMarginV: barFloating ? Math.ceil(Settings.data.bar.marginVertical * Style.marginXL) : 0 + readonly property real attachmentOverlap: 1 // Panel extends 1px into bar area to fix hairline gap with fractional scaling // Check if bar should be visible on this screen readonly property bool barShouldShow: { @@ -306,12 +307,13 @@ Item { // For vertical bars if (panelContent.allowAttach) { // Attached panels: align with bar edge (left or right side) + // Subtract attachmentOverlap to extend panel 1px into bar area if (root.barPosition === "left") { - var leftBarEdge = root.barMarginH + Style.barHeight; + var leftBarEdge = root.barMarginH + Style.barHeight - root.attachmentOverlap; calculatedX = leftBarEdge; } else { // right - var rightBarEdge = root.width - root.barMarginH - Style.barHeight; + var rightBarEdge = root.width - root.barMarginH - Style.barHeight + root.attachmentOverlap; calculatedX = rightBarEdge - panelWidth; } } else { @@ -365,7 +367,8 @@ Item { if (root.effectivePanelAnchorRight) { // Attached: snap to edge/bar if (root.barIsVertical && root.barPosition === "right") { - var rightBarEdge = root.width - root.barMarginH - Style.barHeight; + // Add attachmentOverlap to extend panel 1px into bar area + var rightBarEdge = root.width - root.barMarginH - Style.barHeight + root.attachmentOverlap; calculatedX = rightBarEdge - panelWidth; } else { var panelOnSameEdgeAsBar = (root.barPosition === "top" && root.effectivePanelAnchorTop) || (root.barPosition === "bottom" && root.effectivePanelAnchorBottom); @@ -385,7 +388,8 @@ Item { if (root.effectivePanelAnchorLeft) { // Attached: snap to edge/bar if (root.barIsVertical && root.barPosition === "left") { - var leftBarEdge = root.barMarginH + Style.barHeight; + // Subtract attachmentOverlap to extend panel 1px into bar area + var leftBarEdge = root.barMarginH + Style.barHeight - root.attachmentOverlap; calculatedX = leftBarEdge; } else { var panelOnSameEdgeAsBar = (root.barPosition === "top" && root.effectivePanelAnchorTop) || (root.barPosition === "bottom" && root.effectivePanelAnchorBottom); @@ -404,12 +408,12 @@ Item { // No explicit anchor: attach to bar if allowAttach, otherwise center if (root.barIsVertical) { if (panelContent.allowAttach) { - // Attach to the bar edge + // Attach to the bar edge (with overlap into bar area) if (root.barPosition === "left") { - var leftBarEdge = root.barMarginH + Style.barHeight; + var leftBarEdge = root.barMarginH + Style.barHeight - root.attachmentOverlap; calculatedX = leftBarEdge; } else { - var rightBarEdge = root.width - root.barMarginH - Style.barHeight; + var rightBarEdge = root.width - root.barMarginH - Style.barHeight + root.attachmentOverlap; calculatedX = rightBarEdge - panelWidth; } } else { @@ -466,14 +470,16 @@ Item { if (root.barPosition === "top") { var topBarEdge = root.barMarginV + Style.barHeight; if (panelContent.allowAttach) { - calculatedY = topBarEdge; + // Subtract attachmentOverlap to extend panel 1px into bar area + calculatedY = topBarEdge - root.attachmentOverlap; } else { calculatedY = topBarEdge + Style.marginM; } } else if (root.barPosition === "bottom") { var bottomBarEdge = root.height - root.barMarginV - Style.barHeight; if (panelContent.allowAttach) { - calculatedY = bottomBarEdge - panelHeight; + // Add attachmentOverlap to extend panel 1px into bar area + calculatedY = bottomBarEdge - panelHeight + root.attachmentOverlap; } else { calculatedY = bottomBarEdge - panelHeight - Style.marginM; } @@ -501,14 +507,18 @@ Item { } } else { if (root.effectivePanelAnchorTop && root.barPosition === "top") { - calculatedY = root.barMarginV + Style.barHeight; + // Subtract attachmentOverlap to extend panel 1px into bar area + calculatedY = root.barMarginV + Style.barHeight - root.attachmentOverlap; } else if (root.effectivePanelAnchorBottom && root.barPosition === "bottom") { - calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight; + // Add attachmentOverlap to extend panel 1px into bar area + calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight + root.attachmentOverlap; } else if (!root.hasExplicitVerticalAnchor) { if (root.barPosition === "top") { - calculatedY = root.barMarginV + Style.barHeight; + // Subtract attachmentOverlap to extend panel 1px into bar area + calculatedY = root.barMarginV + Style.barHeight - root.attachmentOverlap; } else if (root.barPosition === "bottom") { - calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight; + // Add attachmentOverlap to extend panel 1px into bar area + calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight + root.attachmentOverlap; } } } @@ -532,8 +542,8 @@ Item { } else if (root.panelAnchorTop) { // Use raw panelAnchorTop for positioning decision if (root.effectivePanelAnchorTop) { - // Attached: snap to edge/bar - calculatedY = root.barPosition === "top" ? root.barMarginV + Style.barHeight : 0; + // Attached: snap to edge/bar (with overlap into bar area) + calculatedY = root.barPosition === "top" ? root.barMarginV + Style.barHeight - root.attachmentOverlap : 0; } else { // Not attached: position at top with margin var topBarOffset = (root.barPosition === "top") ? barOffset : 0; @@ -542,8 +552,8 @@ Item { } else if (root.panelAnchorBottom) { // Use raw panelAnchorBottom for positioning decision if (root.effectivePanelAnchorBottom) { - // Attached: snap to edge/bar - calculatedY = root.barPosition === "bottom" ? root.height - root.barMarginV - Style.barHeight - panelHeight : root.height - panelHeight; + // Attached: snap to edge/bar (with overlap into bar area) + calculatedY = root.barPosition === "bottom" ? root.height - root.barMarginV - Style.barHeight - panelHeight + root.attachmentOverlap : root.height - panelHeight; } else { // Not attached: position at bottom with margin var bottomBarOffset = (root.barPosition === "bottom") ? barOffset : 0; @@ -563,9 +573,11 @@ Item { } else { if (panelContent.allowAttach && !root.barIsVertical) { if (root.barPosition === "top") { - calculatedY = root.barMarginV + Style.barHeight; + // Subtract attachmentOverlap to extend panel 1px into bar area + calculatedY = root.barMarginV + Style.barHeight - root.attachmentOverlap; } else if (root.barPosition === "bottom") { - calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight; + // Add attachmentOverlap to extend panel 1px into bar area + calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight + root.attachmentOverlap; } } else { if (root.barPosition === "top") {