[verified] feat(quickshell): add orbit launcher variant
This commit is contained in:
@@ -0,0 +1,338 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Shapes
|
||||
import qs.widgets.theme
|
||||
|
||||
// Variant 10 visual core: radial application targeting arena plus execution
|
||||
// tape. Shares Theme/AppModel contracts but no launcher panel primitives.
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var apps: []
|
||||
property int selectedIndex: 0
|
||||
property bool showIcons: true
|
||||
property alias query: orbitInput.text
|
||||
|
||||
readonly property var selectedApp: apps.length > 0 && selectedIndex >= 0 && selectedIndex < apps.length
|
||||
? apps[selectedIndex] : null
|
||||
readonly property int orbitCount: Math.min(8, apps.length)
|
||||
|
||||
signal selectionRequested(int index)
|
||||
signal moveRequested(int delta)
|
||||
signal launchRequested(int index)
|
||||
signal dismissRequested
|
||||
|
||||
function focusSearch() { orbitInput.forceActiveFocus(); }
|
||||
function clearSearch() { orbitInput.text = ""; }
|
||||
|
||||
implicitWidth: 1120
|
||||
implicitHeight: 660
|
||||
|
||||
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 }
|
||||
|
||||
// Perimeter bracket frame, deliberately open on all four sides.
|
||||
Shape {
|
||||
anchors.fill: parent
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
ShapePath {
|
||||
fillColor: Theme.textAlpha(0)
|
||||
strokeColor: Theme.accent
|
||||
strokeWidth: 2
|
||||
startX: 24; startY: 92
|
||||
PathLine { x: 24; y: 24 }
|
||||
PathLine { x: 128; y: 24 }
|
||||
}
|
||||
ShapePath {
|
||||
fillColor: Theme.textAlpha(0)
|
||||
strokeColor: Theme.accent
|
||||
strokeWidth: 2
|
||||
startX: root.width - 128; startY: 24
|
||||
PathLine { x: root.width - 24; y: 24 }
|
||||
PathLine { x: root.width - 24; y: 92 }
|
||||
}
|
||||
ShapePath {
|
||||
fillColor: Theme.textAlpha(0)
|
||||
strokeColor: Theme.accent
|
||||
strokeWidth: 2
|
||||
startX: 24; startY: root.height - 92
|
||||
PathLine { x: 24; y: root.height - 24 }
|
||||
PathLine { x: 128; y: root.height - 24 }
|
||||
}
|
||||
ShapePath {
|
||||
fillColor: Theme.textAlpha(0)
|
||||
strokeColor: Theme.accent
|
||||
strokeWidth: 2
|
||||
startX: root.width - 128; startY: root.height - 24
|
||||
PathLine { x: root.width - 24; y: root.height - 24 }
|
||||
PathLine { x: root.width - 24; y: root.height - 92 }
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
x: 44; y: 38
|
||||
text: "ORBIT // APPLICATION TARGETING"
|
||||
color: Theme.text
|
||||
font.family: Theme.displayFont
|
||||
font.pixelSize: 17
|
||||
font.bold: true
|
||||
font.letterSpacing: 1.5
|
||||
}
|
||||
MicroText { x: 46; y: 64; text: "10 / RADIAL INDEX / EXECUTION CONTROL"; color: Theme.accent }
|
||||
MicroText { anchors.right: parent.right; anchors.rightMargin: 44; y: 48; text: root.apps.length + " TRACKED OBJECTS" }
|
||||
|
||||
// Radial targeting arena.
|
||||
Item {
|
||||
id: arena
|
||||
x: 36; y: 92
|
||||
width: 716; height: 500
|
||||
|
||||
// Axis traces.
|
||||
Rectangle { anchors.centerIn: parent; width: parent.width - 28; height: 1; color: Theme.hair }
|
||||
Rectangle { anchors.centerIn: parent; width: 1; height: parent.height - 16; color: Theme.hair }
|
||||
Rectangle { anchors.centerIn: parent; width: 414; height: 414; radius: 207; color: Theme.textAlpha(0.008); border.width: 1; border.color: Theme.accentAlpha(0.50) }
|
||||
Rectangle { anchors.centerIn: parent; width: 326; height: 326; radius: 163; color: Theme.textAlpha(0); border.width: 1; border.color: Theme.hair }
|
||||
Rectangle { anchors.centerIn: parent; width: 220; height: 220; radius: 110; color: Theme.textAlpha(0); border.width: 1; border.color: Theme.accentAlpha(0.28) }
|
||||
|
||||
// Cardinal labels.
|
||||
MicroText { anchors.horizontalCenter: parent.horizontalCenter; y: 8; text: "N // INDEX 00" }
|
||||
MicroText { anchors.horizontalCenter: parent.horizontalCenter; anchors.bottom: parent.bottom; anchors.bottomMargin: 5; text: "S // COMMAND BUS" }
|
||||
MicroText { anchors.verticalCenter: parent.verticalCenter; x: 8; text: "W"; color: Theme.accent }
|
||||
MicroText { anchors.verticalCenter: parent.verticalCenter; anchors.right: parent.right; anchors.rightMargin: 8; text: "E"; color: Theme.accent }
|
||||
|
||||
// App nodes distributed around the outer orbit.
|
||||
Repeater {
|
||||
model: root.orbitCount
|
||||
|
||||
MouseArea {
|
||||
id: node
|
||||
required property int index
|
||||
readonly property var app: root.apps[index]
|
||||
readonly property real angle: -Math.PI / 2 + index * (2 * Math.PI / Math.max(1, root.orbitCount))
|
||||
readonly property bool selected: index === root.selectedIndex
|
||||
width: selected ? 92 : 74
|
||||
height: selected ? 52 : 44
|
||||
x: arena.width / 2 + Math.cos(angle) * 205 - width / 2
|
||||
y: arena.height / 2 + Math.sin(angle) * 205 - height / 2
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onEntered: root.selectionRequested(index)
|
||||
onClicked: root.launchRequested(index)
|
||||
|
||||
Shape {
|
||||
id: nodeShape
|
||||
anchors.fill: parent
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
ShapePath {
|
||||
fillColor: node.selected ? Theme.selection : Theme.surface
|
||||
strokeColor: node.selected ? Theme.accent : Theme.hair
|
||||
strokeWidth: node.selected ? 2 : 1
|
||||
startX: 10; startY: 0
|
||||
PathLine { x: nodeShape.width; y: 0 }
|
||||
PathLine { x: nodeShape.width - 10; y: nodeShape.height }
|
||||
PathLine { x: 0; y: nodeShape.height }
|
||||
PathLine { x: 10; y: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
x: 7; anchors.verticalCenter: parent.verticalCenter
|
||||
width: 27; height: 27
|
||||
color: node.selected ? Theme.accentAlpha(0.18) : Theme.textAlpha(0.025)
|
||||
border.width: 1
|
||||
border.color: node.selected ? Theme.accent : Theme.hair
|
||||
IconImage { visible: root.showIcons; anchors.centerIn: parent; implicitSize: 20; source: root.showIcons ? Quickshell.iconPath(node.app.icon || "application-x-executable", "application-x-executable") : "" }
|
||||
Text { visible: !root.showIcons; anchors.centerIn: parent; text: String(node.app.name || "?").charAt(0).toUpperCase(); color: node.selected ? Theme.accent : Theme.text; font.family: Theme.displayFont; font.pixelSize: 11; font.bold: true }
|
||||
}
|
||||
Text {
|
||||
x: 40; anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - 48
|
||||
text: node.app.name || "UNKNOWN"
|
||||
color: node.selected ? Theme.accent : Theme.text
|
||||
font.family: Theme.displayFont
|
||||
font.pixelSize: 8
|
||||
font.bold: node.selected
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Core target, not a conventional card.
|
||||
Item {
|
||||
id: core
|
||||
anchors.centerIn: parent
|
||||
width: 196; height: 196
|
||||
|
||||
Rectangle { anchors.centerIn: parent; width: 186; height: 186; radius: 93; color: Theme.surface; border.width: 2; border.color: Theme.accent }
|
||||
Rectangle { anchors.centerIn: parent; width: 156; height: 156; radius: 78; color: Theme.accentAlpha(0.04); border.width: 1; border.color: Theme.hair }
|
||||
Rectangle { anchors.centerIn: parent; width: 118; height: 118; color: Theme.textAlpha(0.012); border.width: 1; border.color: Theme.accentAlpha(0.42); transform: Rotation { angle: 45 } }
|
||||
Rectangle { anchors.centerIn: parent; width: 196; height: 1; color: Theme.hair }
|
||||
Rectangle { anchors.centerIn: parent; width: 1; height: 196; color: Theme.hair }
|
||||
|
||||
Rectangle {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
y: 31; width: 58; height: 58
|
||||
color: Theme.surface
|
||||
border.width: 1; border.color: Theme.accent
|
||||
IconImage { visible: root.showIcons; anchors.centerIn: parent; implicitSize: 42; 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: 26; font.bold: true }
|
||||
}
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
y: 100; width: 156
|
||||
text: root.selectedApp ? root.selectedApp.name : "NO TARGET"
|
||||
color: Theme.text
|
||||
font.family: Theme.displayFont
|
||||
font.pixelSize: 11
|
||||
font.bold: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
MicroText { anchors.horizontalCenter: parent.horizontalCenter; y: 124; width: 150; horizontalAlignment: Text.AlignHCenter; text: root.selectedApp ? (root.selectedApp.genericName || "APPLICATION") : "AWAITING LOCK"; color: Theme.accent }
|
||||
MicroText { anchors.horizontalCenter: parent.horizontalCenter; y: 146; text: "LOCK // " + String(Math.max(0, root.selectedIndex + 1)).padStart(3, "0") }
|
||||
}
|
||||
|
||||
// Search rail crosses the bottom of the arena.
|
||||
Shape {
|
||||
id: queryRail
|
||||
x: 98; anchors.bottom: parent.bottom; anchors.bottomMargin: 24
|
||||
width: 520; height: 48
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
ShapePath {
|
||||
fillColor: Theme.accentAlpha(0.10)
|
||||
strokeColor: Theme.accent
|
||||
strokeWidth: 1
|
||||
startX: 16; startY: 0
|
||||
PathLine { x: queryRail.width; y: 0 }
|
||||
PathLine { x: queryRail.width - 16; y: queryRail.height }
|
||||
PathLine { x: 0; y: queryRail.height }
|
||||
PathLine { x: 16; y: 0 }
|
||||
}
|
||||
}
|
||||
Text { x: 118; anchors.bottom: parent.bottom; anchors.bottomMargin: 33; text: "⌕"; color: Theme.accent; font.family: Theme.displayFont; font.pixelSize: 19; font.bold: true }
|
||||
TextInput {
|
||||
id: orbitInput
|
||||
x: 152; anchors.bottom: parent.bottom; anchors.bottomMargin: 31
|
||||
width: 385; height: 32
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
color: Theme.text
|
||||
selectionColor: Theme.accent
|
||||
selectedTextColor: Theme.surface
|
||||
font.family: Theme.displayFont
|
||||
font.pixelSize: 15
|
||||
font.letterSpacing: 1
|
||||
clip: true
|
||||
onTextChanged: root.selectionRequested(0)
|
||||
Keys.onPressed: event => {
|
||||
switch (event.key) {
|
||||
case Qt.Key_Right: case Qt.Key_Down: case Qt.Key_Tab: root.moveRequested(1); event.accepted = true; break;
|
||||
case Qt.Key_Left: 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: orbitInput.text.length === 0; text: "SCAN APPLICATION INDEX"; color: Theme.muted; font: orbitInput.font }
|
||||
}
|
||||
}
|
||||
|
||||
// Right execution tape.
|
||||
Item {
|
||||
id: tape
|
||||
x: 778; y: 92
|
||||
width: 308; height: 500
|
||||
|
||||
Shape {
|
||||
anchors.fill: parent
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
ShapePath {
|
||||
fillColor: Theme.textAlpha(0.014)
|
||||
strokeColor: Theme.hair
|
||||
strokeWidth: 1
|
||||
startX: 0; startY: 0
|
||||
PathLine { x: tape.width - 28; y: 0 }
|
||||
PathLine { x: tape.width; y: 28 }
|
||||
PathLine { x: tape.width; y: tape.height }
|
||||
PathLine { x: 28; y: tape.height }
|
||||
PathLine { x: 0; y: tape.height - 28 }
|
||||
PathLine { x: 0; y: 0 }
|
||||
}
|
||||
ShapePath {
|
||||
fillColor: Theme.accent
|
||||
strokeWidth: 0
|
||||
startX: 0; startY: 0
|
||||
PathLine { x: 82; y: 0 }
|
||||
PathLine { x: 82; y: 4 }
|
||||
PathLine { x: 0; y: 4 }
|
||||
PathLine { x: 0; y: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
Text { x: 16; y: 15; text: "EXECUTION TAPE"; color: Theme.accent; font.family: Theme.displayFont; font.pixelSize: 10; font.bold: true; font.letterSpacing: 1.2 }
|
||||
MicroText { anchors.right: parent.right; anchors.rightMargin: 17; y: 17; text: root.apps.length + " ROWS" }
|
||||
Rectangle { x: 14; y: 40; width: parent.width - 28; height: 1; color: Theme.hair }
|
||||
|
||||
ListView {
|
||||
id: tapeList
|
||||
x: 12; y: 51; width: parent.width - 24; height: 350
|
||||
model: root.apps
|
||||
currentIndex: root.selectedIndex
|
||||
spacing: 3
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
onCurrentIndexChanged: positionViewAtIndex(currentIndex, ListView.Contain)
|
||||
|
||||
delegate: MouseArea {
|
||||
id: tapeRow
|
||||
required property int index
|
||||
required property var modelData
|
||||
readonly property bool selected: index === root.selectedIndex
|
||||
width: ListView.view.width
|
||||
height: 37
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onEntered: root.selectionRequested(index)
|
||||
onClicked: root.launchRequested(index)
|
||||
|
||||
Rectangle { anchors.fill: parent; color: tapeRow.selected ? Theme.selection : Theme.textAlpha(0.018); border.width: 1; border.color: tapeRow.selected ? Theme.accent : Theme.textAlpha(0.09) }
|
||||
Rectangle { x: 0; width: tapeRow.selected ? 6 : 2; height: parent.height; color: tapeRow.selected ? Theme.accent : Theme.hair }
|
||||
Text { x: 13; anchors.verticalCenter: parent.verticalCenter; width: 24; text: String(tapeRow.index + 1).padStart(2, "0"); color: tapeRow.selected ? Theme.accent : Theme.muted; font.family: Theme.microFont; font.pixelSize: 7 }
|
||||
Text { x: 43; anchors.verticalCenter: parent.verticalCenter; width: parent.width - 116; text: tapeRow.modelData.name || "UNKNOWN"; color: tapeRow.selected ? Theme.accent : Theme.text; font.family: Theme.displayFont; font.pixelSize: 9; font.bold: tapeRow.selected; elide: Text.ElideRight }
|
||||
MicroText { anchors.right: parent.right; anchors.rightMargin: 10; anchors.verticalCenter: parent.verticalCenter; text: tapeRow.selected ? "LOCK" : "PASS"; color: tapeRow.selected ? Theme.accent : Theme.muted }
|
||||
}
|
||||
}
|
||||
|
||||
MicroText { x: 16; y: 417; width: parent.width - 32; height: 28; wrapMode: Text.Wrap; maximumLineCount: 2; text: root.selectedApp ? (root.selectedApp.comment || "No target metadata supplied.") : "No target acquired." }
|
||||
|
||||
MouseArea {
|
||||
id: execute
|
||||
x: 14; anchors.bottom: parent.bottom; anchors.bottomMargin: 15
|
||||
width: parent.width - 28; height: 42
|
||||
enabled: root.selectedApp !== null
|
||||
hoverEnabled: true
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: root.launchRequested(root.selectedIndex)
|
||||
Rectangle { anchors.fill: parent; color: execute.containsMouse ? Theme.accent : Theme.accentAlpha(0.16); border.width: 1; border.color: Theme.accent }
|
||||
Text { anchors.centerIn: parent; text: "COMMIT ORBITAL LAUNCH"; color: execute.containsMouse ? Theme.surface : Theme.accent; font.family: Theme.displayFont; font.pixelSize: 9; font.bold: true; font.letterSpacing: 1.1 }
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
x: 42; y: 622; spacing: 4
|
||||
Repeater { model: 28; Rectangle { required property int index; width: 11; height: 3; color: index % 4 === 0 ? Theme.accent : Theme.hair; transform: Rotation { angle: -30 } } }
|
||||
}
|
||||
MicroText { x: 390; y: 617; text: "←↑↓→ TRACK // ENTER COMMIT // ESC RELEASE"; color: Theme.accent }
|
||||
MicroText { anchors.right: parent.right; anchors.rightMargin: 40; y: 617; text: "ORBIT-10 // CRC 9C31" }
|
||||
}
|
||||
Reference in New Issue
Block a user