Files
Gabriel Brown 23141673a2 Hang the Control Center where every other popover hangs
Five surfaces sat 12 pixels under the bar -- the date menu, the activity panel,
notification toasts, signal glass and the clipboard, all using barGap * 2. The
Control Center sat at 2, through a constant of its own, which left the widest
surface in the shell hanging ten pixels higher than the date menu beside it.

That constant arrived with the original Control Center and carried no reason,
while barGap directly above it explains itself. The clipboard even cites
QuickSettings in a comment for how it derived its own margin, and still landed
on 12. It reads as an early value nothing else converged on rather than a
decision, which is why it is going rather than being documented and kept.

The contract that guarded it pinned the literal, and that same file already
records where pinning a literal led: it once asserted the buggy margin
expression, so the code and the test agreed and a 38-pixel gap was invisible to
both. Replacing one number with another would have repeated it. It now reads the
top margin out of the Control Center and out of the date menu and requires them
to match, so drift in either direction fails -- verified by moving each one in
turn and watching it break.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-20 13:41:12 -04:00

126 lines
4.1 KiB
QML

// The quick settings popover — GNOME's system menu (Super+S).
//
// A layer-shell surface rather than a PopupWindow because it needs keyboard
// focus for the wifi password field, and because the bar it hangs from is
// owned by a different module.
import QtQuick
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import qs.config
import qs.services
import qs.widgets
PanelWindow {
id: root
visible: ShellState.quickSettingsOpen
color: "transparent"
// Hangs directly beneath the top-right status cluster. Two logical pixels
// preserve the Prism edge without making the surface feel detached.
//
// The margin is the gap ALONE, not the bar height plus the gap. An
// exclusiveZone of 0 means "reserve nothing, but respect what others
// reserved", so this surface already begins below the bar's 36px zone;
// adding the bar height here counted it twice and left the panel floating
// 38px under the bar instead of 2px. It also means the panel follows the
// bar on its own -- if the bar ever stops reserving space, this closes up
// against the top edge rather than hanging under nothing.
anchors.top: true
anchors.right: true
// The same gap every other popover uses. This had its own constant set to
// 2, which left the widest surface in the shell hanging ten pixels higher
// than the date menu beside it -- an early value nothing else converged on
// rather than a decision; it carried no reason, while barGap does.
margins.top: Theme.barGap * 2
margins.right: Theme.barSideMargin
exclusiveZone: 0
implicitWidth: Theme.controlCenterWidth
implicitHeight: panel.implicitHeight
readonly property string expandedSection: panel.expandedSection
function expand(name: string): void {
panel.expand(name);
}
// The `qs-popover` prefix is matched by a layerrule in hypr/rules.lua.
WlrLayershell.namespace: "qs-popover-quicksettings"
WlrLayershell.layer: WlrLayer.Overlay
// OnDemand rather than Exclusive: the panel must be able to take typing for
// the wifi password field, but must not steal it while merely visible.
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
onVisibleChanged: {
if (root.visible) {
KdeConnect.refresh();
HomeAssistant.refresh();
// The daemon owns the power profile and anything on the system can
// change it, so the panel asks rather than trusting what it last saw.
PowerProfiles.refresh();
openAnim.restart();
} else {
panel.reset();
}
}
// Closes the panel when a click lands anywhere else on the desktop.
HyprlandFocusGrab {
windows: [root]
active: root.visible
onCleared: ShellState.close()
}
Rectangle {
id: surface
anchors.fill: parent
radius: Theme.popoverRadius
color: Theme.alpha(Theme.bgPopover, Theme.popoverAlpha)
border.width: 1
border.color: Theme.alpha(Theme.fg, 0.08)
// The prism edge — see widgets/PrismEdge.qml. Sits just inside the 1px
// border so the two don't fight for the same row of pixels.
PrismEdge {
anchors.top: parent.top
anchors.topMargin: 1
anchors.left: parent.left
anchors.right: parent.right
inset: parent.radius
}
transform: Scale {
id: openScale
origin.x: surface.width
origin.y: 0
xScale: 1
yScale: 1
}
// Escape reaches here by propagating up from whatever holds focus
// (usually the password field), so it works in every sub-state.
Item {
anchors.fill: parent
focus: true
Keys.onEscapePressed: ShellState.close()
QuickSettingsPanel {
id: panel
anchors.fill: parent
}
}
}
NumberAnimation {
id: openAnim
target: openScale
property: "yScale"
from: 0.94
to: 1.0
duration: Theme.durNormal
easing.type: Easing.OutCubic
}
}