Files
homelab/dotfiles/quickshell/widgets/launcher/ApplicationLauncherContent.qml
T

603 lines
22 KiB
QML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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" : "AZ / 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
}
}
}
}
}