diff options
| author | Leander Scherer <leander@schererleander.de> | 2026-05-18 21:48:24 +0200 |
|---|---|---|
| committer | Leander Scherer <leander@schererleander.de> | 2026-05-28 22:42:07 +0200 |
| commit | 9a7cf1242d296dbdb9c03df48ab09054960295aa (patch) | |
| tree | f1a2d5c77ef6bdb049c995afcc4c663c1ffd1373 /modules/system/quickshell/Launcher.qml | |
| parent | 3ef8b4973bcae26445f99467d50ad75730d204b5 (diff) | |
feat(quickshell): basic bar, tray, notification
Diffstat (limited to 'modules/system/quickshell/Launcher.qml')
| -rw-r--r-- | modules/system/quickshell/Launcher.qml | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/modules/system/quickshell/Launcher.qml b/modules/system/quickshell/Launcher.qml new file mode 100644 index 0000000..fc0dc3d --- /dev/null +++ b/modules/system/quickshell/Launcher.qml @@ -0,0 +1,228 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland + +PanelWindow { + id: root + + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.namespace: "launcher" + WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + WlrLayershell.exclusiveZone: -1 + + exclusionMode: ExclusionMode.Ignore + + // Centering logic + anchors { + top: true + } + margins.top: 200 + + implicitWidth: 600 + implicitHeight: 400 + + color: "transparent" + visible: GlobalState.activePopup === "Launcher" + + onVisibleChanged: { + if (visible) { + searchInput.forceActiveFocus() + searchInput.text = "" + } + } + + Scope { + id: internal + + function filterApps(searchText) { + const search = searchText.toLowerCase() + const apps = DesktopEntries.applications.values + + return apps.filter(app => { + if (app.noDisplay) return false + if (!search) return true + + return app.name.toLowerCase().includes(search) || + (app.comment && app.comment.toLowerCase().includes(search)) + }) + } + } + + FocusScope { + anchors.fill: parent + focus: true + + Squircle { + id: launcherContent + anchors.fill: parent + cornerRadius: 12 + fillColor: Theme.barBg + strokeColor: Theme.border + strokeWidth: 1 + + // Capture Escape to close + Keys.onEscapePressed: GlobalState.close() + + ColumnLayout { + anchors.fill: parent + anchors.margins: 12 + spacing: 8 + + // Search Header + RowLayout { + Layout.fillWidth: true + spacing: 12 + + Image { + width: 24; height: 24 + source: Quickshell.iconPath("system-search-symbolic") + sourceSize: Qt.size(24, 24) + smooth: true + mipmap: true + opacity: 0.7 + } + + TextInput { + id: searchInput + Layout.fillWidth: true + font { + family: Theme.mainFont + pixelSize: 20 + } + color: Theme.text + clip: true + + focus: true + cursorVisible: true + selectByMouse: true + inputMethodHints: Qt.ImhNoPredictiveText + + Text { + visible: searchInput.text === "" + text: "Search Applications..." + color: Theme.text + opacity: 0.3 + font: searchInput.font + } + + onTextChanged: resultsList.currentIndex = 0 + + Keys.onPressed: event => { + if (event.key === Qt.Key_Down) { + resultsList.incrementCurrentIndex() + event.accepted = true + } else if (event.key === Qt.Key_Up) { + resultsList.decrementCurrentIndex() + event.accepted = true + } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + if (resultsList.count > 0 && resultsList.currentIndex >= 0) { + if (resultsList.currentItem) { + resultsList.currentItem.launch() + } + } + event.accepted = true + } + } + } + } + + Rectangle { + Layout.fillWidth: true + height: 1 + color: Theme.border + } + + ListView { + id: resultsList + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + boundsBehavior: Flickable.StopAtBounds + highlightFollowsCurrentItem: true + highlightMoveDuration: 150 + highlightResizeDuration: 0 + + highlight: Squircle { + width: resultsList.width + height: 44 + cornerRadius: 8 + fillColor: Theme.surface + z: 1 + } + + model: internal.filterApps(searchInput.text) + + delegate: Squircle { + id: delegateRoot + required property var modelData + required property int index + + height: 44 + width: resultsList.width + + cornerRadius: 8 + fillColor: "transparent" + z: 5 + + function launch() { + if (modelData && modelData.execute) { + modelData.execute() + GlobalState.close() + } + } + + RowLayout { + anchors.fill: parent + anchors.leftMargin: 12 + anchors.rightMargin: 12 + spacing: 12 + + Image { + width: 24; height: 24 + source: Quickshell.iconPath(modelData.icon) + sourceSize: Qt.size(32, 32) + smooth: true + mipmap: true + } + + Column { + Layout.fillWidth: true + Text { + text: delegateRoot.modelData.name + color: Theme.text + font { + family: Theme.mainFont + pixelSize: 14 + weight: Font.Medium + } + } + Text { + visible: delegateRoot.modelData.comment !== "" + text: delegateRoot.modelData.comment + color: Theme.textMuted + font { + family: Theme.mainFont + pixelSize: 11 + } + elide: Text.ElideRight + width: parent.width + } + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: resultsList.currentIndex = index + onClicked: delegateRoot.launch() + } + } + + ScrollBar.vertical: ScrollBar {} + } + } + } +} +} |
