diff --git a/config/dot/quickshell/modules/bar/ActivityPanel.qml b/config/dot/quickshell/modules/bar/ActivityPanel.qml index e84b4fc..eb71faf 100644 --- a/config/dot/quickshell/modules/bar/ActivityPanel.qml +++ b/config/dot/quickshell/modules/bar/ActivityPanel.qml @@ -15,7 +15,11 @@ PanelWindow { color: "transparent" anchors.top: true anchors.right: true - margins.top: Theme.barHeight + Theme.barGap * 2 + // The gap ALONE, not the bar height plus the gap. exclusiveZone 0 means + // "reserve nothing, but respect what others reserved", so this surface + // already begins below the bar's zone -- adding the bar height here counted + // it twice and left the surface floating 48px under the bar instead of 12. + margins.top: Theme.barGap * 2 margins.right: Theme.barSideMargin exclusiveZone: 0 implicitWidth: 350 diff --git a/config/dot/quickshell/modules/clipboard/ClipboardPanel.qml b/config/dot/quickshell/modules/clipboard/ClipboardPanel.qml index 298ae7d..bcf9008 100644 --- a/config/dot/quickshell/modules/clipboard/ClipboardPanel.qml +++ b/config/dot/quickshell/modules/clipboard/ClipboardPanel.qml @@ -20,8 +20,11 @@ PanelWindow { top: true right: true } + // The gap ALONE, not the bar height plus the gap -- see the note in + // QuickSettings.qml: exclusiveZone 0 already places this below the bar's + // reserved zone, so adding the bar height counted it twice. margins { - top: Theme.barHeight + Theme.barGap * 2 + top: Theme.barGap * 2 right: Theme.barSideMargin } diff --git a/config/dot/quickshell/modules/datemenu/DateMenu.qml b/config/dot/quickshell/modules/datemenu/DateMenu.qml index 8ee8030..84ad5a8 100644 --- a/config/dot/quickshell/modules/datemenu/DateMenu.qml +++ b/config/dot/quickshell/modules/datemenu/DateMenu.qml @@ -28,7 +28,11 @@ PanelWindow { color: "transparent" anchors.top: true - margins.top: Theme.barHeight + Theme.barGap * 2 + // The gap ALONE, not the bar height plus the gap. exclusiveZone 0 means + // "reserve nothing, but respect what others reserved", so this surface + // already begins below the bar's zone -- adding the bar height here counted + // it twice and left the surface floating 48px under the bar instead of 12. + margins.top: Theme.barGap * 2 exclusiveZone: 0 implicitWidth: 760 diff --git a/config/dot/quickshell/modules/notifications/Toasts.qml b/config/dot/quickshell/modules/notifications/Toasts.qml index f45ef57..8b93353 100644 --- a/config/dot/quickshell/modules/notifications/Toasts.qml +++ b/config/dot/quickshell/modules/notifications/Toasts.qml @@ -17,7 +17,11 @@ PanelWindow { anchors.top: true anchors.right: true - margins.top: Theme.barHeight + Theme.barGap * 2 + // The gap ALONE, not the bar height plus the gap. exclusiveZone 0 means + // "reserve nothing, but respect what others reserved", so this surface + // already begins below the bar's zone -- adding the bar height here counted + // it twice and left the surface floating 48px under the bar instead of 12. + margins.top: Theme.barGap * 2 margins.right: Theme.barSideMargin exclusiveZone: 0 diff --git a/config/dot/quickshell/modules/signals/SignalGlass.qml b/config/dot/quickshell/modules/signals/SignalGlass.qml index 25537b7..121fea9 100644 --- a/config/dot/quickshell/modules/signals/SignalGlass.qml +++ b/config/dot/quickshell/modules/signals/SignalGlass.qml @@ -22,7 +22,11 @@ PanelWindow { screen: root.modelData anchors.top: true - margins.top: Theme.barHeight + Theme.barGap * 2 + // The gap ALONE, not the bar height plus the gap. exclusiveZone 0 means + // "reserve nothing, but respect what others reserved", so this surface + // already begins below the bar's zone -- adding the bar height here counted + // it twice and left the surface floating 48px under the bar instead of 12. + margins.top: Theme.barGap * 2 exclusiveZone: 0 implicitWidth: root.showingEvent ? 388 : 438 implicitHeight: root.showingEvent ? 76 : 132 diff --git a/tests/quickshell/layer-margin-contract.sh b/tests/quickshell/layer-margin-contract.sh new file mode 100755 index 0000000..cacea3d --- /dev/null +++ b/tests/quickshell/layer-margin-contract.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash + +# A surface that respects the bar's reserved space must not also subtract it by +# hand. +# +# wlr-layer-shell has three behaviors and only the middle one is subtle: +# +# exclusiveZone > 0 reserve this much space; other surfaces are pushed out +# exclusiveZone = 0 reserve nothing, but RESPECT what others reserved +# exclusiveZone < 0 reserve nothing and ignore what others reserved +# +# Every popover here uses 0, so the compositor has already placed it below the +# bar before its own margin applies. Adding Theme.barHeight to that margin +# counts the bar twice. That is not a crash and not a warning -- the panel +# simply opens lower than intended, which is how the control center spent its +# life hanging 38px under a bar it was written to sit 2px below, and five other +# surfaces sat at 48px instead of 12px. +# +# The rule is mechanical, so a test can hold it: with exclusiveZone 0, a margin +# is the GAP, never the gap plus what someone else reserved. +# +# Static and read-only. + +set -uo pipefail + +repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +modules="$repo_dir/config/dot/quickshell/modules" + +fail() { + printf 'layer margin contract: %s\n' "$1" >&2 + exit 1 +} + +[[ -d "$modules" ]] || fail 'the modules directory is missing' + +# The premise: the bar is what reserves space at the top. If that ever changes, +# the rule below stops being the right rule and this should be revisited rather +# than kept passing. +bar="$modules/bar/Bar.qml" +[[ -r "$bar" ]] || fail 'Bar.qml is missing' +grep -q 'exclusiveZone: Theme.barHeight' "$bar" \ + || fail 'the bar no longer reserves its own height, so what popovers should offset by has changed' + +violations="" +checked=0 + +while read -r file; do + [[ -n "$file" ]] || continue + grep -q 'PanelWindow' "$file" || continue + + # Only surfaces that respect other zones. A surface with a negative zone is + # positioned from the screen edge and SHOULD carry the bar height itself. + zone="$(grep -oE 'exclusiveZone:[[:space:]]*-?[0-9]+' "$file" | head -1 | grep -oE '\-?[0-9]+$')" + [[ "$zone" == "0" ]] || continue + checked=$((checked + 1)) + + # Margins only. Theme.barHeight is legitimate elsewhere -- sizing a row to + # match the bar, for instance -- so this looks at the placement lines. + offenders="$(grep -nE '^[[:space:]]*(margins\.(top|bottom)|top|bottom):.*Theme\.barHeight' "$file" || true)" + if [[ -n "$offenders" ]]; then + while IFS= read -r line; do + violations+=" ${file#$repo_dir/}:${line}"$'\n' + done <<<"$offenders" + fi +done < <(find "$modules" -name '*.qml' | sort) + +(( checked > 0 )) || fail 'no surfaces with exclusiveZone 0 were found, so this checked nothing' + +if [[ -n "$violations" ]]; then + printf 'layer margin contract: these surfaces respect the bar'"'"'s reserved space AND add it to their margin, so they open one bar-height too low:\n%s' "$violations" >&2 + exit 1 +fi + +printf 'layer margin contract: PASS (%d surfaces respect the bar without double-counting it)\n' "$checked"