From a27dd6f93b394de060d0e74afb767d9a0fc3652c Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Sun, 4 Oct 2026 20:38:06 +0200 Subject: [PATCH] fix(quickshell): rail popouts no longer land short of the rail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Popouts anchored to their trigger item, and Quickshell maps an anchor rect given in item coordinates through the item's transform. Rail icons scale to 1.08 on hover, so a panel placed ~360px left of the icon landed up to ~29px further left still, leaving a strip of desktop between it and the rail — by however far the hover scale had got when the dwell timer fired, which is why it only happened sometimes. Anchor to the trigger window's content item instead, which never transforms, and place in window coordinates from the trigger's centre, which a scale about the centre leaves where it is. Co-Authored-By: Claude Opus 5.5 --- quickshell/components/Popout.qml | 34 +++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/quickshell/components/Popout.qml b/quickshell/components/Popout.qml index 2c5ed80..0ee0f4c 100644 --- a/quickshell/components/Popout.qml +++ b/quickshell/components/Popout.qml @@ -92,15 +92,25 @@ PopupWindow { // 0 = tucked behind the frame, 1 = fully out. property real reveal: 0 - // Placement, in the trigger's own coordinates. Worked out once per open + // Placement, in the trigger window's coordinates. Worked out once per open // in place(): item positions are not bindable, and a popout should not // drift while it is up anyway. property real placeX: 0 property real placeY: 0 + // The popout anchors to the trigger's window, not to the trigger. Rail + // icons scale up on hover, and an anchor rect given in an item's own + // coordinates is mapped through that item's transform: a panel placed + // 360px left of a 1.08-scaled icon landed 29px further left still, off + // the rail — by however far the hover scale had got when the popout + // opened. The window's content item never transforms. + readonly property Item anchorFrame: anchorItem?.Window.contentItem ?? null + function place() { if (!anchorItem) return; - const p = anchorItem.mapToItem(null, 0, 0); + // The trigger's centre. A transform about the centre leaves it where + // it is, so this is the same whatever the hover scale is doing. + const c = anchorItem.mapToItem(null, anchorItem.width / 2, anchorItem.height / 2); const winW = anchorItem.Window.width; const winH = anchorItem.Window.height; const f = root.fillet, R = Theme.frameRadius; @@ -110,40 +120,40 @@ PopupWindow { // edge, so its x = 0 is that edge and its y = 0 is the bar's // underside. const h = root.contentHeight + f * 2; - const y = p.y + anchorItem.height / 2 - h / 2; + const y = c.y - h / 2; const lo = R, hi = winH - Theme.frame - R - h; if (y < lo + root.dockReach) { root.dock = "topRight"; - root.placeY = -root.seam - p.y; + root.placeY = -root.seam; } else if (y > hi - root.dockReach) { root.dock = "bottomRight"; - root.placeY = winH - Theme.frame + root.seam - root.windowHeight - p.y; + root.placeY = winH - Theme.frame + root.seam - root.windowHeight; } else { root.dock = "right"; - root.placeY = y - p.y; + root.placeY = y; } - root.placeX = root.seam - root.windowWidth - p.x; + root.placeX = root.seam - root.windowWidth; } else { // The bar's window is the full screen width, so the opening runs // from `frame` to `width - railWidth`. const w = root.contentWidth + f * 2; - const x = p.x + anchorItem.width / 2 - w / 2; + const x = c.x - w / 2; const lo = Theme.frame + R - f; const hi = winW - Theme.railWidth - R + f - w; if (x > hi - root.dockReach) { root.dock = "topRight"; - root.placeX = winW - Theme.railWidth + root.seam - root.windowWidth - p.x; + root.placeX = winW - Theme.railWidth + root.seam - root.windowWidth; } else { root.dock = "top"; - root.placeX = Math.max(lo, x) - p.x; + root.placeX = Math.max(lo, x); } - root.placeY = winH - root.seam - p.y; + root.placeY = winH - root.seam; } } - anchor.item: anchorItem + anchor.item: anchorFrame anchor.rect.x: root.placeX anchor.rect.y: root.placeY anchor.rect.w: 1