quickshell: melt edge-adjacent panels into the frame, centre night icon

Panels near a frame band left a visible seam instead of merging. The
clamp now targets the frame's inner edge and the shader extends any
surface within snapDist 4px INTO the band, so the SDF fuses them into
one swollen shape — the launcher's launchBot trick, generalised to
dropdowns, the session menu and the toast.

Reverts the panelGap inset from the previous commit, which pushed in
the wrong direction.

qsIcon used anchors.verticalCenter and was centred only because the
cell was qsIcon.width; at barWidth it sat at x=0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-07-28 13:38:38 +01:00
parent 2cf3004fc4
commit 8c549e1422

View file

@ -968,8 +968,20 @@ in
// Panels grow rightward out of the bar column, so the // Panels grow rightward out of the bar column, so the
// cross-axis is now vertical: the 8px inset that used to // cross-axis is now vertical: the 8px inset that used to
// be left/right is top/bottom. // be left/right is top/bottom.
readonly property real panelTop: chrome.y + 8 readonly property real rawTop: chrome.y + 8
readonly property real panelBottom: chrome.y + chrome.height - 8 readonly property real rawBottom: chrome.y + chrome.height - 8
// A surface that comes within snapDist of a frame band is
// extended 4px INTO it, so the SDF fuses the two into one
// swollen shape rather than leaving a visible seam and a
// stranded rounded corner. The launcher has always done
// this via launchBot; this generalises it to every panel.
readonly property real meltTop: Theme.frameWidth - 4
readonly property real meltBot: bar.height - Theme.frameWidth + 4
readonly property real snapDist: Theme.panelGap + 4
readonly property real panelTop: rawTop <= Theme.frameWidth + snapDist
? meltTop : rawTop
readonly property real panelBottom: rawBottom >= bar.height - Theme.frameWidth - snapDist
? meltBot : rawBottom
// The panel/toast/session centre-x uses (barWidth 4): the // The panel/toast/session centre-x uses (barWidth 4): the
// surface overlaps 4px into the bar so the two melt. // surface overlaps 4px into the bar so the two melt.
readonly property real surfLeftX: Theme.barWidth - 4 readonly property real surfLeftX: Theme.barWidth - 4
@ -982,13 +994,17 @@ in
? Qt.vector4d(surfLeftX + chrome.width / 2, (panelTop + panelBottom) / 2, ? Qt.vector4d(surfLeftX + chrome.width / 2, (panelTop + panelBottom) / 2,
4 + chrome.width / 2, (panelBottom - panelTop) / 2) 4 + chrome.width / 2, (panelBottom - panelTop) / 2)
: Qt.vector4d(0, 0, 0, 0) : Qt.vector4d(0, 0, 0, 0)
// Toast sits under the top frame band and melts up into it.
readonly property real toastBot: toastItem.y + 8 + _toastRect.height
property vector4d toast: toastItem.visible && _toastRect.width > 0.5 property vector4d toast: toastItem.visible && _toastRect.width > 0.5
? Qt.vector4d(surfLeftX + _toastRect.width / 2, toastItem.y + 8 + _toastRect.height / 2, ? Qt.vector4d(surfLeftX + _toastRect.width / 2, (meltTop + toastBot) / 2,
4 + _toastRect.width / 2, _toastRect.height / 2) 4 + _toastRect.width / 2, (toastBot - meltTop) / 2)
: Qt.vector4d(0, 0, 0, 0) : Qt.vector4d(0, 0, 0, 0)
// Session menu sits in the bottom-left corner and melts
// down into the bottom frame band.
property vector4d session: sessionMenu.visible property vector4d session: sessionMenu.visible
? Qt.vector4d(surfLeftX + sessionMenu.width / 2, sessionMenu.y + sessionMenu.height / 2, ? Qt.vector4d(surfLeftX + sessionMenu.width / 2, (sessionMenu.y + meltBot) / 2,
4 + sessionMenu.width / 2, sessionMenu.height / 2) 4 + sessionMenu.width / 2, (meltBot - sessionMenu.y) / 2)
: Qt.vector4d(0, 0, 0, 0) : Qt.vector4d(0, 0, 0, 0)
// The launcher is the one surface that stays put: it still // The launcher is the one surface that stays put: it still
// grows from the bottom frame band at screen centre. // grows from the bottom frame band at screen centre.
@ -1051,9 +1067,11 @@ in
} }
// Unfolds rightward out of the bar at the bottom-left, // Unfolds rightward out of the bar at the bottom-left,
// level with the power button that opens it. // level with the power button that opens it. Bottom edge
// sits on the frame's inner edge so the shader can melt
// the two together (meltBot) instead of leaving a seam.
x: Theme.barWidth x: Theme.barWidth
y: Math.round(bar.height - Theme.frameWidth - height - 8) y: Math.round(bar.height - Theme.frameWidth - height)
width: openW width: openW
height: openH height: openH
visible: openW > 0.5 visible: openW > 0.5
@ -1889,9 +1907,11 @@ in
SIcon { SIcon {
id: qsIcon id: qsIcon
anchors.verticalCenter: parent.verticalCenter // centerIn, not verticalCenter: the cell used to be
// qsIcon.width so horizontal centring was implicit.
anchors.centerIn: parent
text: "nightlight" text: "nightlight"
font.pixelSize: 16 font.pixelSize: 18
} }
function openQsDropdown() { function openQsDropdown() {
@ -2070,12 +2090,12 @@ in
open = true; open = true;
} }
// Inset by panelGap so a panel that hits the clamp never // Clamp to the frame's inner edge. A panel that lands here
// sits flush on the frame band: flush fuses the two in the // is MEANT to fuse with the frame band the shader melts
// SDF and eats the panel's rounded corner. Outer max wins // it in (see meltTop/meltBot). Outer max wins when a panel
// when a panel is taller than the space (yMax < yMin). // is taller than the space (yMax < yMin).
readonly property real yMin: Theme.frameWidth + Theme.panelGap readonly property real yMin: Theme.frameWidth
readonly property real yMax: bar.height - Theme.frameWidth - Theme.panelGap - height readonly property real yMax: bar.height - Theme.frameWidth - height
x: Theme.barWidth x: Theme.barWidth
y: Math.round(Math.max(yMin, Math.min(yMax, dropdownY - height / 2))) y: Math.round(Math.max(yMin, Math.min(yMax, dropdownY - height / 2)))
width: fullWidth + 4 width: fullWidth + 4