603 lines
22 KiB
QML
603 lines
22 KiB
QML
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
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|