init: working version

This commit is contained in:
Kiana Sheibani 2025-10-07 19:43:46 -04:00
commit 7d8d7dacae
Signed by: toki
GPG key ID: 6CB106C25E86A9F7
109 changed files with 15066 additions and 0 deletions

View file

@ -0,0 +1,59 @@
import qs.config
import QtQuick
import QtQuick.Shapes
Shape {
id: root
required property Item wrapper
readonly property real rounding: Config.border.rounding
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
ShapePath {
startX: (root.parent.width - root.wrapper.width) / 2 - rounding
startY: root.parent.height + 0.5
strokeWidth: -1
fillColor: Config.colors.bg
PathArc {
relativeX: root.rounding
relativeY: -root.roundingY
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
direction: PathArc.Counterclockwise
}
PathLine {
relativeX: 0
relativeY: -(root.wrapper.height - root.roundingY * 2)
}
PathArc {
relativeX: root.rounding
relativeY: -root.roundingY
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
}
PathLine {
relativeX: root.wrapper.width - root.rounding * 2
relativeY: 0
}
PathArc {
relativeX: root.rounding
relativeY: root.roundingY
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.roundingY * 2
}
PathArc {
relativeX: root.rounding
relativeY: root.roundingY
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
direction: PathArc.Counterclockwise
}
}
}

View file

@ -0,0 +1,167 @@
pragma ComponentBehavior: Bound
import "services"
import qs.services
import qs.config
import qs.custom
import Quickshell
import QtQuick
Item {
id: root
required property PersistentProperties uiState
required property var wrapper
required property var panels
readonly property color color: list.color
readonly property int padding: 15
readonly property int rounding: 25
implicitWidth: listWrapper.width + padding * 2
implicitHeight: searchWrapper.height + listWrapper.height + padding * 2
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
Item {
id: listWrapper
implicitWidth: list.width
implicitHeight: list.height + root.padding
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: searchWrapper.top
anchors.bottomMargin: root.padding
ContentList {
id: list
uiState: root.uiState
wrapper: root.wrapper
panels: root.panels
search: search
padding: root.padding
rounding: root.rounding
}
}
CustomRect {
id: searchWrapper
color: Config.colors.container
radius: 1000
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: root.padding
implicitHeight: Math.max(searchIcon.implicitHeight, search.implicitHeight, clearIcon.implicitHeight)
MaterialIcon {
id: searchIcon
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: root.padding
text: "search"
color: Config.colors.tertiary
}
CustomTextField {
id: search
anchors.left: searchIcon.right
anchors.right: clearBtn.left
anchors.leftMargin: 7
anchors.rightMargin: 7
topPadding: 12
bottomPadding: 12
placeholderText: qsTr("Type \"%1\" for commands").arg(Config.launcher.actionPrefix)
onAccepted: {
const currentItem = list.list?.currentItem;
if (currentItem) {
if (text.startsWith(Config.launcher.actionPrefix)) {
currentItem.modelData.onClicked(root.uiState);
} else {
Apps.launch(currentItem.modelData);
root.uiState.launcher = false;
}
}
}
Keys.onUpPressed: list.list?.decrementCurrentIndex()
Keys.onDownPressed: list.list?.incrementCurrentIndex()
Keys.onPressed: event => {
if (event.modifiers & Qt.ControlModifier) {
if (event.key === Qt.Key_J) {
list.list?.incrementCurrentIndex();
event.accepted = true;
} else if (event.key === Qt.Key_K) {
list.list?.decrementCurrentIndex();
event.accepted = true;
}
} else if (event.key === Qt.Key_Tab) {
list.list?.incrementCurrentIndex();
event.accepted = true;
} else if (event.key === Qt.Key_Backtab || (event.key === Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))) {
list.list?.decrementCurrentIndex();
event.accepted = true;
}
}
Connections {
target: root.uiState
function onLauncherChanged(): void {
if (root.uiState.launcher)
search.focus = true;
else {
search.text = "";
list.list.currentIndex = 0;
}
}
}
}
StateLayer {
id: clearBtn
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: root.padding
implicitWidth: 24
implicitHeight: 24
disabled: search.text === ""
onClicked: search.text = ""
opacity: disabled ? 0 : 1
MaterialIcon {
id: clearIcon
anchors.centerIn: parent
width: search.text ? implicitWidth : implicitWidth / 2
text: "close"
color: Config.colors.tertiary
Behavior on width {
Anim { duration: Config.anim.durations.small }
}
}
Behavior on opacity {
Anim { duration: Config.anim.durations.small }
}
}
}
}

View file

