feat: launcher actions

This commit is contained in:
2 * r + 2 * t 2025-05-03 14:32:53 +10:00
parent e3a27ec90b
commit 5978db658c
6 changed files with 277 additions and 13 deletions

View file

@ -5,9 +5,11 @@ import QtQuick
Singleton {
readonly property int maxShown: 8
readonly property string actionPrefix: ">"
readonly property Sizes sizes: Sizes {}
component Sizes: QtObject {
property int width: 600
readonly property int width: 600
readonly property int itemHeight: 57
}
}

View file

@ -0,0 +1,59 @@
import "root:/widgets"
import "root:/config"
import Quickshell
import QtQuick
PaddedRect {
id: root
required property Actions.Action modelData
required property var list
implicitWidth: ListView.view.width
implicitHeight: LauncherConfig.sizes.itemHeight
padding: [Appearance.padding.smaller, Appearance.padding.larger]
StateLayer {
radius: Appearance.rounding.normal
function onClicked(): void {
root.modelData.onClicked(root.list);
}
}
MaterialIcon {
id: icon
text: root.modelData.icon
font.pointSize: Appearance.font.size.extraLarge
anchors.verticalCenter: parent.verticalCenter
}
Item {
anchors.left: icon.right
anchors.leftMargin: Appearance.spacing.larger
anchors.verticalCenter: icon.verticalCenter
implicitWidth: parent.width - icon.width
implicitHeight: childrenRect.height
StyledText {
id: name
text: root.modelData.name
font.pointSize: Appearance.font.size.normal
}
StyledText {
text: root.modelData.desc
font.pointSize: Appearance.font.size.small
color: Appearance.alpha(Appearance.colours.m3outline, true)
elide: Text.ElideRight
width: root.width - icon.width - Appearance.rounding.normal * 2
anchors.top: name.bottom
}
}
}

View file

@ -0,0 +1,127 @@
pragma Singleton
import "root:/utils/scripts/fuzzysort.js" as Fuzzy
import "root:/config"
import Quickshell
import Quickshell.Io
import QtQuick
Singleton {
id: root
readonly property list<Action> list: [
Action {
name: qsTr("Scheme")
desc: qsTr("Change the current colour scheme")
icon: "palette"
function onClicked(list: AppList): void {
root.autocomplete(list, "scheme");
}
},
Action {
name: qsTr("Wallpaper")
desc: qsTr("Change the current wallpaper")
icon: "image"
function onClicked(list: AppList): void {
root.autocomplete(list, "wallpaper");
}
},
Action {
name: qsTr("Variant")
desc: qsTr("Change the current scheme variant")
icon: "colors"
function onClicked(list: AppList): void {
root.autocomplete(list, "variant");
}
},
Action {
name: qsTr("Transparency")
desc: qsTr("Change shell transparency")
icon: "opacity"
function onClicked(list: AppList): void {
root.autocomplete(list, "transparency");
}
},
Action {
name: qsTr("Light")
desc: qsTr("Change the scheme to light mode")
icon: "light_mode"
function onClicked(list: AppList): void {
list.launcher.launcherVisible = false;
// TODO
}
},
Action {
name: qsTr("Dark")
desc: qsTr("Change the scheme to dark mode")
icon: "dark_mode"
function onClicked(list: AppList): void {
list.launcher.launcherVisible = false;
// TODO
}
},
Action {
name: qsTr("Lock")
desc: qsTr("Lock the current session")
icon: "lock"
function onClicked(list: AppList): void {
list.launcher.launcherVisible = false;
lock.running = true;
}
},
Action {
name: qsTr("Sleep")
desc: qsTr("Suspend then hibernate")
icon: "bedtime"
function onClicked(list: AppList): void {
list.launcher.launcherVisible = false;
sleep.running = true;
}
}
]
readonly property list<var> preppedNames: list.map(a => ({
name: Fuzzy.prepare(a.name),
action: a
}))
function fuzzyQuery(search: string): var {
return Fuzzy.go(search.slice(LauncherConfig.actionPrefix.length), preppedNames, {
all: true,
key: "name"
}).map(r => r.obj.action);
}
function autocomplete(list: AppList, text: string): void {
list.search.text = `${LauncherConfig.actionPrefix}${text} `;
}
Process {
id: lock
command: ["loginctl", "lock-session"]
}
Process {
id: sleep
command: ["systemctl", "suspend-then-hibernate"]
}
component Action: QtObject {
required property string name
required property string desc
required property string icon
function onClicked(list: AppList): void {
}
}
}

