[verified] feat(quickshell): add dense application launcher
This commit is contained in:
@@ -0,0 +1,602 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Shapes
|
||||
import qs.widgets.bar
|
||||
import qs.widgets.theme
|
||||
|
||||
// Headlessly renderable visual core for the primary application launcher.
|
||||
// Runtime concerns (DesktopEntries, layer shell, launching) stay in
|
||||
// ApplicationLauncher.qml; this component only renders state and emits intent.
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var apps: []
|
||||
property int selectedIndex: 0
|
||||
property bool showIcons: true
|
||||
property alias query: searchInput.text
|
||||
|
||||
readonly property var selectedApp: apps.length > 0 && selectedIndex >= 0 && selectedIndex < apps.length
|
||||
? apps[selectedIndex] : null
|
||||
|
||||
signal selectionRequested(int index)
|
||||
signal moveRequested(int delta)
|
||||
signal launchRequested(int index)
|
||||
signal dismissRequested
|
||||
|
||||
function focusSearch() { searchInput.forceActiveFocus(); }
|
||||
function clearSearch() { searchInput.text = ""; }
|
||||
|
||||
implicitWidth: 1080
|
||||
implicitHeight: 620
|
||||
|
||||
component MicroText: Text {
|
||||
color: Theme.muted
|
||||
font.family: Theme.microFont
|
||||
font.pixelSize: 7
|
||||
font.letterSpacing: 0.9
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
component KeyHint: Row {
|
||||
property string keyText: ""
|
||||
property string actionText: ""
|
||||
spacing: 6
|
||||
|
||||
Rectangle {
|
||||
width: keyLabel.implicitWidth + 10
|
||||
height: 18
|
||||
color: Theme.accentAlpha(0.14)
|
||||
border.width: 1
|
||||
border.color: Theme.accent
|
||||
|
||||
Text {
|
||||
id: keyLabel
|
||||
anchors.centerIn: parent
|
||||
text: parent.parent.keyText
|
||||
color: Theme.accent
|
||||
font.family: Theme.microFont
|
||||
font.pixelSize: 7
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
|
||||
MicroText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: parent.actionText
|
||||
}
|
||||
}
|
||||
|
||||
StatusBarPanel {
|
||||
anchors.fill: parent
|
||||
panelId: "008"
|
||||
title: "APPLICATION COMMAND INDEX"
|
||||
meta: root.apps.length + " TARGETS"
|
||||
chamfer: 18
|
||||
|
||||
// Query module.
|
||||
StatusBarPanel {
|
||||
id: queryPanel
|
||||
x: 18
|
||||
y: 34
|
||||
width: 670
|
||||
height: 76
|
||||
panelId: "QRY"
|
||||
title: "SEARCH VECTOR"
|
||||
meta: "FUZZY / PREFIX"
|
||||
|
||||
RowLayout {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
anchors.topMargin: 31
|
||||
height: 34
|
||||
spacing: 12
|
||||
|
||||
Text {
|
||||
text: ">_"
|
||||
color: Theme.accent
|
||||
font.family: Theme.displayFont
|
||||
font.pixelSize: 20
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
TextInput {
|
||||
id: searchInput
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
color: Theme.text
|
||||
selectionColor: Theme.accent
|
||||
selectedTextColor: Theme.surface
|
||||
font.family: Theme.displayFont
|
||||
font.pixelSize: 18
|
||||
font.letterSpacing: 1
|
||||
clip: true
|
||||
|
||||
onTextChanged: root.selectionRequested(0)
|
||||
|
||||
Keys.onPressed: event => {
|
||||
switch (event.key) {
|
||||
case Qt.Key_Down:
|
||||
case Qt.Key_Tab:
|
||||
root.moveRequested(1);
|
||||
event.accepted = true;
|
||||
break;
|
||||
case Qt.Key_Up:
|
||||
case Qt.Key_Backtab:
|
||||
root.moveRequested(-1);
|
||||
event.accepted = true;
|
||||
break;
|
||||
case Qt.Key_PageDown:
|
||||
root.moveRequested(5);
|
||||
event.accepted = true;
|
||||
break;
|
||||
case Qt.Key_PageUp:
|
||||
root.moveRequested(-5);
|
||||
event.accepted = true;
|
||||
break;
|
||||
case Qt.Key_Return:
|
||||
case Qt.Key_Enter:
|
||||
root.launchRequested(root.selectedIndex);
|
||||
event.accepted = true;
|
||||
break;
|
||||
case Qt.Key_Escape:
|
||||
root.dismissRequested();
|
||||
event.accepted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.fill: parent
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
visible: searchInput.text.length === 0
|
||||
text: "TYPE APPLICATION DESIGNATION"
|
||||
color: Theme.muted
|
||||
font: searchInput.font
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 84
|
||||
Layout.preferredHeight: 22
|
||||
color: Theme.accentAlpha(0.12)
|
||||
border.width: 1
|
||||
border.color: Theme.hair
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "LIVE INDEX"
|
||||
color: Theme.accent
|
||||
font.family: Theme.microFont
|
||||
font.pixelSize: 7
|
||||
font.bold: true
|
||||
font.letterSpacing: 0.8
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search result table.
|
||||
StatusBarPanel {
|
||||
id: resultPanel
|
||||
x: 18
|
||||
y: 118
|
||||
width: 670
|
||||
height: 424
|
||||
panelId: "IDX"
|
||||
title: "APPLICATION TABLE"
|
||||
meta: root.query.length > 0 ? "FILTER ACTIVE" : "A–Z / LOCAL"
|
||||
|
||||
ListView {
|
||||
id: appList
|
||||
x: 9
|
||||
y: 29
|
||||
width: parent.width - 18
|
||||
height: parent.height - 38
|
||||
clip: true
|
||||
spacing: 3
|
||||
model: root.apps
|
||||
currentIndex: root.selectedIndex
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
onCurrentIndexChanged: positionViewAtIndex(currentIndex, ListView.Contain)
|
||||
|
||||
delegate: MouseArea {
|
||||
id: row
|
||||
required property int index
|
||||
required property var modelData
|
||||
|
||||
width: ListView.view.width
|
||||
height: 48
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
readonly property bool selected: index === root.selectedIndex
|
||||
|
||||
onEntered: root.selectionRequested(index)
|
||||
onClicked: root.launchRequested(index)
|
||||
|
||||
Shape {
|
||||
id: rowShape
|
||||
anchors.fill: parent
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
|
||||
ShapePath {
|
||||
fillColor: row.selected ? Theme.selection : Theme.textAlpha(0.025)
|
||||
strokeColor: row.selected ? Theme.accent : Theme.textAlpha(0.10)
|
||||
strokeWidth: 1
|
||||
startX: 9; startY: 0
|
||||
PathLine { x: rowShape.width; y: 0 }
|
||||
PathLine { x: rowShape.width - 9; y: rowShape.height }
|
||||
PathLine { x: 0; y: rowShape.height }
|
||||
PathLine { x: 9; y: 0 }
|
||||
}
|
||||
|
||||
ShapePath {
|
||||
fillColor: row.selected ? Theme.accent : Theme.textAlpha(0.12)
|
||||
strokeWidth: 0
|
||||
startX: 9; startY: 0
|
||||
PathLine { x: 13; y: 0 }
|
||||
PathLine { x: 4; y: rowShape.height }
|
||||
PathLine { x: 0; y: rowShape.height }
|
||||
PathLine { x: 9; y: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 18
|
||||
anchors.rightMargin: 18
|
||||
spacing: 12
|
||||
|
||||
Text {
|
||||
Layout.preferredWidth: 28
|
||||
text: String(row.index + 1).padStart(2, "0")
|
||||
color: row.selected ? Theme.accent : Theme.muted
|
||||
font.family: Theme.microFont
|
||||
font.pixelSize: 8
|
||||
font.bold: row.selected
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 34
|
||||
Layout.preferredHeight: 34
|
||||
color: row.selected ? Theme.accentAlpha(0.14) : Theme.textAlpha(0.035)
|
||||
border.width: 1
|
||||
border.color: row.selected ? Theme.accent : Theme.hair
|
||||
|
||||
IconImage {
|
||||
visible: root.showIcons
|
||||
anchors.centerIn: parent
|
||||
implicitSize: 24
|
||||
source: root.showIcons
|
||||
? Quickshell.iconPath(row.modelData.icon || "application-x-executable", "application-x-executable")
|
||||
: ""
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: !root.showIcons
|
||||
anchors.centerIn: parent
|
||||
text: String(row.modelData.name || "?").charAt(0).toUpperCase()
|
||||
color: row.selected ? Theme.accent : Theme.text
|
||||
font.family: Theme.displayFont
|
||||
font.pixelSize: 14
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 1
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: row.modelData.name || "UNKNOWN TARGET"
|
||||
color: row.selected ? Theme.accent : Theme.text
|
||||
font.family: Theme.displayFont
|
||||
font.pixelSize: 11
|
||||
font.bold: row.selected
|
||||
font.letterSpacing: 0.5
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
MicroText {
|
||||
Layout.fillWidth: true
|
||||
text: row.modelData.genericName || row.modelData.comment || "DESKTOP APPLICATION"
|
||||
}
|
||||
}
|
||||
|
||||
MicroText {
|
||||
Layout.preferredWidth: 100
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: row.index === root.selectedIndex ? "LOCKED" : "AVAILABLE"
|
||||
color: row.selected ? Theme.accent : Theme.muted
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 38
|
||||
Layout.preferredHeight: 6
|
||||
color: Theme.textAlpha(0.05)
|
||||
border.width: 1
|
||||
border.color: Theme.hair
|
||||
|
||||
Rectangle {
|
||||
width: row.selected ? parent.width : Math.max(5, parent.width * (1 - row.index / Math.max(1, root.apps.length)))
|
||||
height: parent.height
|
||||
color: row.selected ? Theme.accent : Theme.textAlpha(0.20)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
visible: root.apps.length === 0
|
||||
text: "NO EXECUTABLE TARGETS MATCH QUERY"
|
||||
color: Theme.muted
|
||||
font.family: Theme.microFont
|
||||
font.pixelSize: 9
|
||||
font.letterSpacing: 1.2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Selected application inspector.
|
||||
StatusBarPanel {
|
||||
id: inspector
|
||||
x: 700
|
||||
y: 34
|
||||
width: 362
|
||||
height: 508
|
||||
panelId: "SEL"
|
||||
title: "TARGET INSPECTOR"
|
||||
meta: root.selectedApp ? "LOCKED" : "NO TARGET"
|
||||
|
||||
Item {
|
||||
id: reticle
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
y: 39
|
||||
width: 126
|
||||
height: 126
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: 112
|
||||
height: 112
|
||||
radius: 56
|
||||
color: Theme.textAlpha(0.015)
|
||||
border.width: 1
|
||||
border.color: Theme.accentAlpha(0.52)
|
||||
}
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: 84
|
||||
height: 84
|
||||
radius: 42
|
||||
color: Theme.textAlpha(0)
|
||||
border.width: 1
|
||||
border.color: Theme.hair
|
||||
}
|
||||
Rectangle { anchors.centerIn: parent; width: 126; height: 1; color: Theme.hair }
|
||||
Rectangle { anchors.centerIn: parent; width: 1; height: 126; color: Theme.hair }
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: 60
|
||||
height: 60
|
||||
color: Theme.surface
|
||||
border.width: 1
|
||||
border.color: Theme.accent
|
||||
|
||||
IconImage {
|
||||
visible: root.showIcons
|
||||
anchors.centerIn: parent
|
||||
implicitSize: 44
|
||||
source: root.showIcons && root.selectedApp
|
||||
? Quickshell.iconPath(root.selectedApp.icon || "application-x-executable", "application-x-executable")
|
||||
: ""
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: !root.showIcons
|
||||
anchors.centerIn: parent
|
||||
text: root.selectedApp ? String(root.selectedApp.name || "?").charAt(0).toUpperCase() : "?"
|
||||
color: Theme.accent
|
||||
font.family: Theme.displayFont
|
||||
font.pixelSize: 28
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: 4
|
||||
Rectangle {
|
||||
required property int index
|
||||
width: 5; height: 5
|
||||
x: index % 2 === 0 ? 8 : reticle.width - 13
|
||||
y: index < 2 ? 8 : reticle.height - 13
|
||||
color: Theme.accent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.top: reticle.bottom
|
||||
anchors.topMargin: 14
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: parent.width - 34
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: root.selectedApp ? root.selectedApp.name : "NO TARGET"
|
||||
color: Theme.text
|
||||
font.family: Theme.displayFont
|
||||
font.pixelSize: 19
|
||||
font.bold: true
|
||||
font.letterSpacing: 1
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
MicroText {
|
||||
id: genericLabel
|
||||
anchors.top: reticle.bottom
|
||||
anchors.topMargin: 42
|
||||
x: 18
|
||||
width: parent.width - 36
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: root.selectedApp ? (root.selectedApp.genericName || "DESKTOP APPLICATION") : "AWAITING SEARCH VECTOR"
|
||||
color: Theme.accent
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
x: 18
|
||||
anchors.top: genericLabel.bottom
|
||||
anchors.topMargin: 12
|
||||
width: parent.width - 36
|
||||
height: 1
|
||||
color: Theme.hair
|
||||
}
|
||||
|
||||
MicroText {
|
||||
id: description
|
||||
x: 22
|
||||
anchors.top: genericLabel.bottom
|
||||
anchors.topMargin: 27
|
||||
width: parent.width - 44
|
||||
height: 42
|
||||
text: root.selectedApp ? (root.selectedApp.comment || "No application description supplied by desktop entry.") : "Enter a designation to acquire an executable target."
|
||||
wrapMode: Text.Wrap
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 3
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
id: telemetryGrid
|
||||
x: 18
|
||||
anchors.top: description.bottom
|
||||
anchors.topMargin: 15
|
||||
width: parent.width - 36
|
||||
columns: 2
|
||||
rowSpacing: 7
|
||||
columnSpacing: 7
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
{ label: "ENTRY", value: "DESKTOP" },
|
||||
{ label: "MODE", value: "DETACHED" },
|
||||
{ label: "AUTH", value: "LOCAL USER" },
|
||||
{ label: "INDEX", value: String(Math.max(0, root.selectedIndex + 1)).padStart(3, "0") }
|
||||
]
|
||||
|
||||
Rectangle {
|
||||
required property var modelData
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 46
|
||||
color: Theme.textAlpha(0.025)
|
||||
border.width: 1
|
||||
border.color: Theme.hair
|
||||
|
||||
MicroText { x: 8; y: 7; width: parent.width - 16; text: modelData.label }
|
||||
Text {
|
||||
x: 8; y: 22; width: parent.width - 16
|
||||
text: modelData.value
|
||||
color: Theme.text
|
||||
font.family: Theme.displayFont
|
||||
font.pixelSize: 9
|
||||
font.bold: true
|
||||
font.letterSpacing: 0.6
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
x: 18
|
||||
anchors.bottom: executeTile.top
|
||||
anchors.bottomMargin: 12
|
||||
width: parent.width - 36
|
||||
height: 18
|
||||
|
||||
Row {
|
||||
spacing: 5
|
||||
Repeater {
|
||||
model: 24
|
||||
Rectangle {
|
||||
required property int index
|
||||
width: 7; height: 3
|
||||
y: index % 3 === 0 ? 0 : 4
|
||||
color: index < 16 ? Theme.accent : Theme.hair
|
||||
transform: Rotation { angle: -35 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: executeTile
|
||||
x: 18
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 14
|
||||
width: parent.width - 36
|
||||
height: 42
|
||||
enabled: root.selectedApp !== null
|
||||
hoverEnabled: true
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: root.launchRequested(root.selectedIndex)
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: executeTile.containsMouse ? Theme.accent : Theme.accentAlpha(0.16)
|
||||
border.width: 1
|
||||
border.color: Theme.accent
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: root.selectedApp ? "EXECUTE SELECTED TARGET" : "NO TARGET ACQUIRED"
|
||||
color: executeTile.containsMouse ? Theme.surface : Theme.accent
|
||||
font.family: Theme.displayFont
|
||||
font.pixelSize: 10
|
||||
font.bold: true
|
||||
font.letterSpacing: 1.2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dense command footer.
|
||||
StatusBarPanel {
|
||||
x: 18
|
||||
y: 550
|
||||
width: 1044
|
||||
height: 52
|
||||
showHeader: false
|
||||
chamfer: 9
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
spacing: 18
|
||||
|
||||
MicroText { text: "INPUT CHANNEL // KEYBOARD"; color: Theme.accent }
|
||||
Rectangle { Layout.preferredWidth: 1; Layout.preferredHeight: 22; color: Theme.hair }
|
||||
KeyHint { keyText: "↑ ↓"; actionText: "SELECT" }
|
||||
KeyHint { keyText: "ENTER"; actionText: "EXECUTE" }
|
||||
KeyHint { keyText: "ESC"; actionText: "ABORT" }
|
||||
Item { Layout.fillWidth: true }
|
||||
MicroText { text: "QRY:" + (root.query.length > 0 ? "ACTIVE" : "IDLE") }
|
||||
MicroText { text: "CRC // A7F2" }
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 76
|
||||
Layout.preferredHeight: 4
|
||||
color: Theme.accent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user