@ -0,0 +1,293 @@
pragma ComponentBehavior: Bound
import "items"
import "services"
import qs.config
import qs.custom
import qs.services
import qs.util
import Quickshell
import QtQuick
import QtQuick.Controls
Item {
id: root
required property PersistentProperties uiState
required property var wrapper
required property var panels
required property TextField search
required property int padding
required property int rounding
readonly property Item list: list
readonly property color color: list.color
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
implicitWidth: Config.launcher.itemWidth
implicitHeight: list.implicitHeight > 0 ? list.implicitHeight : empty.implicitHeight
clip: true
CustomListView {
id: list
model: ScriptModel {
id: model
onValuesChanged: list.currentIndex = 0
}
anchors.left: parent.left
anchors.right: parent.right
spacing: 7
orientation: Qt.Vertical
implicitHeight: (Config.launcher.itemHeight + spacing) * Math.min(Config.launcher.maxItemCount, count) - spacing
highlightMoveDuration: Config.anim.durations.normal
highlightResizeDuration: 0
keyNavigationWraps: true
property color color
highlight: CustomRect {
radius: 1000
color: list.color
opacity: 0.1
}
state: {
const text = root.search.text;
const prefix = Config.launcher.actionPrefix;
if (text.startsWith(prefix)) {
return "actions";
}
return "apps";
}
states: [
State {
name: "apps"
PropertyChanges {
model.values: Apps.search(root.search.text)
list.delegate: appItem
list.color: Config.colors.launcherApps
}
},
State {
name: "actions"
PropertyChanges {
model.values: Actions.query(root.search.text)
list.delegate: actionItem
list.color: Config.colors.launcherActions
}
}
]
// Disable animations before transition starts
onStateChanged: {
// NOTE: uiState check is necessary because this handler runs on startup
if (root.uiState.launcher) {
list.add.enabled = false;
list.remove.enabled = false;
}
}
transitions: Transition {
SequentialAnimation {
ParallelAnimation {
Anim {
target: list
property: "opacity"
from: 1
to: 0
duration: Config.anim.durations.small
easing.bezierCurve: Config.anim.curves.standardAccel
}
Anim {
target: list
property: "scale"
from: 1
to: 0.9
duration: Config.anim.durations.small
easing.bezierCurve: Config.anim.curves.standardAccel
}
}
PropertyAction {
targets: [model, list]
properties: "values,delegate,color"
}
ParallelAnimation {
Anim {
target: list
property: "opacity"
from: 0
to: 1
duration: Config.anim.durations.small
easing.bezierCurve: Config.anim.curves.standardDecel
}
Anim {
target: list
property: "scale"
from: 0.9
to: 1
duration: Config.anim.durations.small
easing.bezierCurve: Config.anim.curves.standardDecel
}
}
PropertyAction {
targets: [list.add, list.remove]
property: "enabled"
value: true
}
}
}
CustomScrollBar.vertical: CustomScrollBar {
flickable: list
}
add: Transition {
Anim {
property: "opacity"
from: 0
to: 1
}
Anim {
property: "scale"
from: 0.8
to: 1
}
}
remove: Transition {
Anim {
property: "opacity"
from: 1
to: 0
}
Anim {
property: "scale"
from: 1
to: 0.8
}
}
move: Transition {
Anim {
property: "y"
}
Anim {
properties: "opacity,scale"
to: 1
}
}
addDisplaced: Transition {
Anim {
property: "y"
duration: Config.anim.durations.small
}
Anim {
properties: "opacity,scale"
to: 1
}
}
displaced: Transition {
Anim {
property: "y"
}
Anim {
properties: "opacity,scale"
to: 1
}
}
Component {
id: appItem
AppItem {
uiState: root.uiState
}
}
Component {
id: actionItem
ActionItem {
uiState: root.uiState
list: list
}
}
}
Row {
id: empty
opacity: root.list?.count === 0 ? 1 : 0
scale: root.list?.count === 0 ? 1 : 0.5
spacing: 12
padding: 15
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
MaterialIcon {
text: "manage_search"
color: Config.colors.tertiary
font.pointSize: Config.font.size.largest
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
CustomText {
text: qsTr("No results")
color: Config.colors.tertiary
font.pointSize: Config.font.size.larger
font.weight: 500
}
CustomText {
text: qsTr("Try searching for something else")
color: Config.colors.tertiary
font.pointSize: Config.font.size.normal
}
}
Behavior on opacity {
Anim {}
}
Behavior on scale {
Anim {}
}
}
Behavior on implicitWidth {
enabled: root.uiState.launcher
Anim {
duration: Config.anim.durations.large
easing.bezierCurve: Config.anim.curves.emphasizedDecel
}
}
Behavior on implicitHeight {
enabled: root.uiState.launcher
Anim {
duration: Config.anim.durations.large
easing.bezierCurve: Config.anim.curves.emphasizedDecel
}
}
}

