quickshell: session menu card backing, keyboard nav with default selection

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-06-12 14:23:33 +01:00
parent a14ccd8c09
commit 516b8b6d5b

View file

@ -475,6 +475,8 @@ in
required property var shellRoot
screen: modelData
WlrLayershell.namespace: "quickshell-bar"
// Keyboard only while the session menu is open (arrow/Enter nav)
WlrLayershell.keyboardFocus: sessionMenu.open ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
anchors {
top: true
@ -528,25 +530,54 @@ in
}
// Session menu: icon-only power controls morphing out of
// the right frame column at screen centre (Super+L).
// the right frame column at screen centre (Super+L). Keyboard:
// arrows/Tab move the selection, Enter activates, Esc closes.
Item {
id: sessionMenu
property bool open: false
property real openW: open ? 56 : 0
property int selIdx: 0
property real openW: open ? 64 : 0
Behavior on openW {
NumberAnimation { duration: 280; easing.type: Easing.OutExpo }
}
readonly property var actions: [
{ icon: "lock", danger: false, act: "lock" },
{ icon: "logout", danger: false, act: "logout" },
{ icon: "restart_alt", danger: true, act: "reboot" },
{ icon: "power_settings_new", danger: true, act: "poweroff" }
]
function activate(act) {
open = false;
if (act === "lock") Quickshell.execDetached([Commands.hyprlock]);
else if (act === "logout") Hyprland.dispatch("hl.dsp.exit()");
else if (act === "reboot") Quickshell.execDetached([Commands.systemctl, "reboot"]);
else Quickshell.execDetached([Commands.systemctl, "poweroff"]);
}
function toggle() {
open = !open;
if (open) {
selIdx = 0;
forceActiveFocus();
_sessionAutoClose.restart();
}
}
x: bar.width - Theme.frameWidth - openW
y: Math.round((bar.height - height) / 2)
width: openW
height: sessionCol.height + 24
height: sessionCard.height + 24
visible: openW > 0.5
function toggle() {
open = !open;
if (open) _sessionAutoClose.restart();
}
focus: open
Keys.onEscapePressed: open = false
Keys.onUpPressed: { selIdx = (selIdx + actions.length - 1) % actions.length; _sessionAutoClose.restart(); }
Keys.onDownPressed: { selIdx = (selIdx + 1) % actions.length; _sessionAutoClose.restart(); }
Keys.onTabPressed: { selIdx = (selIdx + 1) % actions.length; _sessionAutoClose.restart(); }
Keys.onReturnPressed: activate(actions[selIdx].act)
Keys.onEnterPressed: activate(actions[selIdx].act)
Timer {
id: _sessionAutoClose
@ -570,51 +601,51 @@ in
NumberAnimation { duration: 200; easing.type: Easing.OutCubic }
}
Column {
id: sessionCol
// Card backing, matching the other dropdowns
Rectangle {
id: sessionCard
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: 8
spacing: 4
width: 48
height: sessionCol.height + 8
radius: 8
color: Theme.base01
Repeater {
model: [
{ icon: "lock", danger: false, act: "lock" },
{ icon: "logout", danger: false, act: "logout" },
{ icon: "restart_alt", danger: true, act: "reboot" },
{ icon: "power_settings_new", danger: true, act: "poweroff" }
]
Column {
id: sessionCol
anchors.centerIn: parent
spacing: 4
Rectangle {
id: sessBtn
required property var modelData
width: 40
height: 40
radius: 8
color: sessBtnMa.containsMouse ? Theme.base02 : "transparent"
Behavior on color { ColorAnimation { duration: 120 } }
Repeater {
model: sessionMenu.actions
Text {
anchors.centerIn: parent
text: sessBtn.modelData.icon
color: sessBtn.modelData.danger && sessBtnMa.containsMouse
? Theme.base08 : Theme.base05
Rectangle {
id: sessBtn
required property var modelData
required property int index
width: 40
height: 40
radius: 8
color: sessionMenu.selIdx === index ? Theme.base02 : "transparent"
Behavior on color { ColorAnimation { duration: 120 } }
font.family: Theme.iconFont
font.pixelSize: 20
}
MouseArea {
id: sessBtnMa
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
sessionMenu.open = false;
if (sessBtn.modelData.act === "lock") Quickshell.execDetached([Commands.hyprlock]);
else if (sessBtn.modelData.act === "logout") Hyprland.dispatch("hl.dsp.exit()");
else if (sessBtn.modelData.act === "reboot") Quickshell.execDetached([Commands.systemctl, "reboot"]);
else Quickshell.execDetached([Commands.systemctl, "poweroff"]);
Text {
anchors.centerIn: parent
text: sessBtn.modelData.icon
color: sessBtn.modelData.danger && sessionMenu.selIdx === sessBtn.index
? Theme.base08 : Theme.base05
Behavior on color { ColorAnimation { duration: 120 } }
font.family: Theme.iconFont
font.pixelSize: 20
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: sessionMenu.selIdx = sessBtn.index
onClicked: sessionMenu.activate(sessBtn.modelData.act)
}
}
}