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
126 lines
4.1 KiB
QML
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
|
|
}
|
|
}
|