View file

@ -0,0 +1,75 @@
import qs.config
import qs.custom
import Quickshell
import Quickshell.Hyprland
import QtQuick
Item {
id: root
required property PersistentProperties uiState
required property Item panels
visible: height > 0
implicitHeight: 0
implicitWidth: content.implicitWidth
states: State {
name: "visible"
when: root.uiState.launcher
PropertyChanges {
root.implicitHeight: content.implicitHeight
}
}
transitions: [
Transition {
from: ""
to: "visible"
Anim {
target: root
property: "implicitHeight"
duration: Config.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Config.anim.curves.expressiveDefaultSpatial
}
},
Transition {
from: "visible"
to: ""
Anim {
target: root
property: "implicitHeight"
duration: Config.anim.durations.normal
easing.bezierCurve: Config.anim.curves.emphasized
}
}
]
HyprlandFocusGrab {
active: root.uiState.launcher
windows: [QsWindow.window]
onCleared: root.uiState.launcher = false
}
Background {
id: background
visible: false
wrapper: root
}
GlowEffect {
source: background
glowColor: content.color
}
Content {
id: content
uiState: root.uiState
wrapper: root
panels: root.panels
}
}

View file

@ -0,0 +1,80 @@
import "../services"
import qs.config
import qs.custom
import qs.services
import qs.util
import QtQuick
import Quickshell
Item {
id: root
required property Actions.Action modelData
required property PersistentProperties uiState
required property var list
implicitHeight: Config.launcher.itemHeight
anchors.left: parent?.left
anchors.right: parent?.right
StateLayer {
radius: 1000
anchors.fill: parent
function onClicked(): void {
root.uiState.launcher = false;
root.modelData?.onClicked(root.uiState);
}
}
Item {
anchors.fill: parent
anchors.leftMargin: 12
anchors.rightMargin: 12
anchors.margins: 7
MaterialIcon {
id: icon
text: root.modelData?.icon ?? ""
font.pointSize: Config.font.size.largest
anchors.verticalCenter: parent.verticalCenter
}
Item {
anchors.left: icon.right
anchors.leftMargin: 12
anchors.verticalCenter: icon.verticalCenter
implicitWidth: parent.width - icon.width
implicitHeight: name.implicitHeight + desc.implicitHeight
CustomText {
id: name
text: root.modelData?.name ?? ""
font.pointSize: Config.font.size.normal
color: root.ListView.isCurrentItem ? Color.mute(Config.colors.launcherActions, 1.1) : Config.colors.primary
Behavior on color {
CAnim {}
}
}
CustomText {
id: desc
text: root.modelData?.desc ?? ""
font.pointSize: Config.font.size.small
color: Config.colors.tertiary
elide: Text.ElideRight
width: root.width - icon.width - 34
anchors.top: name.bottom
}
}
}
}

View file

@ -0,0 +1,80 @@
import "../services"
import qs.config
import qs.custom
import qs.services
import qs.util
import Quickshell
import Quickshell.Widgets
import QtQuick
Item {
id: root
required property DesktopEntry modelData
required property PersistentProperties uiState
implicitHeight: Config.launcher.itemHeight
anchors.left: parent?.left
anchors.right: parent?.right
StateLayer {
radius: 1000
anchors.fill: parent
function onClicked(): void {
Apps.launch(root.modelData);
root.uiState.launcher = false;
}
}
Item {
anchors.fill: parent
anchors.leftMargin: 12
anchors.rightMargin: 12
anchors.margins: 7
IconImage {
id: icon
source: Quickshell.iconPath(root.modelData?.icon, "image-missing")
implicitSize: parent.height * 0.9
anchors.verticalCenter: parent.verticalCenter
}
Item {
anchors.left: icon.right
anchors.leftMargin: 12
anchors.verticalCenter: icon.verticalCenter
implicitWidth: parent.width - icon.width
implicitHeight: name.implicitHeight + comment.implicitHeight
CustomText {
id: name
text: root.modelData?.name ?? ""
font.pointSize: Config.font.size.normal
color: root.ListView.isCurrentItem ? Color.mute(Config.colors.launcherApps) : Config.colors.primary
Behavior on color {
CAnim {}
}
}
CustomText {
id: comment
text: (root.modelData?.comment || root.modelData?.genericName || root.modelData?.name) ?? ""
font.pointSize: Config.font.size.small
color: Config.colors.tertiary
elide: Text.ElideRight
width: root.width - icon.width - 34
anchors.top: name.bottom
}
}
}
}

