aboutsummaryrefslogtreecommitdiff
path: root/quickshell/AppLauncher.qml
diff options
context:
space:
mode:
Diffstat (limited to 'quickshell/AppLauncher.qml')
-rw-r--r--quickshell/AppLauncher.qml248
1 files changed, 248 insertions, 0 deletions
diff --git a/quickshell/AppLauncher.qml b/quickshell/AppLauncher.qml
new file mode 100644
index 0000000..a1b5ab2
--- /dev/null
+++ b/quickshell/AppLauncher.qml
@@ -0,0 +1,248 @@
+import QtQuick
+import QtQuick.Controls
+import Quickshell
+import Quickshell.Io
+
+Rectangle {
+ id: appLauncher
+
+ width: 400
+ height: 600
+ color: shellRoot.black
+ radius: 16
+ border.width: 3
+ border.color: shellRoot.blue
+ antialiasing: true
+
+ property bool isVisible: false
+ property int selectedIndex: -1
+ property int hoverIndex: -1
+
+ signal requestClose()
+
+ focus: true
+
+ Keys.onEscapePressed: requestClose()
+
+ Keys.onUpPressed: {
+ hoverIndex = -1
+ if (selectedIndex === -1) {
+ selectedIndex = appListModel.count - 1
+ } else if (selectedIndex > 0) {
+ selectedIndex--
+ }
+ listView.positionViewAtIndex(selectedIndex, ListView.Contain)
+ }
+
+ Keys.onDownPressed: {
+ hoverIndex = -1
+ if (selectedIndex === -1) {
+ selectedIndex = 0
+ } else if (selectedIndex < appListModel.count - 1) {
+ selectedIndex++
+ }
+ listView.positionViewAtIndex(selectedIndex, ListView.Contain)
+ }
+
+ Keys.onReturnPressed: {
+ if (selectedIndex >= 0 && selectedIndex < appListModel.count) {
+ launchApp(appListModel.get(selectedIndex).appCommand)
+ }
+ }
+
+ Keys.onEnterPressed: {
+ if (selectedIndex >= 0 && selectedIndex < appListModel.count) {
+ launchApp(appListModel.get(selectedIndex).appCommand)
+ }
+ }
+
+ // Reset selection when widget becomes visible
+ onIsVisibleChanged: {
+ if (isVisible) {
+ selectedIndex = -1 // Start with nothing selected
+ hoverIndex = -1 // Reset hover on open
+ appLauncher.forceActiveFocus()
+ // Reload apps every time the launcher opens
+ loadApps()
+ }
+ }
+
+ // Process to load apps
+ Process {
+ id: appLoader
+ running: false // Don't auto-start, we'll trigger it manually
+ command: ["sh", "-c", "~/.config/quickshell/scripts/list-apps.sh"]
+
+ stdout: SplitParser {
+ onRead: data => {
+ const lines = data.split('\n')
+ for (const line of lines) {
+ if (line.trim().length === 0) continue
+
+ const parts = line.split('|')
+ if (parts.length >= 4) {
+ appListModel.append({
+ appName: parts[0],
+ appDescription: parts[1],
+ appIcon: parts[2],
+ appCommand: parts[3]
+ })
+ }
+ }
+ console.log("Loaded", appListModel.count, "applications")
+ }
+ }
+
+ onRunningChanged: {
+ // When process finishes, reset it for next run
+ if (!running) {
+ appLoader.running = false
+ }
+ }
+ }
+
+ // Function to load/reload applications
+ function loadApps() {
+ // Clear existing apps
+ appListModel.clear()
+ // Start the process to reload
+ appLoader.running = true
+ }
+
+ // Load apps initially when component is created
+ Component.onCompleted: {
+ loadApps()
+ }
+
+ Column {
+ anchors.fill: parent
+ anchors.margins: 12
+ spacing: 8
+
+ // Header
+ Rectangle {
+ width: parent.width
+ height: 36
+ color: "transparent"
+
+ Text {
+ anchors.centerIn: parent
+ text: "Applications"
+ font.family: shellRoot.fontFamily
+ font.pixelSize: 14
+ font.weight: Font.Medium
+ color: shellRoot.white
+ }
+
+ // Close button
+ Rectangle {
+ width: 28
+ height: 28
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ radius: 6
+ color: closeMouseArea.containsMouse ? shellRoot.red : "transparent"
+
+ Text {
+ anchors.centerIn: parent
+ text: "✕"
+ font.family: shellRoot.fontFamily
+ font.pixelSize: 16
+ font.weight: Font.Bold
+ color: closeMouseArea.containsMouse ? shellRoot.red : shellRoot.orange
+ }
+
+ MouseArea {
+ id: closeMouseArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: appLauncher.requestClose()
+ }
+ }
+ }
+
+ // Apps list
+ ListView {
+ id: listView
+ width: parent.width
+ height: parent.height - 44
+ spacing: 8
+ clip: true
+
+ model: ListModel {
+ id: appListModel
+ // Apps will be loaded dynamically from the script
+ }
+
+ delegate: Rectangle {
+ width: listView.width
+ height: 30
+ color: {
+ if (appLauncher.hoverIndex === index) return shellRoot.lightgray
+ if (appLauncher.hoverIndex === -1 && index === shellRoot.selectedIndex) return shellRoot.lightgray
+ return shellRoot.gray
+ }
+ radius: 12
+ border.width: 2
+ border.color: {
+ if (appLauncher.hoverIndex === index || (shellRoot.hoverIndex === -1 && index === shellRoot.selectedIndex)) return shellRoot.blue
+ return "transparent"
+ }
+
+ Row {
+ anchors.fill: parent
+ anchors.leftMargin: 12
+ anchors.rightMargin: 12
+ anchors.topMargin: 8
+ anchors.bottomMargin: 8
+ spacing: 12
+
+ Text {
+ width: parent.width - 62
+ anchors.centerIn: parent
+ text: model.appName
+ font.family: shellRoot.fontFamily
+ font.pixelSize: 14
+ font.weight: Font.Medium
+ color: {
+ if (appLauncher.hoverIndex === index) return shellRoot.blue
+ if (appLauncher.hoverIndex === -1 && index === shellRoot.selectedIndex) return shellRoot.blue
+ return shellRoot.purple
+ }
+ elide: Text.ElideRight
+ }
+ }
+
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+
+ onEntered: {
+ appLauncher.hoverIndex = index
+ }
+ onExited: {
+ appLauncher.hoverIndex = -1
+ }
+
+ onClicked: {
+ launchApp(model.appCommand)
+ }
+ }
+ }
+ }
+ }
+
+ function launchApp(command) {
+ console.log("Launching app:", command)
+ // Close the launcher first
+ appLauncher.requestClose()
+
+ // Use a small delay before launching to ensure the window closes properly
+ Qt.callLater(() => {
+ Quickshell.execDetached(["sh", "-c", command + " &"])
+ })
+ }
+}