View file

@ -1,5 +1,4 @@
import "root:/widgets"
import "root:/services"
import "root:/config"
import Quickshell
import Quickshell.Widgets
@ -12,6 +11,7 @@ PaddedRect {
required property Scope launcher
implicitWidth: ListView.view.width
implicitHeight: LauncherConfig.sizes.itemHeight
padding: [Appearance.padding.smaller, Appearance.padding.normal]
StateLayer {

View file

@ -1,3 +1,5 @@
pragma ComponentBehavior: Bound
import "root:/widgets"
import "root:/services"
import "root:/config"
@ -9,18 +11,29 @@ ListView {
id: root
required property int padding
required property string search
required property TextField search
required property Scope launcher
property bool isAction: search.text.startsWith(LauncherConfig.actionPrefix)
function getModelValues() {
let text = search.text;
if (isAction)
return Actions.fuzzyQuery(text);
if (text.startsWith(LauncherConfig.actionPrefix))
text = search.text.slice(LauncherConfig.actionPrefix.length);
return Apps.fuzzyQuery(text);
}
model: ScriptModel {
values: Apps.fuzzyQuery(root.search)
values: root.getModelValues()
onValuesChanged: root.currentIndex = 0
}
clip: true
spacing: Appearance.spacing.small
orientation: Qt.Vertical
implicitHeight: ((currentItem?.height ?? 1) + spacing) * Math.min(LauncherConfig.maxShown, count) - spacing
implicitHeight: (LauncherConfig.sizes.itemHeight + spacing) * Math.min(LauncherConfig.maxShown, count) - spacing
anchors.left: parent.left
anchors.right: parent.right
@ -34,9 +47,7 @@ ListView {
color: Appearance.alpha(Appearance.colours.m3surfaceContainerHighest, true)
}
delegate: AppItem {
launcher: root.launcher
}
delegate: isAction ? actionItem : appItem
ScrollBar.vertical: StyledScrollBar {
// Move half out
@ -84,8 +95,69 @@ ListView {
}
}
Component {
id: appItem
AppItem {
launcher: root.launcher
}
}
Component {
id: actionItem
ActionItem {
list: root
}
}
Behavior on implicitHeight {
Anim {}
Anim {
duration: Appearance.anim.durations.large
easing.bezierCurve: Appearance.anim.curves.emphasizedDecel
}
}
Behavior on isAction {
SequentialAnimation {
ParallelAnimation {
Anim {
target: root
property: "opacity"
from: 1
to: 0
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.standardAccel
}
Anim {
target: root
property: "scale"
from: 1
to: 0.9
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.standardAccel
}
}
PropertyAction {}
ParallelAnimation {
Anim {
target: root
property: "opacity"
from: 0
to: 1
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.standardDecel
}
Anim {
target: root
property: "scale"
from: 0.9
to: 1
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.standardDecel
}
}
}
}
component Anim: NumberAnimation {

View file

@ -36,7 +36,7 @@ Item {
id: list
padding: root.padding
search: search.text
search: search
launcher: root.launcher
}
@ -60,7 +60,7 @@ Item {
leftPadding: root.padding
rightPadding: root.padding
placeholderText: qsTr("Type \">\" for commands")
placeholderText: qsTr(`Type "${LauncherConfig.actionPrefix}" for commands`)
background: StyledRect {
color: Appearance.alpha(Appearance.colours.m3surfaceContainerHigh, true)
@ -69,8 +69,12 @@ Item {
onAccepted: {
if (list.currentItem) {
Apps.launch(list.currentItem?.modelData);
root.launcher.launcherVisible = false;
if (list.isAction)
list.currentItem.modelData.onClicked(list);
else {
Apps.launch(list.currentItem?.modelData);
root.launcher.launcherVisible = false;
}
}
}