View file

@ -0,0 +1,86 @@
pragma Singleton
import ".."
import qs.config
import qs.services
import qs.util
import Quickshell
import QtQuick
Searcher {
id: root
readonly property list<Action> actions: [
Action {
name: qsTr("Shutdown")
desc: qsTr("Shutdown the system")
icon: "power_settings_new"
function onClicked(uiState: PersistentProperties): void {
Quickshell.execDetached(Config.session.shutdown);
}
},
Action {
name: qsTr("Reboot")
desc: qsTr("Reboot the system")
icon: "cached"
function onClicked(uiState: PersistentProperties): void {
Quickshell.execDetached(Config.session.reboot);
}
},
Action {
name: qsTr("Logout")
desc: qsTr("Log out of the session and go back to the startup screen")
icon: "logout"
function onClicked(uiState: PersistentProperties): void {
Quickshell.execDetached(Config.session.logout);
}
},
Action {
name: qsTr("Lock")
desc: qsTr("Activate the lock screen (hyprlock)")
icon: "lock"
function onClicked(uiState: PersistentProperties): void {
Quickshell.execDetached(Config.session.lock);
}
},
Action {
name: qsTr("Suspend")
desc: qsTr("Suspend the system")
icon: "bedtime"
function onClicked(uiState: PersistentProperties): void {
Quickshell.execDetached(Config.session.suspend);
}
},
Action {
name: qsTr("Hibernate")
desc: qsTr("Suspend, then hibernate the system")
icon: "downloading"
function onClicked(uiState: PersistentProperties): void {
Quickshell.execDetached(Config.session.hibernate);
}
}
]
function transformSearch(search: string): string {
return search.slice(Config.launcher.actionPrefix.length);
}
list: actions.filter(a => !a.disabled)
useFuzzy: true
component Action: QtObject {
required property string name
required property string desc
required property string icon
property bool disabled
function onClicked(uiState: PersistentProperties): void {
}
}
}

View file

@ -0,0 +1,86 @@
pragma Singleton
import qs.config
import qs.util
import Quickshell
import QtQuick
Searcher {
id: root
function launch(entry: DesktopEntry): void {
if (entry.runInTerminal)
Quickshell.execDetached({
command: [...Config.terminalCommand, ...entry.command],
workingDirectory: entry.workingDirectory
});
else
Quickshell.execDetached({
command: entry.command,
workingDirectory: entry.workingDirectory
});
}
function search(search: string): list<var> {
const prefix = Config.launcher.specialPrefix;
if (search.startsWith(`${prefix}i `)) {
keys = ["id", "name"];
weights = [0.9, 0.1];
} else if (search.startsWith(`${prefix}c `)) {
keys = ["categories", "name"];
weights = [0.9, 0.1];
} else if (search.startsWith(`${prefix}d `)) {
keys = ["desc", "name"];
weights = [0.9, 0.1];
} else if (search.startsWith(`${prefix}e `)) {
keys = ["execString", "name"];
weights = [0.9, 0.1];
} else if (search.startsWith(`${prefix}w `)) {
keys = ["wmClass", "name"];
weights = [0.9, 0.1];
} else if (search.startsWith(`${prefix}g `)) {
keys = ["genericName", "name"];
weights = [0.9, 0.1];
} else if (search.startsWith(`${prefix}k `)) {
keys = ["keywords", "name"];
weights = [0.9, 0.1];
} else {
keys = ["name"];
weights = [1];
if (!search.startsWith(`${prefix}t `))
return query(search).map(e => e.modelData);
}
const results = query(search.slice(prefix.length + 2)).map(e => e.modelData);
if (search.startsWith(`${prefix}t `))
return results.filter(a => a.runInTerminal);
return results;
}
function selector(item: var): string {
return keys.map(k => item[k]).join(" ");
}
list: variants.instances
useFuzzy: true
Variants {
id: variants
model: [...DesktopEntries.applications.values].sort((a, b) => a.name.localeCompare(b.name))
QtObject {
required property DesktopEntry modelData
readonly property string id: modelData.id
readonly property string name: modelData.name
readonly property string desc: modelData.comment
readonly property string execString: modelData.execString
readonly property string wmClass: modelData.startupClass
readonly property string genericName: modelData.genericName
readonly property string categories: modelData.categories.join(" ")
readonly property string keywords: modelData.keywords.join(" ")
}
}
}