feat: session menu

This commit is contained in:
2 * r + 2 * t 2025-05-11 23:55:05 +10:00
parent a2c3417675
commit 60858f6f02
11 changed files with 399 additions and 34 deletions

BIN
assets/kurukuru.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

12
config/SessionConfig.qml Normal file
View file

@ -0,0 +1,12 @@
pragma Singleton
import Quickshell
import QtQuick
Singleton {
readonly property Sizes sizes: Sizes {}
component Sizes: QtObject {
readonly property int button: 80
}
}

View file

@ -1,7 +1,6 @@
import "root:/widgets"
import "root:/services"
import "root:/config"
import Quickshell
import QtQuick
Column {

View file

@ -80,49 +80,60 @@ Variants {
Component.onCompleted: root.winHeight = height
Background {
id: bg
visible: false
Item {
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: Drawers.rightExclusion
wrapperWidth: Math.min(wrapper.width, content.width)
wrapperHeight: wrapper.height
}
clip: true
visible: width > 0
implicitWidth: wrapper.width
implicitHeight: wrapper.height
LayerShadow {
source: bg
}
Background {
id: bg
Wrapper {
id: wrapper
visible: false
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
implicitHeight: content.height + bg.rounding * 2
osdVisible: root.osdVisible
contentWidth: content.width
Content {
id: content
monitor: root.monitor
wrapperWidth: Math.min(wrapper.width, content.width)
wrapperHeight: wrapper.height
}
}
HoverHandler {
id: hoverHandler
LayerShadow {
source: bg
}
onHoveredChanged: {
root.hovered = hovered;
if (hovered)
timer.stop();
else
root.osdVisible = false;
Wrapper {
id: wrapper
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
implicitHeight: content.height + bg.rounding * 2
osdVisible: root.osdVisible
contentWidth: content.width
Content {
id: content
monitor: root.monitor
}
}
HoverHandler {
id: hoverHandler
onHoveredChanged: {
root.hovered = hovered;
if (hovered)
timer.stop();
else
root.osdVisible = false;
}
}
}
}

View file

@ -0,0 +1,70 @@
import "root:/services"
import "root:/config"
import QtQuick
import QtQuick.Shapes
Shape {
id: root
required property real wrapperWidth
required property real wrapperHeight
readonly property real rounding: BorderConfig.rounding
readonly property bool flatten: wrapperWidth < rounding * 2
readonly property real roundingX: flatten ? wrapperWidth / 2 : rounding
preferredRendererType: Shape.CurveRenderer
opacity: Colours.transparency.enabled ? Colours.transparency.base : 1
ShapePath {
strokeWidth: -1
fillColor: BorderConfig.colour
startX: root.wrapperWidth - 1
PathArc {
relativeX: -root.roundingX
relativeY: root.rounding
radiusX: Math.min(root.rounding, root.wrapperWidth)
radiusY: root.rounding
}
PathLine {
x: root.roundingX
relativeY: 0
}
PathArc {
relativeX: -root.roundingX
relativeY: root.rounding
radiusX: Math.min(root.rounding, root.wrapperWidth)
radiusY: root.rounding
direction: PathArc.Counterclockwise
}
PathLine {
y: root.wrapperHeight - root.rounding * 2
}
PathArc {
relativeX: root.roundingX
relativeY: root.rounding
radiusX: Math.min(root.rounding, root.wrapperWidth)
radiusY: root.rounding
direction: PathArc.Counterclockwise
}
PathLine {
x: (root.flatten ? root.roundingX : root.wrapperWidth - root.rounding) - 1
relativeY: 0
}
PathArc {
relativeX: root.roundingX
relativeY: root.rounding
radiusX: Math.min(root.rounding, root.wrapperWidth)
radiusY: root.rounding
}
Behavior on fillColor {
ColorAnimation {
duration: Appearance.anim.durations.normal
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.anim.curves.standard
}
}
}
}

118
modules/session/Content.qml Normal file
View file

@ -0,0 +1,118 @@
pragma ComponentBehavior: Bound
import "root:/widgets"
import "root:/services"
import "root:/config"
import Quickshell
import Quickshell.Io
import QtQuick
Column {
id: root
required property Scope session
padding: Appearance.padding.large
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
spacing: Appearance.spacing.large
SessionButton {
id: logout
icon: "logout"
command: ["uwsm", "stop"]
KeyNavigation.down: shutdown
Connections {
target: session
function onSessionVisibleChanged(): void {
if (session.sessionVisible)
logout.focus = true;
}
}
}
SessionButton {
id: shutdown
icon: "power_settings_new"
command: ["systemctl", "poweroff"]
KeyNavigation.up: logout
KeyNavigation.down: hibernate
}
AnimatedImage {
width: SessionConfig.sizes.button
height: SessionConfig.sizes.button
sourceSize.width: width
sourceSize.height: height
playing: session.sessionVisible
asynchronous: true
speed: 0.7
source: "root:/assets/kurukuru.gif"
}
SessionButton {
id: hibernate
icon: "downloading"
command: ["systemctl", "hibernate"]
KeyNavigation.up: shutdown
KeyNavigation.down: reboot
}
SessionButton {
id: reboot
icon: "cached"
command: ["systemctl", "reboot"]
KeyNavigation.up: hibernate
}
component SessionButton: StyledRect {
id: button
required property string icon
required property list<string> command
implicitWidth: SessionConfig.sizes.button
implicitHeight: SessionConfig.sizes.button
radius: Appearance.rounding.large
color: button.activeFocus ? Colours.palette.m3secondaryContainer : Colours.palette.m3surfaceContainer
Keys.onEnterPressed: proc.startDetached()
Keys.onEscapePressed: root.session.sessionVisible = false
Process {
id: proc
command: button.command
}
StateLayer {
radius: parent.radius
function onClicked(): void {
proc.startDetached();
}
}
MaterialIcon {
anchors.centerIn: parent
text: button.icon
color: button.activeFocus ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
font.pointSize: Appearance.font.size.extraLarge
}
}
}

View file

@ -0,0 +1,87 @@
import "root:/widgets"
import "root:/services"
import "root:/config"
import Quickshell
import Quickshell.Wayland
import QtQuick
Scope {
id: root
property int winHeight
property bool sessionVisible
Connections {
target: Drawers
function onPosChanged(screen: ShellScreen, x: int, y: int): void {
if (x > screen.width - BorderConfig.thickness && y > (screen.height - root.winHeight) / 2 && y < (screen.height + root.winHeight) / 2)
root.sessionVisible = true;
}
}
LazyLoader {
loading: true
StyledWindow {
id: win
name: "osd"
keyboardFocus: root.sessionVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
visible: wrapper.shouldBeVisible
mask: Region {
item: wrapper
}
anchors.left: true
anchors.right: true
height: wrapper.height
Component.onCompleted: {
root.winHeight = height;
Drawers.rightExclusion = Qt.binding(() => bg.width);
}
Background {
id: bg
visible: false
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
wrapperWidth: Math.min(wrapper.width, content.width)
wrapperHeight: wrapper.height
}
LayerShadow {
source: bg
}
Wrapper {
id: wrapper
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
implicitHeight: content.height + bg.rounding * 2
sessionVisible: root.sessionVisible
contentWidth: content.width
Content {
id: content
session: root
}
}
}
}
CustomShortcut {
name: "session"
description: "Toggle session menu"
onPressed: root.sessionVisible = !root.sessionVisible
}
}

View file

@ -0,0 +1,62 @@
import "root:/config"
import QtQuick
Item {
id: root
required property bool sessionVisible
required property real contentWidth
property bool shouldBeVisible
visible: width > 0
width: 0
states: State {
name: "visible"
when: root.sessionVisible
PropertyChanges {
root.width: contentWidth
root.shouldBeVisible: true
}
}
transitions: [
Transition {
from: ""
to: "visible"
SequentialAnimation {
PropertyAction {
target: root
property: "shouldBeVisible"
}
NumberAnimation {
target: root
property: "width"
duration: Appearance.anim.durations.large
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.anim.curves.emphasizedDecel
}
}
},
Transition {
from: "visible"
to: ""
SequentialAnimation {
NumberAnimation {
target: root
property: "width"
duration: Appearance.anim.durations.normal
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.anim.curves.emphasizedAccel
}
PropertyAction {
target: root
property: "shouldBeVisible"
}
}
}
]
}

View file

@ -6,6 +6,7 @@ Singleton {
id: root
property var positions: ({})
property int rightExclusion
signal posChanged(screen: ShellScreen, x: int, y: int)

View file

@ -2,6 +2,7 @@ import "modules/bar"
import "modules/launcher"
import "modules/osd"
import "modules/notifications"
import "modules/session"
import "modules/drawers"
import "modules/background"
import Quickshell
@ -13,4 +14,5 @@ ShellRoot {
Background {}
Drawers {}
Notifications {}
Session {}
}

View file

@ -6,6 +6,9 @@ import QtQuick
Rectangle {
id: root
readonly property alias hovered: mouse.hovered
readonly property alias pressed: mouse.pressed
function onClicked(event: MouseEvent): void {
}