474 lines
20 KiB
QML
474 lines
20 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import Quickshell
|
|
import Quickshell.Widgets
|
|
import QtQuick
|
|
import QtQuick.Layouts
|
|
import QtQuick.Shapes
|
|
import qs.widgets.theme
|
|
|
|
// Variant 9 visual core: asymmetric "Blade" composition. It shares the shell
|
|
// Theme and AppModel contract, but intentionally does not reuse StatusBarPanel
|
|
// or the nested-panel structure of ApplicationLauncherContent.
|
|
Item {
|
|
id: root
|
|
|
|
property var apps: []
|
|
property int selectedIndex: 0
|
|
property bool showIcons: true
|
|
property alias query: commandInput.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() { commandInput.forceActiveFocus(); }
|
|
function clearSearch() { commandInput.text = ""; }
|
|
|
|
implicitWidth: 1120
|
|
implicitHeight: 640
|
|
|
|
component MicroText: Text {
|
|
color: Theme.muted
|
|
font.family: Theme.microFont
|
|
font.pixelSize: 7
|
|
font.letterSpacing: 0.9
|
|
elide: Text.ElideRight
|
|
}
|
|
|
|
Rectangle { anchors.fill: parent; color: Theme.surface }
|
|
|
|
// Sparse circuit traces behind the active surfaces.
|
|
Shape {
|
|
anchors.fill: parent
|
|
preferredRendererType: Shape.CurveRenderer
|
|
ShapePath {
|
|
fillColor: Theme.textAlpha(0)
|
|
strokeColor: Theme.accentAlpha(0.24)
|
|
strokeWidth: 1
|
|
startX: 24; startY: 92
|
|
PathLine { x: 115; y: 92 }
|
|
PathLine { x: 138; y: 69 }
|
|
PathLine { x: 480; y: 69 }
|
|
PathLine { x: 494; y: 55 }
|
|
PathLine { x: 840; y: 55 }
|
|
}
|
|
ShapePath {
|
|
fillColor: Theme.textAlpha(0)
|
|
strokeColor: Theme.hair
|
|
strokeWidth: 1
|
|
startX: 115; startY: 570
|
|
PathLine { x: 146; y: 601 }
|
|
PathLine { x: 775; y: 601 }
|
|
PathLine { x: 801; y: 575 }
|
|
PathLine { x: 1095; y: 575 }
|
|
}
|
|
}
|
|
|
|
// Top command mast — an open angular frame rather than panel chrome.
|
|
Shape {
|
|
id: mast
|
|
x: 26; y: 18
|
|
width: parent.width - 52; height: 72
|
|
preferredRendererType: Shape.CurveRenderer
|
|
ShapePath {
|
|
fillColor: Theme.textAlpha(0.018)
|
|
strokeColor: Theme.hair
|
|
strokeWidth: 1
|
|
startX: 0; startY: 17
|
|
PathLine { x: 17; y: 0 }
|
|
PathLine { x: mast.width - 110; y: 0 }
|
|
PathLine { x: mast.width - 88; y: 22 }
|
|
PathLine { x: mast.width; y: 22 }
|
|
PathLine { x: mast.width; y: mast.height }
|
|
PathLine { x: 0; y: mast.height }
|
|
PathLine { x: 0; y: 17 }
|
|
}
|
|
ShapePath {
|
|
fillColor: Theme.accent
|
|
strokeWidth: 0
|
|
startX: 0; startY: 17
|
|
PathLine { x: 17; y: 0 }
|
|
PathLine { x: 122; y: 0 }
|
|
PathLine { x: 116; y: 4 }
|
|
PathLine { x: 20; y: 4 }
|
|
PathLine { x: 4; y: 20 }
|
|
PathLine { x: 4; y: mast.height }
|
|
PathLine { x: 0; y: mast.height }
|
|
PathLine { x: 0; y: 17 }
|
|
}
|
|
}
|
|
|
|
Text {
|
|
x: 54; y: 31
|
|
text: "BLADE // EXECUTION MATRIX"
|
|
color: Theme.text
|
|
font.family: Theme.displayFont
|
|
font.pixelSize: 16
|
|
font.bold: true
|
|
font.letterSpacing: 1.4
|
|
}
|
|
MicroText { x: 56; y: 57; text: "09 / APPLICATION ROUTER / LOCAL DESKTOP ENTRIES"; color: Theme.accent }
|
|
|
|
Row {
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: 48
|
|
y: 37
|
|
spacing: 4
|
|
Repeater {
|
|
model: 16
|
|
Rectangle {
|
|
required property int index
|
|
width: 9; height: index % 4 === 0 ? 16 : 8
|
|
y: index % 4 === 0 ? 0 : 8
|
|
color: index < Math.min(16, root.apps.length + 6) ? Theme.accent : Theme.hair
|
|
transform: Rotation { angle: -28 }
|
|
}
|
|
}
|
|
}
|
|
|
|
// Left category rotor and mode spine.
|
|
Item {
|
|
id: rotorZone
|
|
x: 26; y: 108
|
|
width: 188; height: 444
|
|
|
|
Shape {
|
|
anchors.fill: parent
|
|
preferredRendererType: Shape.CurveRenderer
|
|
ShapePath {
|
|
fillColor: Theme.textAlpha(0.018)
|
|
strokeColor: Theme.hair
|
|
strokeWidth: 1
|
|
startX: 0; startY: 0
|
|
PathLine { x: 154; y: 0 }
|
|
PathLine { x: 188; y: 34 }
|
|
PathLine { x: 188; y: 408 }
|
|
PathLine { x: 152; y: 444 }
|
|
PathLine { x: 0; y: 444 }
|
|
PathLine { x: 0; y: 0 }
|
|
}
|
|
ShapePath {
|
|
fillColor: Theme.accent
|
|
strokeWidth: 0
|
|
startX: 0; startY: 0
|
|
PathLine { x: 7; y: 0 }
|
|
PathLine { x: 7; y: 444 }
|
|
PathLine { x: 0; y: 444 }
|
|
PathLine { x: 0; y: 0 }
|
|
}
|
|
}
|
|
|
|
Text {
|
|
x: 20; y: 18
|
|
text: "VECTOR"
|
|
color: Theme.accent
|
|
font.family: Theme.displayFont
|
|
font.pixelSize: 11
|
|
font.bold: true
|
|
font.letterSpacing: 1.4
|
|
}
|
|
MicroText { x: 20; y: 38; text: "CATEGORY BUS" }
|
|
|
|
Item {
|
|
id: rotor
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
y: 70; width: 126; height: 126
|
|
Rectangle { anchors.centerIn: parent; width: 116; height: 116; radius: 58; color: Theme.textAlpha(0); border.width: 1; border.color: Theme.accentAlpha(0.55) }
|
|
Rectangle { anchors.centerIn: parent; width: 82; height: 82; radius: 41; 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: 32; height: 32; color: Theme.accentAlpha(0.18); border.width: 1; border.color: Theme.accent; transform: Rotation { angle: 45 } }
|
|
Text { anchors.centerIn: parent; text: "A"; color: Theme.accent; font.family: Theme.displayFont; font.pixelSize: 16; font.bold: true }
|
|
Repeater {
|
|
model: 4
|
|
Rectangle {
|
|
required property int index
|
|
width: 6; height: 6; radius: 3
|
|
x: [16, 101, 101, 16][index]
|
|
y: [16, 16, 101, 101][index]
|
|
color: Theme.accent
|
|
}
|
|
}
|
|
}
|
|
|
|
Column {
|
|
x: 18; y: 218
|
|
width: parent.width - 38
|
|
spacing: 7
|
|
Repeater {
|
|
model: ["ALL TARGETS", "SYSTEM", "DEVELOP", "NETWORK", "MEDIA"]
|
|
Rectangle {
|
|
required property int index
|
|
required property string modelData
|
|
width: parent.width - index * 5
|
|
height: 31
|
|
x: index * 5
|
|
color: index === 0 ? Theme.accentAlpha(0.14) : Theme.textAlpha(0.025)
|
|
border.width: 1
|
|
border.color: index === 0 ? Theme.accent : Theme.hair
|
|
Text {
|
|
x: 9; anchors.verticalCenter: parent.verticalCenter
|
|
text: String(index).padStart(2, "0") + " " + modelData
|
|
color: index === 0 ? Theme.accent : Theme.muted
|
|
font.family: Theme.microFont
|
|
font.pixelSize: 7
|
|
font.bold: index === 0
|
|
font.letterSpacing: 0.7
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MicroText { x: 18; anchors.bottom: parent.bottom; anchors.bottomMargin: 14; text: "BUS // UNFILTERED"; color: Theme.accent }
|
|
}
|
|
|
|
// Central command spine.
|
|
Item {
|
|
id: commandZone
|
|
x: 230; y: 108
|
|
width: 594; height: 486
|
|
|
|
Shape {
|
|
anchors.fill: parent
|
|
preferredRendererType: Shape.CurveRenderer
|
|
ShapePath {
|
|
fillColor: Theme.textAlpha(0.012)
|
|
strokeColor: Theme.hair
|
|
strokeWidth: 1
|
|
startX: 20; startY: 0
|
|
PathLine { x: commandZone.width; y: 0 }
|
|
PathLine { x: commandZone.width - 20; y: commandZone.height }
|
|
PathLine { x: 0; y: commandZone.height }
|
|
PathLine { x: 20; y: 0 }
|
|
}
|
|
}
|
|
|
|
// Search blade.
|
|
Shape {
|
|
id: searchBlade
|
|
x: 12; y: 12; width: parent.width - 32; height: 58
|
|
preferredRendererType: Shape.CurveRenderer
|
|
ShapePath {
|
|
fillColor: Theme.accentAlpha(0.10)
|
|
strokeColor: Theme.accent
|
|
strokeWidth: 1
|
|
startX: 18; startY: 0
|
|
PathLine { x: searchBlade.width; y: 0 }
|
|
PathLine { x: searchBlade.width - 18; y: searchBlade.height }
|
|
PathLine { x: 0; y: searchBlade.height }
|
|
PathLine { x: 18; y: 0 }
|
|
}
|
|
}
|
|
Text { x: 32; y: 28; text: ">"; color: Theme.accent; font.family: Theme.displayFont; font.pixelSize: 20; font.bold: true }
|
|
TextInput {
|
|
id: commandInput
|
|
x: 62; y: 22; width: 430; height: 38
|
|
verticalAlignment: TextInput.AlignVCenter
|
|
color: Theme.text
|
|
selectionColor: Theme.accent
|
|
selectedTextColor: Theme.surface
|
|
font.family: Theme.displayFont
|
|
font.pixelSize: 17
|
|
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: commandInput.text.length === 0; text: "ACQUIRE TARGET"; color: Theme.muted; font: commandInput.font }
|
|
}
|
|
MicroText { anchors.right: parent.right; anchors.rightMargin: 38; y: 35; text: root.apps.length + " HIT"; color: Theme.accent }
|
|
|
|
ListView {
|
|
id: bladeList
|
|
x: 10; y: 84; width: parent.width - 26; height: 382
|
|
model: root.apps
|
|
currentIndex: root.selectedIndex
|
|
clip: true
|
|
spacing: 4
|
|
boundsBehavior: Flickable.StopAtBounds
|
|
onCurrentIndexChanged: positionViewAtIndex(currentIndex, ListView.Contain)
|
|
|
|
delegate: MouseArea {
|
|
id: blade
|
|
required property int index
|
|
required property var modelData
|
|
readonly property bool selected: index === root.selectedIndex
|
|
width: ListView.view.width - (index % 2 === 0 ? 0 : 16)
|
|
height: 43
|
|
x: index % 2 === 0 ? 0 : 16
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onEntered: root.selectionRequested(index)
|
|
onClicked: root.launchRequested(index)
|
|
|
|
Shape {
|
|
id: bladeShape
|
|
anchors.fill: parent
|
|
preferredRendererType: Shape.CurveRenderer
|
|
ShapePath {
|
|
fillColor: blade.selected ? Theme.selection : Theme.textAlpha(0.025)
|
|
strokeColor: blade.selected ? Theme.accent : Theme.textAlpha(0.12)
|
|
strokeWidth: 1
|
|
startX: 16; startY: 0
|
|
PathLine { x: bladeShape.width; y: 0 }
|
|
PathLine { x: bladeShape.width - 24; y: bladeShape.height }
|
|
PathLine { x: 0; y: bladeShape.height }
|
|
PathLine { x: 16; y: 0 }
|
|
}
|
|
ShapePath {
|
|
fillColor: blade.selected ? Theme.accent : Theme.textAlpha(0.12)
|
|
strokeWidth: 0
|
|
startX: 16; startY: 0
|
|
PathLine { x: 22; y: 0 }
|
|
PathLine { x: 6; y: bladeShape.height }
|
|
PathLine { x: 0; y: bladeShape.height }
|
|
PathLine { x: 16; y: 0 }
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
anchors.fill: parent
|
|
anchors.leftMargin: 26
|
|
anchors.rightMargin: 28
|
|
spacing: 10
|
|
Text { Layout.preferredWidth: 26; text: String(blade.index + 1).padStart(2, "0"); color: blade.selected ? Theme.accent : Theme.muted; font.family: Theme.microFont; font.pixelSize: 7 }
|
|
Rectangle {
|
|
Layout.preferredWidth: 28; Layout.preferredHeight: 28
|
|
color: blade.selected ? Theme.accentAlpha(0.14) : Theme.textAlpha(0.025)
|
|
border.width: 1; border.color: blade.selected ? Theme.accent : Theme.hair
|
|
IconImage { visible: root.showIcons; anchors.centerIn: parent; implicitSize: 20; source: root.showIcons ? Quickshell.iconPath(blade.modelData.icon || "application-x-executable", "application-x-executable") : "" }
|
|
Text { visible: !root.showIcons; anchors.centerIn: parent; text: String(blade.modelData.name || "?").charAt(0).toUpperCase(); color: blade.selected ? Theme.accent : Theme.text; font.family: Theme.displayFont; font.pixelSize: 11; font.bold: true }
|
|
}
|
|
Text { Layout.fillWidth: true; text: blade.modelData.name || "UNKNOWN"; color: blade.selected ? Theme.accent : Theme.text; font.family: Theme.displayFont; font.pixelSize: 10; font.bold: blade.selected; elide: Text.ElideRight }
|
|
MicroText { Layout.preferredWidth: 125; horizontalAlignment: Text.AlignRight; text: blade.modelData.genericName || "APPLICATION" }
|
|
Rectangle { Layout.preferredWidth: blade.selected ? 32 : 12; Layout.preferredHeight: 3; color: blade.selected ? Theme.accent : Theme.hair }
|
|
}
|
|
}
|
|
|
|
Text { anchors.centerIn: parent; visible: root.apps.length === 0; text: "NO TARGET VECTOR"; color: Theme.muted; font.family: Theme.microFont; font.pixelSize: 9; font.letterSpacing: 1.2 }
|
|
}
|
|
}
|
|
|
|
// Right dossier: open brackets and data lines, deliberately not a panel.
|
|
Item {
|
|
id: dossier
|
|
x: 842; y: 108
|
|
width: 252; height: 444
|
|
|
|
Shape {
|
|
anchors.fill: parent
|
|
preferredRendererType: Shape.CurveRenderer
|
|
ShapePath {
|
|
fillColor: Theme.textAlpha(0)
|
|
strokeColor: Theme.accent
|
|
strokeWidth: 2
|
|
startX: 36; startY: 0
|
|
PathLine { x: 0; y: 0 }
|
|
PathLine { x: 0; y: 86 }
|
|
}
|
|
ShapePath {
|
|
fillColor: Theme.textAlpha(0)
|
|
strokeColor: Theme.accent
|
|
strokeWidth: 2
|
|
startX: dossier.width - 36; startY: dossier.height
|
|
PathLine { x: dossier.width; y: dossier.height }
|
|
PathLine { x: dossier.width; y: dossier.height - 86 }
|
|
}
|
|
ShapePath {
|
|
fillColor: Theme.textAlpha(0)
|
|
strokeColor: Theme.hair
|
|
strokeWidth: 1
|
|
startX: 20; startY: 20
|
|
PathLine { x: dossier.width; y: 20 }
|
|
PathLine { x: dossier.width; y: dossier.height - 20 }
|
|
PathLine { x: 0; y: dossier.height - 20 }
|
|
PathLine { x: 0; y: 104 }
|
|
}
|
|
}
|
|
|
|
MicroText { x: 18; y: 12; text: "ACTIVE DOSSIER"; color: Theme.accent }
|
|
Text {
|
|
x: 18; y: 45; width: parent.width - 36
|
|
text: root.selectedApp ? root.selectedApp.name : "NO TARGET"
|
|
color: Theme.text
|
|
font.family: Theme.displayFont
|
|
font.pixelSize: 18
|
|
font.bold: true
|
|
font.letterSpacing: 0.8
|
|
wrapMode: Text.Wrap
|
|
maximumLineCount: 2
|
|
elide: Text.ElideRight
|
|
}
|
|
MicroText { x: 18; y: 94; width: parent.width - 36; text: root.selectedApp ? (root.selectedApp.genericName || "DESKTOP APPLICATION") : "AWAITING ACQUISITION"; color: Theme.accent }
|
|
|
|
Rectangle { x: 18; y: 119; width: parent.width - 36; height: 1; color: Theme.hair }
|
|
MicroText { x: 18; y: 137; width: parent.width - 36; height: 52; wrapMode: Text.Wrap; maximumLineCount: 4; text: root.selectedApp ? (root.selectedApp.comment || "No metadata supplied by desktop entry.") : "Enter a search vector and select an executable target." }
|
|
|
|
Column {
|
|
x: 18; y: 210; width: parent.width - 36; spacing: 8
|
|
Repeater {
|
|
model: [
|
|
{ k: "INDEX", v: String(Math.max(0, root.selectedIndex + 1)).padStart(3, "0") },
|
|
{ k: "TYPE", v: "DESKTOP ENTRY" },
|
|
{ k: "ROUTE", v: "DETACHED" },
|
|
{ k: "STATE", v: root.selectedApp ? "ARMED" : "IDLE" }
|
|
]
|
|
Item {
|
|
required property var modelData
|
|
width: parent.width; height: 27
|
|
Rectangle { x: 0; y: parent.height - 1; width: parent.width; height: 1; color: Theme.hair }
|
|
MicroText { x: 0; anchors.verticalCenter: parent.verticalCenter; text: modelData.k }
|
|
Text { anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter; text: modelData.v; color: modelData.k === "STATE" ? Theme.accent : Theme.text; font.family: Theme.displayFont; font.pixelSize: 8; font.bold: true; font.letterSpacing: 0.5 }
|
|
}
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: fire
|
|
x: 18; anchors.bottom: parent.bottom; anchors.bottomMargin: 34
|
|
width: parent.width - 36; height: 44
|
|
enabled: root.selectedApp !== null
|
|
hoverEnabled: true
|
|
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
|
onClicked: root.launchRequested(root.selectedIndex)
|
|
Shape {
|
|
id: fireShape
|
|
anchors.fill: parent
|
|
preferredRendererType: Shape.CurveRenderer
|
|
ShapePath {
|
|
fillColor: fire.containsMouse ? Theme.accent : Theme.accentAlpha(0.16)
|
|
strokeColor: Theme.accent
|
|
strokeWidth: 1
|
|
startX: 14; startY: 0
|
|
PathLine { x: fireShape.width; y: 0 }
|
|
PathLine { x: fireShape.width - 14; y: fireShape.height }
|
|
PathLine { x: 0; y: fireShape.height }
|
|
PathLine { x: 14; y: 0 }
|
|
}
|
|
}
|
|
Text { anchors.centerIn: parent; text: "LAUNCH VECTOR"; color: fire.containsMouse ? Theme.surface : Theme.accent; font.family: Theme.displayFont; font.pixelSize: 10; font.bold: true; font.letterSpacing: 1.2 }
|
|
}
|
|
}
|
|
|
|
// Bottom caution lane and key map.
|
|
Row {
|
|
x: 28; y: 612; spacing: 4
|
|
Repeater { model: 30; Rectangle { required property int index; width: 12; height: 4; color: index % 3 === 0 ? Theme.accent : Theme.hair; transform: Rotation { angle: -32 } } }
|
|
}
|
|
MicroText { x: 420; y: 606; text: "↑↓ SELECT // ENTER LAUNCH // ESC ABORT // PGUP/PGDN STEP"; color: Theme.accent }
|
|
MicroText { anchors.right: parent.right; anchors.rightMargin: 28; y: 606; text: "BLADE-09 / CRC A7F2" }
|
|
}
|