updated design

This commit is contained in:
2026-08-11 16:34:09 +02:00
parent 3e02c3f36a
commit 39f925cb58
17 changed files with 859 additions and 19 deletions
+7 -1
View File
@@ -80,7 +80,13 @@ contentPad * 2`), which keeps the shape maths out of binding loops.
| CPU/MEM/GPU | bar | btop | | | | resource dashboard |
Popouts open on a short dwell and stay while the pointer is over the trigger or
the panel. Clicking pins one open.
the panel. Clicking pins one open. A shell-wide coordinator keeps only one
transient surface mapped at a time, including tray context menus, so sweeping
across the rail cannot stack several animated panels over one another.
The power menu is fully keyboard-driven as well as clickable: `Up`/`Down` or
`J`/`K` moves the active slab, `Enter`/`Space` confirms it, and `Escape`
dismisses the screen.
## Keybinds
+2 -1
View File
@@ -11,13 +11,14 @@ Item {
property color accent: Theme.primary
property color hoverFill: Theme.accent
property bool destructive: false
property bool selected: false
property real lean: Theme.skew
property real lunge: 6
property bool wide: false
signal clicked()
readonly property bool hovered: hover.hovered
readonly property bool hovered: hover.hovered || root.selected
readonly property bool pressed: tap.pressed
implicitWidth: wide ? 260 : (row.implicitWidth + panel.contentPad * 2)
+2
View File
@@ -103,6 +103,7 @@ PopupWindow {
}
function open() {
PopupCoordinator.claim(root);
exitAnim.stop();
shown = true;
enterAnim.restart();
@@ -183,6 +184,7 @@ PopupWindow {
root.pinned = false;
contentHolder.x = root.bleed;
contentHolder.y = root.bleed;
PopupCoordinator.release(root);
root.dismissedFully();
}
}
+27
View File
@@ -0,0 +1,27 @@
pragma Singleton
import QtQuick
import Quickshell
// One transient surface at a time. Without a shared owner, a quick sweep over
// the bar/rail can leave several independently animated windows mapped on top
// of each other. Every coordinated surface exposes dismiss().
Singleton {
property var active: null
function claim(surface: var) {
if (active && active !== surface)
active.dismiss();
active = surface;
}
function release(surface: var) {
if (active === surface)
active = null;
}
function dismissAll() {
if (active)
active.dismiss();
}
}
+11 -5
View File
@@ -20,6 +20,8 @@ Item {
property string status: ""
property bool failed: false
property bool busy: pam.active
readonly property real uiScale: Math.max(0.72, Math.min(1.15,
Math.min(width / 1920, height / 1080)))
Rectangle {
anchors.fill: parent
@@ -137,7 +139,9 @@ Item {
anchors.top: parent.top
anchors.topMargin: parent.height * 0.2
spacing: Theme.padM
width: 300
width: Math.min(300, parent.width * 0.28)
scale: surface.uiScale
transformOrigin: Item.TopRight
RailRow {
width: parent.width
@@ -181,8 +185,10 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: parent.height * 0.14
width: 460
width: Math.min(460, parent.width - Theme.padXL * 2)
height: 150
scale: surface.uiScale
transformOrigin: Item.Bottom
transform: Translate { id: shakeShift }
@@ -206,7 +212,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: prompt.bottom
anchors.topMargin: Theme.padL
width: 380
width: Math.min(380, authBlock.width - Theme.padL * 2)
height: 56
lean: 0.12
chop: 16
@@ -320,7 +326,7 @@ Item {
Text {
anchors.verticalCenter: parent.verticalCenter
text: Quickshell.env("USER") || "narl"
text: Quickshell.env("USER") || "USER"
color: Theme.muted
font.family: Theme.fontMono
font.pixelSize: Theme.fsSmall
@@ -349,7 +355,7 @@ Item {
id: pam
config: "hyprlock"
user: Quickshell.env("USER") || "narl"
user: Quickshell.env("USER") || "USER"
onPamMessage: {
if (pam.responseRequired) pam.respond(surface.entry);
+31 -9
View File
@@ -26,9 +26,21 @@ PanelWindow {
exclusionMode: ExclusionMode.Ignore
readonly property bool open: Actions.powerMenuOpen
property int selectedAction: 0
readonly property var actionModel: [
{ label: "Lock", icon: "󰌾", danger: false, act: "lock" },
{ label: "Suspend", icon: "󰤄", danger: false, act: "suspend" },
{ label: "Hibernate", icon: "󰒲", danger: false, act: "hibernate" },
{ label: "Log Out", icon: "󰗽", danger: true, act: "logout" },
{ label: "Reboot", icon: "󰜉", danger: true, act: "reboot" },
{ label: "Shut Down", icon: "󰐥", danger: true, act: "shutdown" }
]
onOpenChanged: {
if (open) {
PopupCoordinator.dismissAll();
selectedAction = 0;
openAnim.restart();
} else {
closeAnim.restart();
@@ -122,14 +134,7 @@ PanelWindow {
Repeater {
id: buttonRepeater
model: [
{ label: "Lock", icon: "󰌾", danger: false, act: "lock" },
{ label: "Suspend", icon: "󰤄", danger: false, act: "suspend" },
{ label: "Hibernate", icon: "󰒲", danger: false, act: "hibernate" },
{ label: "Log Out", icon: "󰗽", danger: true, act: "logout" },
{ label: "Reboot", icon: "󰜉", danger: true, act: "reboot" },
{ label: "Shut Down", icon: "󰐥", danger: true, act: "shutdown" }
]
model: root.actionModel
// A carrier item owns the staggered slide so the button keeps its
// own hover transform.
@@ -159,10 +164,12 @@ PanelWindow {
text: entry.modelData.label
icon: entry.modelData.icon
destructive: entry.modelData.danger
selected: root.selectedAction === entry.index
accent: entry.modelData.danger ? Theme.accent : Theme.primary
lean: Theme.skew
lunge: -10
onClicked: root.invoke(entry.modelData.act)
onHoveredChanged: if (hovered) root.selectedAction = entry.index
}
// Staggered entry, one button after another.
@@ -211,7 +218,7 @@ PanelWindow {
anchors.bottom: parent.bottom
anchors.bottomMargin: 40
anchors.horizontalCenter: parent.horizontalCenter
text: "ESC TO CANCEL"
text: "↑ ↓ SELECT ENTER CONFIRM ESC CANCEL"
color: Theme.muted
font.family: Theme.fontMono
font.pixelSize: Theme.fsMicro
@@ -237,6 +244,21 @@ PanelWindow {
anchors.fill: parent
focus: root.open
Keys.onEscapePressed: Actions.closePowerMenu()
Keys.onPressed: event => {
if (!root.open) return;
if (event.key === Qt.Key_Up || event.key === Qt.Key_K) {
root.selectedAction = (root.selectedAction + root.actionModel.length - 1)
% root.actionModel.length;
event.accepted = true;
} else if (event.key === Qt.Key_Down || event.key === Qt.Key_J) {
root.selectedAction = (root.selectedAction + 1) % root.actionModel.length;
event.accepted = true;
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter
|| event.key === Qt.Key_Space) {
root.invoke(root.actionModel[root.selectedAction].act);
event.accepted = true;
}
}
}
ParallelAnimation {
+7
View File
@@ -53,6 +53,7 @@ PopupWindow {
function open() {
if (!menuHandle) return;
PopupCoordinator.claim(root);
closeAnim.stop();
shown = true;
openAnim.restart();
@@ -68,6 +69,11 @@ PopupWindow {
if (shown) close(); else open();
}
// Shared name used by PopupCoordinator for every transient surface.
function dismiss() {
close();
}
// The grab can only take hold once the popup is actually mapped. Asked for
// in the same tick as `shown`, Hyprland has no surface to hand it and
// clears it immediately — which closed the menu the instant it opened.
@@ -145,6 +151,7 @@ PopupWindow {
onFinished: {
root.shown = false;
holder.x = root.bleed;
PopupCoordinator.release(root);
}
}
}
+1 -1
View File
@@ -188,7 +188,7 @@ Column {
anchors.verticalCenter: parent.verticalCenter
width: row.entry.hasChildren ? 18 : 0
visible: row.entry.hasChildren
text: row.expanded ? "" : ""
text: row.expanded ? "󰅀" : "󰅂"
color: row.hovered ? Theme.ink : Theme.subtext
font.family: Theme.fontIcon
font.pixelSize: Theme.fsMicro