diff --git a/settings/quickshell.nix b/settings/quickshell.nix index 2289be4..6c09fb8 100644 --- a/settings/quickshell.nix +++ b/settings/quickshell.nix @@ -625,6 +625,11 @@ in readonly property int radiusTiny: Math.round(radius * 0.5) // hover rows, action chips readonly property int cardPad: 8 // card inner padding (inset = 2×) readonly property int panelGap: 8 // gap between panel border and cards (all dropdowns/menus/toast) + // How near a frame band a surface has to land before it fuses + // with it. Read by BarDropdown (where the panel is placed) and + // by the SDF (where it is melted) — they must agree, or the + // content sits a few px off the surface it is drawn inside. + readonly property int snapDist: panelGap + 4 // ── Animation duration tokens (ms) ── readonly property int animMorph: 280 // panel grow / slide (OutExpo) @@ -2266,10 +2271,9 @@ in readonly property real meltTop: Theme.frameWidth - meltInto readonly property real meltBot: bar.height - Theme.frameWidth + meltInto // Trigger distance is about proximity, not burial depth. - readonly property real snapDist: Theme.panelGap + 4 - readonly property real panelTop: rawTop <= Theme.frameWidth + snapDist + readonly property real panelTop: rawTop <= Theme.frameWidth + Theme.snapDist ? meltTop : rawTop - readonly property real panelBottom: rawBottom >= bar.height - Theme.frameWidth - snapDist + readonly property real panelBottom: rawBottom >= bar.height - Theme.frameWidth - Theme.snapDist ? meltBot : rawBottom // Left edge lands at (barWidth − meltInto): the surface // buries a full corner radius inside the bar column. @@ -2285,9 +2289,9 @@ in : Qt.vector4d(0, 0, 0, 0) // Toast melts into the band it sits against — toastItem // picks the side, this just buries the matching edge. - readonly property real toastTop: toastItem.atBottom + readonly property real toastTop: toastItem.atBottom || toastItem.floating ? toastItem.y + 8 : meltTop - readonly property real toastBot: toastItem.atBottom + readonly property real toastBot: toastItem.atBottom && !toastItem.floating ? meltBot : toastItem.y + 8 + _toastRect.height property vector4d toast: toastItem.visible && _toastRect.width > 0.5 ? Qt.vector4d(surfLeftX + _toastRect.width / 2, (toastTop + toastBot) / 2, @@ -4980,19 +4984,20 @@ in // is taller than the space (yMax < yMin). readonly property real yMin: Theme.frameWidth readonly property real yMax: bar.height - Theme.frameWidth - height - // A panel whose opener sits high on the bar looks adrift - // when it merely centres on it — pin it to the frame, the - // way the bottom cluster's menus already land on yMax. - property bool alignTop: false - // Mirror for the bottom cluster: a tall panel opened from a - // low widget reads better fused to the bottom frame than - // floating a few px off it. max(yMin, ...) keeps a panel - // taller than the screen pinned to the top instead. - property bool alignBottom: false + // Where the panel lands when it just tracks its widget. + readonly property real yTracked: Math.max(yMin, Math.min(yMax, dropdownY - height / 2)) x: Theme.barWidth - y: alignTop ? Math.round(yMin) - : alignBottom ? Math.round(Math.max(yMin, yMax)) - : Math.round(Math.max(yMin, Math.min(yMax, dropdownY - height / 2))) + // Snap to whichever band is within reach, and only then. + // Proximity rather than an alignTop/alignBottom flag per + // dropdown: a panel that gains a card later starts fusing + // with the frame on its own, and one that loses a card + // stops — no flag to remember to flip. A panel taller than + // the screen has yMax < yMin, so yTracked pins to yMin and + // the first branch catches it. + y: Math.round( + yTracked - yMin <= Theme.snapDist ? yMin + : yMax - yTracked <= Theme.snapDist ? Math.max(yMin, yMax) + : yTracked) width: fullWidth + 4 // +24, not +16: the shader insets the panel 8px top and // bottom, so +16 left the panel edge flush with the content @@ -5359,7 +5364,6 @@ in // no rebuild and no restart. BarDropdown { id: wallDropdown - alignTop: true fullWidth: wallCard.width + 2 * Theme.panelGap fullHeight: wallCard.height + 2 * Theme.panelGap @@ -6011,7 +6015,6 @@ in id: batteryDropdown fullWidth: batteryDropdownCol.width + 2 * Theme.panelGap fullHeight: batteryDropdownCol.height + 2 * Theme.panelGap - alignBottom: true Column { id: batteryDropdownCol @@ -7607,9 +7610,34 @@ in // Flush to that band (it melts into it), never centred on // the clock — a short toast floating mid-column reads as a // stray card instead of a surface growing out of the frame. - y: Math.round(atBottom + readonly property real baseY: atBottom ? bar.height - Theme.frameWidth - height - 8 - : Theme.frameWidth + 8) + : Theme.frameWidth + 8 + // Dropdowns share this column, so a toast arriving under an + // open one lands on top of it. Step away from the band far + // enough to clear the panel, capped by the room left before + // the opposite frame — a full-height panel pushes the toast + // as far as it goes and no further, rather than off screen. + readonly property real dodge: { + const d = bar.activeDropdown; + if (!d || !d.visible) return 0; + const want = atBottom + ? (d.y + d.height + Theme.panelGap) - baseY + : (baseY + height + Theme.panelGap) - d.y; + const room = atBottom + ? baseY - Theme.frameWidth - 8 + : bar.height - Theme.frameWidth - 8 - height - baseY; + return Math.max(0, Math.min(want, room)); + } + // Once it has stepped away it is no longer against the + // band, so the shader draws it as a free-standing card. + readonly property bool floating: dodge > 0 + y: Math.round(atBottom ? baseY - dodge : baseY + dodge) + + // Opening a dropdown under a live toast moves it; slide. + Behavior on y { + NumberAnimation { duration: Theme.animContent; easing.type: Easing.OutCubic } + } width: _toastRect.width + 4 height: _toastRect.height + 16