Compare commits
9
Commits
master
...
abccf536f6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abccf536f6 | ||
|
|
6406e06330 | ||
|
|
9b6b799a5d | ||
|
|
5ecc73f084 | ||
|
|
fdc00a8574 | ||
|
|
b16cc93ff6 | ||
|
|
01bc169180 | ||
|
|
54896033c6 | ||
|
|
22fe8ab778 |
@@ -14,3 +14,6 @@ keys.txt
|
|||||||
|
|
||||||
# local env (PATH etc.)
|
# local env (PATH etc.)
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
# local visual-verification output
|
||||||
|
.artifacts/
|
||||||
|
|||||||
@@ -27,14 +27,19 @@ Quickshell hot-reloads QML on file save when already running, so for most edits
|
|||||||
**Directory layout**:
|
**Directory layout**:
|
||||||
- `widgets/bar/` — `Bar.qml` is the main sidebar (right-anchored, full height) hosting the module stack (date, clock, tray, decorative dividers); `BarTop.qml`/`BarBottom.qml` are thin accent-colored strips anchored to the top/bottom edges.
|
- `widgets/bar/` — `Bar.qml` is the main sidebar (right-anchored, full height) hosting the module stack (date, clock, tray, decorative dividers); `BarTop.qml`/`BarBottom.qml` are thin accent-colored strips anchored to the top/bottom edges.
|
||||||
- `widgets/bar/modules/` — individual bar widgets (`Clock`, `Date`, `Tray`/`TrayItem`, `Volume`) built on the shared `BarWidget` base component.
|
- `widgets/bar/modules/` — individual bar widgets (`Clock`, `Date`, `Tray`/`TrayItem`, `Volume`) built on the shared `BarWidget` base component.
|
||||||
- `widgets/launcher/` — app launcher panel (`Launcher` → `LauncherPanel` → `SearchBar`/`TextField`), currently a WIP skeleton (search box has no backing logic yet, `Launcher.qml` is instantiated with `visible: false`).
|
- `widgets/launcher/` — shared `AppModel` search/execution plus eight launcher variants. `ApplicationLauncher` (variant 8 and the primary `SUPER` launcher) keeps its visual core in the headlessly renderable `ApplicationLauncherContent`; variants 1–7 remain available on `SUPER CTRL 1–7` for comparison.
|
||||||
- `widgets/decoration/` — reusable QtQuick `Shape`-based visual accents (angled panel edges, slashes) used to give bar panels their non-rectangular look. `Dummy.qml` is a placeholder/test rectangle.
|
- `widgets/decoration/` — reusable QtQuick `Shape`-based visual accents (angled panel edges, slashes) used to give bar panels their non-rectangular look. `Dummy.qml` is a placeholder/test rectangle.
|
||||||
- `widgets/input/` — thin wrappers around `QtQuick.Controls` inputs (currently just `TextField`).
|
- `widgets/input/` — thin wrappers around `QtQuick.Controls` inputs (currently just `TextField`).
|
||||||
- `widgets/layout/` — `HorizontalStack`/`VerticalStack`: `RowLayout`/`ColumnLayout` wrappers that expose `default property alias content` for terser call sites, with a trailing filler `Item` that soaks up remaining space.
|
- `widgets/layout/` — `HorizontalStack`/`VerticalStack`: `RowLayout`/`ColumnLayout` wrappers that expose `default property alias content` for terser call sites, with a trailing filler `Item` that soaks up remaining space.
|
||||||
- `assets/` — SVG icons referenced via `file://${Quickshell.shellDir}/assets/...`.
|
- `assets/` — SVG icons referenced via `file://${Quickshell.shellDir}/assets/...`.
|
||||||
|
|
||||||
**Styling**: No shared theme/tokens file yet — colors (`#FFD063` accent, `#0F1012`/`#292C30` backgrounds, `#EEEEEE` text) and metrics are hardcoded per-component. When touching visuals, grep for the existing hex color across `widgets/` to keep new elements consistent rather than introducing new values.
|
**Styling**: All colors and font families come from the `Theme` singleton
|
||||||
|
(`widgets/theme/Theme.qml`, `import qs.widgets.theme`) — there are no color or
|
||||||
|
font literals left anywhere under `widgets/`. Add a token there rather than
|
||||||
|
hardcoding a value; alpha variants of the two main colors go through
|
||||||
|
`Theme.textAlpha(a)` / `Theme.accentAlpha(a)` instead of a hand-written
|
||||||
|
`Qt.rgba(...)`. Metrics (sizes, spacing) are still per-component.
|
||||||
|
|
||||||
**Component base pattern**: `BarWidget.qml` (`widgets/bar/modules/BarWidget.qml`) is a `WrapperMouseArea` + `WrapperRectangle` combo providing hover-triggered border highlight (`Behavior on border.color` animation) and `Layout.fillWidth`. Bar modules extend it via `default property alias content` rather than duplicating the hover/border chrome.
|
**Component base pattern**: `BarWidget.qml` (`widgets/bar/modules/BarWidget.qml`) is a `WrapperMouseArea` + `WrapperRectangle` combo providing hover-triggered border highlight (`Behavior on border.color` animation) and `Layout.fillWidth`. Bar modules extend it via `default property alias content` rather than duplicating the hover/border chrome.
|
||||||
|
|
||||||
**Fonts**: Clock/Date use the `Digital-7 Mono` font family — expected to be installed system-wide (see sibling `~/.dots/fonts/digital_7`), not bundled in this repo.
|
**Fonts**: Two families, both via `Theme`. `Theme.displayFont` / `Theme.readoutFont` (an alias of it) are `DepartureMono Nerd Font`, installed by `services/desktop/desktop-apps.nix`; `Theme.microFont` is `DejaVu Sans Mono`. The shell no longer uses `Digital-7 Mono`, which was never packaged and depended on a manual `~/.dots/fonts/digital_7` install.
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
hyprctl dispatch 'hl.dsp.global ("quickshell:launcher7")'
|
hyprctl dispatch 'hl.dsp.global ("quickshell:launcher8")'
|
||||||
|
|||||||
@@ -5,17 +5,14 @@ import qs.widgets.bar
|
|||||||
import qs.widgets.launcher
|
import qs.widgets.launcher
|
||||||
import qs.widgets.notifications
|
import qs.widgets.notifications
|
||||||
import qs.widgets.osd
|
import qs.widgets.osd
|
||||||
import qs.widgets.sidebar
|
|
||||||
import qs.widgets.systray
|
import qs.widgets.systray
|
||||||
import qs.widgets.vitals
|
import qs.widgets.vitals
|
||||||
|
|
||||||
Scope {
|
Scope {
|
||||||
// Left sidebar in the Slant (V6) style — toggle with SUPER CTRL S.
|
// Dense multi-monitor status rail; visual core is headlessly renderable.
|
||||||
SideBar {}
|
DenseBar {}
|
||||||
BarBottom {}
|
|
||||||
|
|
||||||
// App launcher variants — toggled via Hyprland global shortcuts
|
// App launcher variants — 1–11; variant 8 remains the primary HUD.
|
||||||
// (SUPER CTRL 1/2/3). Try each and keep the one you like.
|
|
||||||
LauncherStack {} // 1 — left vertical list
|
LauncherStack {} // 1 — left vertical list
|
||||||
LauncherGrid {} // 2 — centered icon grid
|
LauncherGrid {} // 2 — centered icon grid
|
||||||
LauncherSpotlight {} // 3 — top-center command bar
|
LauncherSpotlight {} // 3 — top-center command bar
|
||||||
@@ -23,6 +20,10 @@ Scope {
|
|||||||
LauncherDock {} // 5 — deck rising from the bottom bar
|
LauncherDock {} // 5 — deck rising from the bottom bar
|
||||||
LauncherSlant {} // 6 — angular / sheared panel
|
LauncherSlant {} // 6 — angular / sheared panel
|
||||||
LauncherCorner {} // 7 — Slant (V6) copy + floating power panel (shutdown/reboot)
|
LauncherCorner {} // 7 — Slant (V6) copy + floating power panel (shutdown/reboot)
|
||||||
|
ApplicationLauncher {} // 8 — dense HUD command index (primary)
|
||||||
|
BladeLauncher {} // 9 — asymmetric blade matrix
|
||||||
|
OrbitLauncher {} // 10 — radial targeting arena
|
||||||
|
CyberDock {} // 11 — cyberpunk bottom cartridge dock
|
||||||
|
|
||||||
Notifications {}
|
Notifications {}
|
||||||
VolumeOsd {}
|
VolumeOsd {}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.widgets.launcher
|
||||||
|
|
||||||
|
ApplicationLauncherContent {
|
||||||
|
width: 1080
|
||||||
|
height: 620
|
||||||
|
|
||||||
|
query: "term"
|
||||||
|
selectedIndex: 1
|
||||||
|
showIcons: false
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
name: "Alacritty",
|
||||||
|
genericName: "Terminal Emulator",
|
||||||
|
comment: "GPU accelerated command interface",
|
||||||
|
icon: "utilities-terminal",
|
||||||
|
categories: ["System", "TerminalEmulator"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ghostty",
|
||||||
|
genericName: "Terminal",
|
||||||
|
comment: "Fast native terminal for local operations",
|
||||||
|
icon: "com.mitchellh.ghostty",
|
||||||
|
categories: ["System", "TerminalEmulator"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Kitty",
|
||||||
|
genericName: "Terminal Emulator",
|
||||||
|
comment: "GPU based terminal with multiplexing",
|
||||||
|
icon: "kitty",
|
||||||
|
categories: ["System", "TerminalEmulator"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Vivaldi",
|
||||||
|
genericName: "Web Browser",
|
||||||
|
comment: "Access network and web applications",
|
||||||
|
icon: "vivaldi",
|
||||||
|
categories: ["Network", "WebBrowser"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Cosmic Files",
|
||||||
|
genericName: "File Manager",
|
||||||
|
comment: "Browse and manage local storage",
|
||||||
|
icon: "com.system76.CosmicFiles",
|
||||||
|
categories: ["System", "FileManager"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Obsidian",
|
||||||
|
genericName: "Knowledge Base",
|
||||||
|
comment: "Local-first markdown workspace",
|
||||||
|
icon: "obsidian",
|
||||||
|
categories: ["Office"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Steam",
|
||||||
|
genericName: "Game Platform",
|
||||||
|
comment: "Launch and manage games",
|
||||||
|
icon: "steam",
|
||||||
|
categories: ["Game"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.widgets.launcher
|
||||||
|
|
||||||
|
BladeLauncherContent {
|
||||||
|
width: 1120
|
||||||
|
height: 640
|
||||||
|
query: "dev"
|
||||||
|
selectedIndex: 2
|
||||||
|
showIcons: false
|
||||||
|
apps: [
|
||||||
|
{ name: "Visual Studio Code", genericName: "Code Editor", comment: "Edit and debug software projects", icon: "visual-studio-code", categories: ["Development"] },
|
||||||
|
{ name: "GitKraken", genericName: "Git Client", comment: "Inspect branches and repository history", icon: "gitkraken", categories: ["Development"] },
|
||||||
|
{ name: "OpenCode", genericName: "AI Development", comment: "Agentic terminal coding environment", icon: "utilities-terminal", categories: ["Development", "Utility"] },
|
||||||
|
{ name: "Alacritty", genericName: "Terminal Emulator", comment: "GPU accelerated command interface", icon: "utilities-terminal", categories: ["System"] },
|
||||||
|
{ name: "Vivaldi", genericName: "Web Browser", comment: "Access network and web applications", icon: "vivaldi", categories: ["Network"] },
|
||||||
|
{ name: "Obsidian", genericName: "Knowledge Base", comment: "Local-first markdown workspace", icon: "obsidian", categories: ["Office"] },
|
||||||
|
{ name: "Blender", genericName: "3D Creation", comment: "Model, animate and render 3D scenes", icon: "blender", categories: ["Graphics"] },
|
||||||
|
{ name: "Steam", genericName: "Game Platform", comment: "Launch and manage games", icon: "steam", categories: ["Game"] }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.widgets.launcher
|
||||||
|
|
||||||
|
CyberDockContent {
|
||||||
|
width: 1440
|
||||||
|
height: 272
|
||||||
|
query: ""
|
||||||
|
selectedIndex: 3
|
||||||
|
showIcons: false
|
||||||
|
apps: [
|
||||||
|
{ name: "Alacritty", genericName: "Terminal", comment: "GPU accelerated command interface", icon: "utilities-terminal" },
|
||||||
|
{ name: "Vivaldi", genericName: "Browser", comment: "Access web applications", icon: "vivaldi" },
|
||||||
|
{ name: "Obsidian", genericName: "Knowledge", comment: "Local-first markdown workspace", icon: "obsidian" },
|
||||||
|
{ name: "Cosmic Files", genericName: "Files", comment: "Browse local storage", icon: "com.system76.CosmicFiles" },
|
||||||
|
{ name: "Jellyfin", genericName: "Media", comment: "Stream the homelab library", icon: "jellyfin-media-player" },
|
||||||
|
{ name: "Spotify", genericName: "Music", comment: "Browse and play music", icon: "spotify" },
|
||||||
|
{ name: "Steam", genericName: "Games", comment: "Launch and manage games", icon: "steam" },
|
||||||
|
{ name: "Blender", genericName: "3D", comment: "Model and render scenes", icon: "blender" },
|
||||||
|
{ name: "Visual Studio Code", genericName: "Editor", comment: "Edit and debug projects", icon: "visual-studio-code" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.widgets.bar
|
||||||
|
|
||||||
|
DenseBarContent {
|
||||||
|
width: 1920
|
||||||
|
height: 164
|
||||||
|
|
||||||
|
now: new Date(2026, 7, 27, 21, 47, 0)
|
||||||
|
hostName: "TERRA"
|
||||||
|
telemetryReady: true
|
||||||
|
ratesReady: true
|
||||||
|
cpuFraction: 0.62
|
||||||
|
cpuThreads: 16
|
||||||
|
memoryFraction: 0.78
|
||||||
|
memoryUsedText: "24.9G"
|
||||||
|
temperatureCelsius: 54
|
||||||
|
networkInterface: "enp7s0"
|
||||||
|
networkRxText: "842.6M/S"
|
||||||
|
networkTxText: "116.2M/S"
|
||||||
|
storageFraction: 0.69
|
||||||
|
storageFreeText: "1.82T FREE"
|
||||||
|
workspaceIds: [1, 2, 3, 4]
|
||||||
|
activeWorkspaceId: 1
|
||||||
|
trayCount: 3
|
||||||
|
audioFraction: 0.50
|
||||||
|
audioMuted: false
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Shapes
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
implicitWidth: 720
|
||||||
|
implicitHeight: 120
|
||||||
|
|
||||||
|
readonly property color voidColor: "#0a0a0a"
|
||||||
|
readonly property color inkColor: "#dedede"
|
||||||
|
readonly property color mutedColor: "#858585"
|
||||||
|
readonly property color accentColor: "#e8722a"
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: root.voidColor
|
||||||
|
}
|
||||||
|
|
||||||
|
Shape {
|
||||||
|
anchors.fill: parent
|
||||||
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
|
||||||
|
ShapePath {
|
||||||
|
fillColor: root.voidColor
|
||||||
|
strokeColor: Qt.rgba(0.87, 0.87, 0.87, 0.28)
|
||||||
|
strokeWidth: 1
|
||||||
|
startX: 1
|
||||||
|
startY: 1
|
||||||
|
PathLine { x: root.width - 14; y: 1 }
|
||||||
|
PathLine { x: root.width - 1; y: 14 }
|
||||||
|
PathLine { x: root.width - 1; y: root.height - 9 }
|
||||||
|
PathLine { x: root.width - 9; y: root.height - 1 }
|
||||||
|
PathLine { x: 17; y: root.height - 1 }
|
||||||
|
PathLine { x: 1; y: root.height - 17 }
|
||||||
|
PathLine { x: 1; y: 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
ShapePath {
|
||||||
|
fillColor: root.accentColor
|
||||||
|
strokeWidth: 0
|
||||||
|
startX: 1
|
||||||
|
startY: 1
|
||||||
|
PathLine { x: 92; y: 1 }
|
||||||
|
PathLine { x: 92; y: 3 }
|
||||||
|
PathLine { x: 1; y: 3 }
|
||||||
|
PathLine { x: 1; y: 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
x: 14
|
||||||
|
y: 12
|
||||||
|
width: 29
|
||||||
|
height: 14
|
||||||
|
color: root.accentColor
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "001"
|
||||||
|
color: root.voidColor
|
||||||
|
font.pixelSize: 8
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
x: 52
|
||||||
|
y: 13
|
||||||
|
text: "HEADLESS RENDER ARRAY"
|
||||||
|
color: root.inkColor
|
||||||
|
font.family: "monospace"
|
||||||
|
font.pixelSize: 9
|
||||||
|
font.bold: true
|
||||||
|
font.letterSpacing: 1.4
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
x: 14
|
||||||
|
y: 34
|
||||||
|
width: root.width - 28
|
||||||
|
height: 1
|
||||||
|
color: root.inkColor
|
||||||
|
opacity: 0.18
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
x: 16
|
||||||
|
y: 48
|
||||||
|
text: "ORBITAL"
|
||||||
|
color: root.inkColor
|
||||||
|
font.family: "sans-serif-condensed"
|
||||||
|
font.pixelSize: 28
|
||||||
|
font.bold: true
|
||||||
|
font.letterSpacing: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
x: 18
|
||||||
|
y: 80
|
||||||
|
text: "QML // GRABTOIMAGE // SOFTWARE RHI"
|
||||||
|
color: root.accentColor
|
||||||
|
font.family: "monospace"
|
||||||
|
font.pixelSize: 7
|
||||||
|
font.letterSpacing: 1.4
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
x: 250
|
||||||
|
y: 56
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: 16
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
required property int index
|
||||||
|
width: 20
|
||||||
|
height: 12
|
||||||
|
color: index < 11 ? root.accentColor : Qt.rgba(0.87, 0.87, 0.87, 0.06)
|
||||||
|
border.width: 1
|
||||||
|
border.color: index < 11 ? root.accentColor : Qt.rgba(0.87, 0.87, 0.87, 0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
x: 250
|
||||||
|
y: 78
|
||||||
|
text: "RENDER PIPELINE"
|
||||||
|
color: root.mutedColor
|
||||||
|
font.family: "monospace"
|
||||||
|
font.pixelSize: 7
|
||||||
|
font.letterSpacing: 1.1
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
x: 585
|
||||||
|
y: 77
|
||||||
|
text: "68.75%"
|
||||||
|
color: root.inkColor
|
||||||
|
font.family: "monospace"
|
||||||
|
font.pixelSize: 15
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
x: 14
|
||||||
|
y: root.height - 9
|
||||||
|
width: root.width - 28
|
||||||
|
height: 3
|
||||||
|
color: root.accentColor
|
||||||
|
opacity: 0.6
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.widgets.launcher
|
||||||
|
|
||||||
|
OrbitLauncherContent {
|
||||||
|
width: 1120
|
||||||
|
height: 660
|
||||||
|
query: "media"
|
||||||
|
selectedIndex: 3
|
||||||
|
showIcons: false
|
||||||
|
apps: [
|
||||||
|
{ name: "Vivaldi", genericName: "Web Browser", comment: "Access network and web applications", icon: "vivaldi" },
|
||||||
|
{ name: "Obsidian", genericName: "Knowledge Base", comment: "Local-first markdown workspace", icon: "obsidian" },
|
||||||
|
{ name: "Blender", genericName: "3D Creation", comment: "Model, animate and render 3D scenes", icon: "blender" },
|
||||||
|
{ name: "Jellyfin Media Player", genericName: "Media Player", comment: "Stream media from the homelab library", icon: "jellyfin-media-player" },
|
||||||
|
{ name: "Spotify", genericName: "Music Player", comment: "Browse and play music", icon: "spotify" },
|
||||||
|
{ name: "Steam", genericName: "Game Platform", comment: "Launch and manage games", icon: "steam" },
|
||||||
|
{ name: "Cosmic Files", genericName: "File Manager", comment: "Browse and manage local storage", icon: "com.system76.CosmicFiles" },
|
||||||
|
{ name: "Alacritty", genericName: "Terminal Emulator", comment: "GPU accelerated command interface", icon: "utilities-terminal" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Widgets
|
|
||||||
import QtQuick
|
|
||||||
import QtQuick.Layouts
|
|
||||||
|
|
||||||
import qs.widgets.launcher
|
|
||||||
|
|
||||||
Scope {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
Variants {
|
|
||||||
model: Quickshell.screens
|
|
||||||
|
|
||||||
PanelWindow {
|
|
||||||
required property var modelData
|
|
||||||
screen: modelData
|
|
||||||
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
anchors {
|
|
||||||
bottom: true
|
|
||||||
left: true
|
|
||||||
right: true
|
|
||||||
}
|
|
||||||
|
|
||||||
implicitHeight: wrapper.implicitHeight
|
|
||||||
|
|
||||||
WrapperRectangle {
|
|
||||||
id: wrapper
|
|
||||||
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
leftMargin: 12
|
|
||||||
rightMargin: 12
|
|
||||||
bottomMargin: 12
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
implicitHeight: 12
|
|
||||||
|
|
||||||
// Brightens when the Dock launcher (variant 5) opens.
|
|
||||||
color: LauncherState.dockOpen ? "#FFF3C0" : "#FFD063"
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: 250
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gentle "listening" pulse while the dock is open.
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: "#FFFFFF"
|
|
||||||
opacity: 0
|
|
||||||
visible: LauncherState.dockOpen
|
|
||||||
|
|
||||||
SequentialAnimation on opacity {
|
|
||||||
running: LauncherState.dockOpen
|
|
||||||
loops: Animation.Infinite
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
to: 0.4
|
|
||||||
duration: 700
|
|
||||||
easing.type: Easing.InOutSine
|
|
||||||
}
|
|
||||||
NumberAnimation {
|
|
||||||
to: 0.0
|
|
||||||
duration: 700
|
|
||||||
easing.type: Easing.InOutSine
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Widgets
|
|
||||||
import QtQuick
|
|
||||||
|
|
||||||
import qs.widgets.launcher
|
|
||||||
|
|
||||||
Scope {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
Variants {
|
|
||||||
model: Quickshell.screens
|
|
||||||
|
|
||||||
PanelWindow {
|
|
||||||
id: topBar
|
|
||||||
|
|
||||||
required property var modelData
|
|
||||||
screen: modelData
|
|
||||||
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
anchors {
|
|
||||||
top: true
|
|
||||||
left: true
|
|
||||||
right: true
|
|
||||||
}
|
|
||||||
|
|
||||||
implicitHeight: wrapper.implicitHeight
|
|
||||||
|
|
||||||
WrapperRectangle {
|
|
||||||
id: wrapper
|
|
||||||
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
margin: 12
|
|
||||||
bottomMargin: 0
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
implicitHeight: 6
|
|
||||||
|
|
||||||
// Brightens when the Console launcher (variant 4) opens.
|
|
||||||
color: LauncherState.consoleOpen ? "#FFF3C0" : "#FFD063"
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: 250
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gentle "listening" pulse while the console is open.
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: "#FFFFFF"
|
|
||||||
opacity: 0
|
|
||||||
visible: LauncherState.consoleOpen
|
|
||||||
|
|
||||||
SequentialAnimation on opacity {
|
|
||||||
running: LauncherState.consoleOpen
|
|
||||||
loops: Animation.Infinite
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
to: 0.4
|
|
||||||
duration: 700
|
|
||||||
easing.type: Easing.InOutSine
|
|
||||||
}
|
|
||||||
NumberAnimation {
|
|
||||||
to: 0.0
|
|
||||||
duration: 700
|
|
||||||
easing.type: Easing.InOutSine
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Hyprland
|
||||||
|
import Quickshell.Services.Pipewire
|
||||||
|
import Quickshell.Services.SystemTray
|
||||||
|
import QtQuick
|
||||||
|
import qs.widgets.vitals
|
||||||
|
|
||||||
|
// Production/layer-shell adapter. All visual composition lives in the
|
||||||
|
// Item-rooted DenseBarContent so the exact bar can be rendered headlessly.
|
||||||
|
Scope {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property PwNode sink: Pipewire.defaultAudioSink
|
||||||
|
readonly property real volume: sink?.audio?.volume ?? 0
|
||||||
|
readonly property bool muted: sink?.audio?.muted ?? false
|
||||||
|
|
||||||
|
readonly property var workspaceIds: Hyprland.workspaces.values
|
||||||
|
.filter(workspace => workspace.id > 0)
|
||||||
|
.map(workspace => workspace.id)
|
||||||
|
readonly property int activeWorkspaceId: Hyprland.focusedWorkspace?.id ?? 1
|
||||||
|
readonly property var rootDisk: vitals.disks.length > 0 ? vitals.disks[0] : null
|
||||||
|
|
||||||
|
PwObjectTracker {
|
||||||
|
objects: [root.sink]
|
||||||
|
}
|
||||||
|
|
||||||
|
VitalsData {
|
||||||
|
id: vitals
|
||||||
|
active: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Variants {
|
||||||
|
model: Quickshell.screens
|
||||||
|
|
||||||
|
PanelWindow {
|
||||||
|
id: window
|
||||||
|
|
||||||
|
required property var modelData
|
||||||
|
screen: modelData
|
||||||
|
color: "transparent"
|
||||||
|
implicitHeight: 164
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: true
|
||||||
|
left: true
|
||||||
|
right: true
|
||||||
|
}
|
||||||
|
|
||||||
|
DenseBarContent {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
hostName: vitals.host || "LOCAL"
|
||||||
|
telemetryReady: vitals.ready && !vitals.failed
|
||||||
|
ratesReady: vitals.ratesReady && !vitals.failed
|
||||||
|
cpuFraction: vitals.cpu
|
||||||
|
cpuThreads: vitals.cpuThreads
|
||||||
|
memoryFraction: vitals.memTotal > 0 ? vitals.memUsed / vitals.memTotal : 0
|
||||||
|
memoryUsedText: vitals.fmtBytes(vitals.memUsed)
|
||||||
|
temperatureCelsius: vitals.cpuTemp
|
||||||
|
networkInterface: vitals.netIface || "NET"
|
||||||
|
networkRxText: vitals.fmtRate(vitals.netRx)
|
||||||
|
networkTxText: vitals.fmtRate(vitals.netTx)
|
||||||
|
storageFraction: root.rootDisk && root.rootDisk.size > 0
|
||||||
|
? root.rootDisk.used / root.rootDisk.size : 0
|
||||||
|
storageFreeText: root.rootDisk
|
||||||
|
? vitals.fmtBytes(root.rootDisk.size - root.rootDisk.used) + " FREE"
|
||||||
|
: "-- FREE"
|
||||||
|
workspaceIds: root.workspaceIds.length > 0 ? root.workspaceIds : [1, 2, 3, 4]
|
||||||
|
activeWorkspaceId: root.activeWorkspaceId
|
||||||
|
trayCount: SystemTray.items.values.length
|
||||||
|
audioFraction: Math.max(0, Math.min(1, root.volume))
|
||||||
|
audioMuted: root.muted
|
||||||
|
|
||||||
|
onWorkspaceActivated: workspaceId => {
|
||||||
|
const workspace = Hyprland.workspaces.values.find(item => item.id === workspaceId);
|
||||||
|
if (workspace)
|
||||||
|
workspace.activate();
|
||||||
|
else
|
||||||
|
Hyprland.dispatch("workspace " + workspaceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,675 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Shapes
|
||||||
|
import qs.widgets.theme
|
||||||
|
|
||||||
|
// Renderable visual core for the desktop telemetry rail. Runtime services stay
|
||||||
|
// in DenseBar.qml so this Item can be exercised without Wayland or Hyprland.
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
implicitWidth: 1920
|
||||||
|
implicitHeight: 164
|
||||||
|
|
||||||
|
readonly property bool compact: width <= 1400
|
||||||
|
|
||||||
|
property bool autoClock: true
|
||||||
|
property date now: new Date()
|
||||||
|
property string hostName: "LOCAL"
|
||||||
|
property bool telemetryReady: false
|
||||||
|
property bool ratesReady: false
|
||||||
|
property real cpuFraction: 0
|
||||||
|
property int cpuThreads: 0
|
||||||
|
property real memoryFraction: 0
|
||||||
|
property string memoryUsedText: "--"
|
||||||
|
property real temperatureCelsius: NaN
|
||||||
|
property string networkInterface: "NET"
|
||||||
|
property string networkRxText: "--"
|
||||||
|
property string networkTxText: "--"
|
||||||
|
property real storageFraction: 0
|
||||||
|
property string storageFreeText: "-- FREE"
|
||||||
|
property var workspaceIds: [1, 2, 3, 4]
|
||||||
|
property int activeWorkspaceId: 1
|
||||||
|
property int trayCount: 0
|
||||||
|
property real audioFraction: 0
|
||||||
|
property bool audioMuted: false
|
||||||
|
|
||||||
|
signal workspaceActivated(int workspaceId)
|
||||||
|
|
||||||
|
function pct(value, ready) {
|
||||||
|
return ready ? Math.round(Math.max(0, Math.min(1, value)) * 100) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function two(value) {
|
||||||
|
return value < 10 ? "0" + value : String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function timeText(value) {
|
||||||
|
return root.two(value.getHours()) + ":" + root.two(value.getMinutes());
|
||||||
|
}
|
||||||
|
|
||||||
|
function dateText(value) {
|
||||||
|
const days = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
|
||||||
|
const months = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
|
||||||
|
return days[value.getDay()] + " // " + root.two(value.getDate()) + " " + months[value.getMonth()];
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: 30000
|
||||||
|
running: root.autoClock
|
||||||
|
repeat: true
|
||||||
|
triggeredOnStart: true
|
||||||
|
onTriggered: root.now = new Date()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Faint drafting grid; no gradient and deliberately subordinate to data.
|
||||||
|
Repeater {
|
||||||
|
model: Math.ceil(root.width / 40)
|
||||||
|
Rectangle {
|
||||||
|
required property int index
|
||||||
|
x: index * 40
|
||||||
|
width: 1
|
||||||
|
height: root.height
|
||||||
|
color: Theme.text
|
||||||
|
opacity: 0.018
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: Math.ceil(root.height / 40)
|
||||||
|
Rectangle {
|
||||||
|
required property int index
|
||||||
|
y: index * 40
|
||||||
|
width: root.width
|
||||||
|
height: 1
|
||||||
|
color: Theme.text
|
||||||
|
opacity: 0.018
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: array
|
||||||
|
x: 12
|
||||||
|
y: 8
|
||||||
|
width: root.width - 24
|
||||||
|
height: 92
|
||||||
|
|
||||||
|
readonly property real identityWidth: root.compact ? 250 : 320
|
||||||
|
readonly property real stateWidth: root.compact ? 300 : 350
|
||||||
|
readonly property real flexWidth: Math.max(250, (width - identityWidth - stateWidth - 32) / 2)
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
StatusBarPanel {
|
||||||
|
width: array.identityWidth
|
||||||
|
height: array.height
|
||||||
|
panelId: "001"
|
||||||
|
title: "COMMAND LAYER"
|
||||||
|
|
||||||
|
Text {
|
||||||
|
x: 12
|
||||||
|
y: 34
|
||||||
|
text: root.hostName.toUpperCase()
|
||||||
|
color: Theme.text
|
||||||
|
font.family: Theme.displayFont
|
||||||
|
font.pixelSize: root.compact ? 20 : 25
|
||||||
|
font.bold: true
|
||||||
|
font.letterSpacing: 2
|
||||||
|
elide: Text.ElideRight
|
||||||
|
width: parent.width - 92
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
x: 13
|
||||||
|
y: 63
|
||||||
|
text: "STATUS ARRAY // " + (root.telemetryReady ? "LIVE" : "STANDBY")
|
||||||
|
color: Theme.accent
|
||||||
|
font.family: Theme.microFont
|
||||||
|
font.pixelSize: 7
|
||||||
|
font.letterSpacing: 1.4
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
x: parent.width - 75
|
||||||
|
y: 36
|
||||||
|
width: 1
|
||||||
|
height: 43
|
||||||
|
color: Theme.hair
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
x: parent.width - 62
|
||||||
|
y: 42
|
||||||
|
width: 5
|
||||||
|
height: 5
|
||||||
|
color: Theme.accent
|
||||||
|
opacity: root.telemetryReady ? 1 : 0.35
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
x: parent.width - 50
|
||||||
|
y: 38
|
||||||
|
text: root.telemetryReady ? "ONLINE" : "LOCAL"
|
||||||
|
color: Theme.accent
|
||||||
|
font.family: Theme.microFont
|
||||||
|
font.pixelSize: 7
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
x: parent.width - 63
|
||||||
|
y: 57
|
||||||
|
text: "NODE // " + (root.hostName || "--").toUpperCase().slice(0, 7)
|
||||||
|
color: Theme.muted
|
||||||
|
font.family: Theme.microFont
|
||||||
|
font.pixelSize: 6
|
||||||
|
}
|
||||||
|
|
||||||
|
Shape {
|
||||||
|
x: parent.width - 63
|
||||||
|
y: 71
|
||||||
|
width: 48
|
||||||
|
height: 10
|
||||||
|
ShapePath {
|
||||||
|
fillColor: "transparent"
|
||||||
|
strokeColor: Theme.accent
|
||||||
|
strokeWidth: 1
|
||||||
|
startX: 0; startY: 6
|
||||||
|
PathLine { x: 11; y: 6 }
|
||||||
|
PathLine { x: 16; y: 1 }
|
||||||
|
PathLine { x: 22; y: 9 }
|
||||||
|
PathLine { x: 27; y: 6 }
|
||||||
|
PathLine { x: 48; y: 6 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusBarPanel {
|
||||||
|
width: array.flexWidth
|
||||||
|
height: array.height
|
||||||
|
panelId: "02"
|
||||||
|
title: "RESOURCE LATTICE"
|
||||||
|
meta: root.telemetryReady ? "REALTIME" : "FALLBACK"
|
||||||
|
|
||||||
|
Row {
|
||||||
|
x: 11
|
||||||
|
y: 32
|
||||||
|
width: parent.width - 22
|
||||||
|
height: 51
|
||||||
|
spacing: root.compact ? 7 : 12
|
||||||
|
|
||||||
|
MetricBlock {
|
||||||
|
width: (parent.width - parent.spacing * (root.compact ? 1 : 2)) / (root.compact ? 2 : 3)
|
||||||
|
label: "CPU"
|
||||||
|
value: root.cpuFraction
|
||||||
|
ready: root.ratesReady
|
||||||
|
readout: root.ratesReady ? root.pct(root.cpuFraction, true) + "%" : "--"
|
||||||
|
detailLeft: "CORE:" + (root.cpuThreads || "--")
|
||||||
|
detailRight: root.ratesReady ? "BUSY" : "PRIME"
|
||||||
|
}
|
||||||
|
|
||||||
|
MetricBlock {
|
||||||
|
width: (parent.width - parent.spacing * (root.compact ? 1 : 2)) / (root.compact ? 2 : 3)
|
||||||
|
label: "MEM"
|
||||||
|
value: root.memoryFraction
|
||||||
|
ready: root.telemetryReady
|
||||||
|
readout: root.telemetryReady ? root.pct(root.memoryFraction, true) + "%" : "--"
|
||||||
|
detailLeft: "ALLOC"
|
||||||
|
detailRight: root.memoryUsedText
|
||||||
|
}
|
||||||
|
|
||||||
|
MetricBlock {
|
||||||
|
visible: !root.compact
|
||||||
|
width: (parent.width - parent.spacing * 2) / 3
|
||||||
|
label: "THERM"
|
||||||
|
value: isFinite(root.temperatureCelsius) ? root.temperatureCelsius / 100 : 0
|
||||||
|
ready: isFinite(root.temperatureCelsius)
|
||||||
|
readout: isFinite(root.temperatureCelsius) ? Math.round(root.temperatureCelsius) + "°C" : "--"
|
||||||
|
detailLeft: "ZONE:01"
|
||||||
|
detailRight: isFinite(root.temperatureCelsius) && root.temperatureCelsius >= 85 ? "HOT" : "NOMINAL"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusBarPanel {
|
||||||
|
width: array.flexWidth
|
||||||
|
height: array.height
|
||||||
|
panelId: "03"
|
||||||
|
title: "CARRIER UPLINK"
|
||||||
|
meta: root.networkInterface.toUpperCase()
|
||||||
|
|
||||||
|
NetworkTrace {
|
||||||
|
x: 12
|
||||||
|
y: 43
|
||||||
|
width: parent.width - (root.compact ? 117 : 145)
|
||||||
|
height: 33
|
||||||
|
level: root.ratesReady ? root.cpuFraction : 0.25
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 14
|
||||||
|
y: 34
|
||||||
|
width: root.compact ? 92 : 116
|
||||||
|
spacing: 1
|
||||||
|
|
||||||
|
Text {
|
||||||
|
width: parent.width
|
||||||
|
text: root.networkRxText
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
color: Theme.accent
|
||||||
|
font.family: Theme.displayFont
|
||||||
|
font.pixelSize: root.compact ? 11 : 13
|
||||||
|
font.bold: true
|
||||||
|
elide: Text.ElideLeft
|
||||||
|
}
|
||||||
|
MicroText { width: parent.width; text: "RX // DOWN"; horizontalAlignment: Text.AlignRight }
|
||||||
|
Text {
|
||||||
|
width: parent.width
|
||||||
|
text: root.networkTxText
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
color: Theme.text
|
||||||
|
font.family: Theme.displayFont
|
||||||
|
font.pixelSize: root.compact ? 11 : 13
|
||||||
|
font.bold: true
|
||||||
|
elide: Text.ElideLeft
|
||||||
|
}
|
||||||
|
MicroText { width: parent.width; text: "TX // UP"; horizontalAlignment: Text.AlignRight }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusBarPanel {
|
||||||
|
width: array.stateWidth
|
||||||
|
height: array.height
|
||||||
|
panelId: "04"
|
||||||
|
title: "SYSTEM STATE"
|
||||||
|
|
||||||
|
Row {
|
||||||
|
x: 10
|
||||||
|
y: 35
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
|
StateCell { code: "N"; active: root.ratesReady; label: "NET" }
|
||||||
|
StateCell { code: root.audioMuted ? "M" : "A"; active: !root.audioMuted; label: "AUD" }
|
||||||
|
StateCell { visible: !root.compact; code: String(root.trayCount); active: root.trayCount > 0; label: "TRAY" }
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
x: root.compact ? 101 : 151
|
||||||
|
y: 33
|
||||||
|
width: 1
|
||||||
|
height: 46
|
||||||
|
color: Theme.hair
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 12
|
||||||
|
y: 34
|
||||||
|
width: root.compact ? 91 : 82
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
Text {
|
||||||
|
width: parent.width
|
||||||
|
text: root.timeText(root.now)
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
color: Theme.text
|
||||||
|
font.family: Theme.displayFont
|
||||||
|
font.pixelSize: root.compact ? 20 : 22
|
||||||
|
font.bold: true
|
||||||
|
font.letterSpacing: 1
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
width: parent.width
|
||||||
|
text: root.dateText(root.now)
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
color: Theme.accent
|
||||||
|
font.family: Theme.microFont
|
||||||
|
font.pixelSize: 6
|
||||||
|
elide: Text.ElideLeft
|
||||||
|
}
|
||||||
|
MicroText { width: parent.width; text: "LOCAL TIME"; horizontalAlignment: Text.AlignRight }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusBarPanel {
|
||||||
|
id: rail
|
||||||
|
x: 12
|
||||||
|
y: 108
|
||||||
|
width: root.width - 24
|
||||||
|
height: 34
|
||||||
|
showHeader: false
|
||||||
|
chamfer: 10
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: 10
|
||||||
|
anchors.rightMargin: 10
|
||||||
|
|
||||||
|
RailSection {
|
||||||
|
width: root.compact ? 252 : 310
|
||||||
|
Text {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: "06 // DESKTOP"
|
||||||
|
color: Theme.accent
|
||||||
|
font.family: Theme.microFont
|
||||||
|
font.pixelSize: 7
|
||||||
|
font.letterSpacing: 1
|
||||||
|
}
|
||||||
|
Row {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 8
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 3
|
||||||
|
Repeater {
|
||||||
|
model: root.workspaceIds.slice(0, root.compact ? 4 : 6)
|
||||||
|
Rectangle {
|
||||||
|
required property var modelData
|
||||||
|
width: 24
|
||||||
|
height: 17
|
||||||
|
color: Number(modelData) === root.activeWorkspaceId ? Theme.accentAlpha(0.18) : "transparent"
|
||||||
|
border.width: 1
|
||||||
|
border.color: Number(modelData) === root.activeWorkspaceId ? Theme.accent : Theme.hair
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: root.two(Number(parent.modelData))
|
||||||
|
color: Number(parent.modelData) === root.activeWorkspaceId ? Theme.accent : Theme.muted
|
||||||
|
font.family: Theme.microFont
|
||||||
|
font.pixelSize: 7
|
||||||
|
}
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: root.workspaceActivated(Number(parent.modelData))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RailSection {
|
||||||
|
width: root.compact ? 260 : 410
|
||||||
|
MicroText { x: 9; anchors.verticalCenter: parent.verticalCenter; text: "PROCESS" }
|
||||||
|
Text {
|
||||||
|
x: 76
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: "COMPOSITOR // NODE_EXPORTER // SHELL"
|
||||||
|
color: root.telemetryReady ? Theme.text : Theme.muted
|
||||||
|
font.family: Theme.microFont
|
||||||
|
font.pixelSize: 7
|
||||||
|
elide: Text.ElideRight
|
||||||
|
width: parent.width - 84
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RailSection {
|
||||||
|
width: root.compact ? 300 : 385
|
||||||
|
MicroText { x: 9; anchors.verticalCenter: parent.verticalCenter; text: "VOL // ROOT" }
|
||||||
|
Text {
|
||||||
|
x: 93
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: root.storageFreeText
|
||||||
|
color: Theme.text
|
||||||
|
font.family: Theme.microFont
|
||||||
|
font.pixelSize: 8
|
||||||
|
}
|
||||||
|
SegmentMeter {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 10
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: root.compact ? 95 : 170
|
||||||
|
height: 7
|
||||||
|
segments: root.compact ? 10 : 16
|
||||||
|
value: root.storageFraction
|
||||||
|
ready: root.telemetryReady
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RailSection {
|
||||||
|
visible: !root.compact
|
||||||
|
width: 350
|
||||||
|
MicroText { x: 9; anchors.verticalCenter: parent.verticalCenter; text: "AUDIO BUS" }
|
||||||
|
Text {
|
||||||
|
x: 89
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: root.audioMuted ? "MUTED" : Math.round(root.audioFraction * 100) + "%"
|
||||||
|
color: root.audioMuted ? Theme.muted : Theme.accent
|
||||||
|
font.family: Theme.microFont
|
||||||
|
font.pixelSize: 8
|
||||||
|
}
|
||||||
|
SegmentMeter {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 10
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: 155
|
||||||
|
height: 7
|
||||||
|
segments: 16
|
||||||
|
value: root.audioFraction
|
||||||
|
ready: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RailSection {
|
||||||
|
width: Math.max(150, rail.width - (root.compact ? 812 : 1455) - 20)
|
||||||
|
borderVisible: false
|
||||||
|
MicroText { x: 9; anchors.verticalCenter: parent.verticalCenter; text: root.compact ? "SYS // " + root.timeText(root.now) : "EVENTS" }
|
||||||
|
Text {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 8
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: root.telemetryReady ? "WARN:00 // ERR:00" : "TELEMETRY WAIT"
|
||||||
|
color: root.telemetryReady ? Theme.muted : Theme.accent
|
||||||
|
font.family: Theme.microFont
|
||||||
|
font.pixelSize: 7
|
||||||
|
elide: Text.ElideLeft
|
||||||
|
width: parent.width - 64
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bottom routing trace and caution hatch.
|
||||||
|
Shape {
|
||||||
|
x: 12
|
||||||
|
y: 150
|
||||||
|
width: root.width - 24
|
||||||
|
height: 12
|
||||||
|
ShapePath {
|
||||||
|
fillColor: "transparent"
|
||||||
|
strokeColor: Theme.accentAlpha(0.55)
|
||||||
|
strokeWidth: 1
|
||||||
|
startX: 0; startY: 1
|
||||||
|
PathLine { x: 220; y: 1 }
|
||||||
|
PathLine { x: 232; y: 11 }
|
||||||
|
PathLine { x: 330; y: 11 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
x: 360
|
||||||
|
y: 151
|
||||||
|
width: root.width - 372
|
||||||
|
height: 3
|
||||||
|
spacing: 5
|
||||||
|
clip: true
|
||||||
|
Repeater {
|
||||||
|
model: Math.ceil(parent.width / 10)
|
||||||
|
Rectangle {
|
||||||
|
required property int index
|
||||||
|
width: 5
|
||||||
|
height: 3
|
||||||
|
color: index % 2 === 0 ? Theme.accent : "transparent"
|
||||||
|
opacity: 0.58
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component MicroText: Text {
|
||||||
|
color: Theme.muted
|
||||||
|
font.family: Theme.microFont
|
||||||
|
font.pixelSize: 6
|
||||||
|
font.letterSpacing: 0.7
|
||||||
|
}
|
||||||
|
|
||||||
|
component RailSection: Item {
|
||||||
|
property bool borderVisible: true
|
||||||
|
height: rail.height
|
||||||
|
Rectangle {
|
||||||
|
visible: parent.borderVisible
|
||||||
|
anchors.right: parent.right
|
||||||
|
width: 1
|
||||||
|
height: parent.height
|
||||||
|
color: Theme.hair
|
||||||
|
opacity: 0.65
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component MetricBlock: Item {
|
||||||
|
id: metric
|
||||||
|
property string label: ""
|
||||||
|
property real value: 0
|
||||||
|
property bool ready: false
|
||||||
|
property string readout: "--"
|
||||||
|
property string detailLeft: ""
|
||||||
|
property string detailRight: ""
|
||||||
|
height: 51
|
||||||
|
|
||||||
|
MicroText { x: 0; y: 1; text: metric.label; font.pixelSize: 7 }
|
||||||
|
Text {
|
||||||
|
anchors.right: parent.right
|
||||||
|
y: -3
|
||||||
|
text: metric.readout
|
||||||
|
color: Theme.text
|
||||||
|
font.family: Theme.displayFont
|
||||||
|
font.pixelSize: root.compact ? 14 : 17
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
SegmentMeter {
|
||||||
|
x: 0; y: 19
|
||||||
|
width: parent.width
|
||||||
|
height: 9
|
||||||
|
segments: 10
|
||||||
|
value: metric.value
|
||||||
|
ready: metric.ready
|
||||||
|
}
|
||||||
|
MicroText { x: 0; y: 34; text: metric.detailLeft }
|
||||||
|
MicroText { anchors.right: parent.right; y: 34; text: metric.detailRight; horizontalAlignment: Text.AlignRight }
|
||||||
|
}
|
||||||
|
|
||||||
|
component SegmentMeter: Row {
|
||||||
|
id: meter
|
||||||
|
property int segments: 10
|
||||||
|
property real value: 0
|
||||||
|
property bool ready: false
|
||||||
|
spacing: 2
|
||||||
|
Repeater {
|
||||||
|
model: meter.segments
|
||||||
|
Rectangle {
|
||||||
|
required property int index
|
||||||
|
width: Math.max(2, (meter.width - (meter.segments - 1) * meter.spacing) / meter.segments)
|
||||||
|
height: meter.height
|
||||||
|
readonly property bool on: meter.ready && index < Math.round(Math.max(0, Math.min(1, meter.value)) * meter.segments)
|
||||||
|
color: on ? Theme.accent : Theme.textAlpha(0.06)
|
||||||
|
border.width: 1
|
||||||
|
border.color: on ? Theme.accent : Theme.textAlpha(0.18)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component StateCell: Item {
|
||||||
|
id: state
|
||||||
|
property string code: "--"
|
||||||
|
property string label: ""
|
||||||
|
property bool active: false
|
||||||
|
width: root.compact ? 39 : 43
|
||||||
|
height: 42
|
||||||
|
|
||||||
|
Shape {
|
||||||
|
anchors.fill: parent
|
||||||
|
ShapePath {
|
||||||
|
fillColor: state.active ? Theme.accentAlpha(0.18) : "transparent"
|
||||||
|
strokeColor: state.active ? Theme.accent : Theme.hair
|
||||||
|
strokeWidth: 1
|
||||||
|
startX: 1; startY: 1
|
||||||
|
PathLine { x: parent.width - 7; y: 1 }
|
||||||
|
PathLine { x: parent.width - 1; y: 7 }
|
||||||
|
PathLine { x: parent.width - 1; y: parent.height - 1 }
|
||||||
|
PathLine { x: 7; y: parent.height - 1 }
|
||||||
|
PathLine { x: 1; y: parent.height - 7 }
|
||||||
|
PathLine { x: 1; y: 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
y: 7
|
||||||
|
text: state.code
|
||||||
|
color: state.active ? Theme.accent : Theme.text
|
||||||
|
font.family: Theme.displayFont
|
||||||
|
font.pixelSize: 13
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
MicroText { anchors.horizontalCenter: parent.horizontalCenter; y: 27; text: state.label }
|
||||||
|
}
|
||||||
|
|
||||||
|
component RadarGauge: Item {
|
||||||
|
id: radar
|
||||||
|
property real size: 58
|
||||||
|
property real level: 0
|
||||||
|
width: size
|
||||||
|
height: size
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: [1, 0.72, 0.36]
|
||||||
|
Rectangle {
|
||||||
|
required property int index
|
||||||
|
required property var modelData
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: radar.size * Number(modelData)
|
||||||
|
height: width
|
||||||
|
radius: width / 2
|
||||||
|
color: "transparent"
|
||||||
|
border.width: 1
|
||||||
|
border.color: index === 0 ? Theme.accent : Theme.hair
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Rectangle { x: 0; y: parent.height / 2; width: parent.width; height: 1; color: Theme.hair }
|
||||||
|
Rectangle { x: parent.width / 2; y: 0; width: 1; height: parent.height; color: Theme.hair }
|
||||||
|
Rectangle { x: 12; y: 18; width: 4; height: 4; color: Theme.accent }
|
||||||
|
Rectangle { x: parent.width - 14; y: parent.height - 20; width: 4; height: 4; color: Theme.accent }
|
||||||
|
Rectangle { x: parent.width / 2; y: parent.height - 11; width: 4; height: 4; color: Theme.accent }
|
||||||
|
MicroText { anchors.right: parent.right; y: 3; text: "R:" + Math.round(radar.level * 99); color: Theme.accent }
|
||||||
|
}
|
||||||
|
|
||||||
|
component NetworkTrace: Item {
|
||||||
|
id: trace
|
||||||
|
property real level: 0
|
||||||
|
Rectangle { x: 0; y: parent.height - 1; width: parent.width; height: 1; color: Theme.hair }
|
||||||
|
Rectangle { x: 0; y: 0; width: 1; height: parent.height; color: Theme.hair }
|
||||||
|
Shape {
|
||||||
|
anchors.fill: parent
|
||||||
|
ShapePath {
|
||||||
|
fillColor: "transparent"
|
||||||
|
strokeColor: Theme.accent
|
||||||
|
strokeWidth: 1.2
|
||||||
|
startX: 0; startY: trace.height * 0.65
|
||||||
|
PathLine { x: trace.width * 0.10; y: trace.height * 0.65 }
|
||||||
|
PathLine { x: trace.width * 0.15; y: trace.height * (0.25 + trace.level * 0.15) }
|
||||||
|
PathLine { x: trace.width * 0.21; y: trace.height * 0.78 }
|
||||||
|
PathLine { x: trace.width * 0.31; y: trace.height * 0.56 }
|
||||||
|
PathLine { x: trace.width * 0.43; y: trace.height * 0.62 }
|
||||||
|
PathLine { x: trace.width * 0.49; y: trace.height * 0.18 }
|
||||||
|
PathLine { x: trace.width * 0.57; y: trace.height * 0.82 }
|
||||||
|
PathLine { x: trace.width * 0.68; y: trace.height * 0.58 }
|
||||||
|
PathLine { x: trace.width * 0.79; y: trace.height * 0.62 }
|
||||||
|
PathLine { x: trace.width * 0.85; y: trace.height * 0.34 }
|
||||||
|
PathLine { x: trace.width * 0.91; y: trace.height * 0.75 }
|
||||||
|
PathLine { x: trace.width; y: trace.height * 0.60 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Shapes
|
||||||
|
import qs.widgets.theme
|
||||||
|
|
||||||
|
// Chamfered panel chrome for the dense status rail: outline, corner accent
|
||||||
|
// lines, and the optional header strip (id chip / title / meta / tick marks).
|
||||||
|
// Content is supplied as children by the call site.
|
||||||
|
//
|
||||||
|
// Colors and fonts both come from the Theme singleton.
|
||||||
|
Item {
|
||||||
|
id: panel
|
||||||
|
|
||||||
|
property string panelId: ""
|
||||||
|
property string title: ""
|
||||||
|
property string meta: ""
|
||||||
|
property bool showHeader: true
|
||||||
|
property int chamfer: 13
|
||||||
|
property int offsetY: 2
|
||||||
|
property int accentLineThickness: 3
|
||||||
|
|
||||||
|
Shape {
|
||||||
|
id: panelShape
|
||||||
|
anchors.fill: parent
|
||||||
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
|
||||||
|
ShapePath {
|
||||||
|
fillColor: Theme.surface
|
||||||
|
strokeColor: Theme.hair
|
||||||
|
strokeWidth: 1
|
||||||
|
startX: 0; startY: panel.offsetY
|
||||||
|
PathLine { x: panelShape.width - panel.chamfer; y: panel.offsetY }
|
||||||
|
PathLine { x: panelShape.width; y: panel.chamfer }
|
||||||
|
PathLine { x: panelShape.width; y: panelShape.height }
|
||||||
|
PathLine { x: panel.chamfer; y: panelShape.height }
|
||||||
|
PathLine { x: 0; y: panelShape.height - panel.chamfer }
|
||||||
|
PathLine { x: 0; y: panel.offsetY }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upper left accent line
|
||||||
|
ShapePath {
|
||||||
|
fillColor: Theme.accent
|
||||||
|
strokeWidth: 0
|
||||||
|
startX: 0; startY: 0
|
||||||
|
PathLine { x: Math.min(49, panelShape.width / 3); y: 0 }
|
||||||
|
PathLine { x: Math.min(49, panelShape.width / 3); y: panel.accentLineThickness }
|
||||||
|
PathLine { x: 0; y: panel.accentLineThickness }
|
||||||
|
PathLine { x: 0; y: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lower right accent line
|
||||||
|
ShapePath {
|
||||||
|
fillColor: Theme.accent
|
||||||
|
strokeWidth: 0
|
||||||
|
startX: panelShape.width; startY: panelShape.height
|
||||||
|
PathLine { x: panelShape.width - Math.min(49, panelShape.width / 3); y: panelShape.height }
|
||||||
|
PathLine { x: panelShape.width - Math.min(49, panelShape.width / 3); y: panelShape.height - panel.accentLineThickness }
|
||||||
|
PathLine { x: panelShape.width; y: panelShape.height - panel.accentLineThickness }
|
||||||
|
PathLine { x: panelShape.width; y: panelShape.height }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: panel.showHeader
|
||||||
|
x: 1; y: 22
|
||||||
|
width: parent.width - 2
|
||||||
|
height: 1
|
||||||
|
color: Theme.text
|
||||||
|
opacity: 0.12
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: panel.showHeader
|
||||||
|
x: 5; y: 7
|
||||||
|
width: panel.panelId.length > 2 ? 29 : 24
|
||||||
|
height: 11
|
||||||
|
color: Theme.accent
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: panel.panelId
|
||||||
|
color: Theme.surface
|
||||||
|
font.family: Theme.microFont
|
||||||
|
font.pixelSize: 8
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
visible: panel.showHeader
|
||||||
|
x: 40; y: 7
|
||||||
|
width: parent.width - 105
|
||||||
|
text: panel.title
|
||||||
|
color: Theme.text
|
||||||
|
font.family: Theme.displayFont
|
||||||
|
font.pixelSize: 9
|
||||||
|
font.bold: true
|
||||||
|
font.letterSpacing: 1.1
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inlined rather than reusing DenseBarContent's MicroText, which is an
|
||||||
|
// inline component and therefore not visible from another file.
|
||||||
|
Text {
|
||||||
|
visible: panel.showHeader && panel.meta.length > 0
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 12
|
||||||
|
y: 6
|
||||||
|
text: panel.meta
|
||||||
|
width: Math.min(80, parent.width / 4)
|
||||||
|
color: Theme.muted
|
||||||
|
font.family: Theme.microFont
|
||||||
|
font.pixelSize: 6
|
||||||
|
font.letterSpacing: 0.7
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
visible: panel.showHeader
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 10
|
||||||
|
y: 14
|
||||||
|
spacing: 2
|
||||||
|
Repeater {
|
||||||
|
model: 5
|
||||||
|
Rectangle { required property int index; width: 4; height: 2; color: Theme.accent }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Shapes
|
import QtQuick.Shapes
|
||||||
|
import qs.widgets.theme
|
||||||
|
|
||||||
Shape {
|
Shape {
|
||||||
preferredRendererType: Shape.CurveRenderer
|
preferredRendererType: Shape.CurveRenderer
|
||||||
ShapePath {
|
ShapePath {
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
|
|
||||||
startX: 0
|
startX: 0
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import QtQuick.Layouts
|
|||||||
import QtQuick.Shapes
|
import QtQuick.Shapes
|
||||||
|
|
||||||
import qs.widgets.decoration
|
import qs.widgets.decoration
|
||||||
|
import qs.widgets.theme
|
||||||
|
|
||||||
WrapperItem {
|
WrapperItem {
|
||||||
RowLayout {
|
RowLayout {
|
||||||
@@ -24,7 +25,7 @@ WrapperItem {
|
|||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#0F1012"
|
fillColor: Theme.surface
|
||||||
|
|
||||||
startX: 8
|
startX: 8
|
||||||
startY: 0
|
startY: 0
|
||||||
@@ -52,7 +53,7 @@ WrapperItem {
|
|||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#0F1012"
|
fillColor: Theme.surface
|
||||||
|
|
||||||
startX: 16
|
startX: 16
|
||||||
startY: 0
|
startY: 0
|
||||||
@@ -83,7 +84,7 @@ WrapperItem {
|
|||||||
startY: 0
|
startY: 0
|
||||||
|
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#0F1012"
|
fillColor: Theme.surface
|
||||||
|
|
||||||
PathLine {
|
PathLine {
|
||||||
x: shape.width
|
x: shape.width
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Hyprland
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import QtQuick
|
||||||
|
import qs.widgets.theme
|
||||||
|
|
||||||
|
// Primary application launcher (variant 8). The full-screen layer-shell adapter
|
||||||
|
// owns focus, DesktopEntries and execution; ApplicationLauncherContent remains
|
||||||
|
// an Item so the complete visual state can be rendered headlessly.
|
||||||
|
Scope {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool active: false
|
||||||
|
|
||||||
|
function toggle() { root.active = !root.active; }
|
||||||
|
|
||||||
|
GlobalShortcut {
|
||||||
|
name: "launcher8"
|
||||||
|
description: "Toggle dense application command index"
|
||||||
|
onPressed: root.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
PanelWindow {
|
||||||
|
id: win
|
||||||
|
|
||||||
|
visible: root.active
|
||||||
|
WlrLayershell.layer: WlrLayer.Overlay
|
||||||
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||||
|
exclusionMode: ExclusionMode.Ignore
|
||||||
|
color: Theme.textAlpha(0)
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: true
|
||||||
|
left: true
|
||||||
|
right: true
|
||||||
|
bottom: true
|
||||||
|
}
|
||||||
|
|
||||||
|
property int selectedIndex: 0
|
||||||
|
|
||||||
|
function clampSelection(index) {
|
||||||
|
if (model.apps.length === 0)
|
||||||
|
return 0;
|
||||||
|
return Math.max(0, Math.min(model.apps.length - 1, index));
|
||||||
|
}
|
||||||
|
|
||||||
|
function move(delta) {
|
||||||
|
const count = model.apps.length;
|
||||||
|
if (count === 0)
|
||||||
|
return;
|
||||||
|
selectedIndex = ((selectedIndex + delta) % count + count) % count;
|
||||||
|
}
|
||||||
|
|
||||||
|
function launch(index) {
|
||||||
|
if (model.launch(index))
|
||||||
|
root.active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (visible) {
|
||||||
|
content.clearSearch();
|
||||||
|
selectedIndex = 0;
|
||||||
|
content.focusSearch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AppModel {
|
||||||
|
id: model
|
||||||
|
search: content.query
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Theme.surface
|
||||||
|
opacity: 0.72
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: root.active = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationLauncherContent {
|
||||||
|
id: content
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 1080
|
||||||
|
height: 620
|
||||||
|
scale: Math.min(1, (parent.width - 64) / width, (parent.height - 64) / height)
|
||||||
|
transformOrigin: Item.Center
|
||||||
|
apps: model.apps
|
||||||
|
selectedIndex: win.selectedIndex
|
||||||
|
|
||||||
|
onSelectionRequested: index => win.selectedIndex = win.clampSelection(index)
|
||||||
|
onMoveRequested: delta => win.move(delta)
|
||||||
|
onLaunchRequested: index => win.launch(index)
|
||||||
|
onDismissRequested: root.active = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Hyprland
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import QtQuick
|
||||||
|
import qs.widgets.theme
|
||||||
|
|
||||||
|
// Variant 9 — asymmetric Blade application launcher.
|
||||||
|
Scope {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool active: false
|
||||||
|
function toggle() { root.active = !root.active; }
|
||||||
|
|
||||||
|
GlobalShortcut {
|
||||||
|
name: "launcher9"
|
||||||
|
description: "Toggle app launcher (Blade matrix)"
|
||||||
|
onPressed: root.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
PanelWindow {
|
||||||
|
id: win
|
||||||
|
visible: root.active
|
||||||
|
WlrLayershell.layer: WlrLayer.Overlay
|
||||||
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||||
|
exclusionMode: ExclusionMode.Ignore
|
||||||
|
color: Theme.textAlpha(0)
|
||||||
|
|
||||||
|
anchors { top: true; left: true; right: true; bottom: true }
|
||||||
|
|
||||||
|
property int selectedIndex: 0
|
||||||
|
|
||||||
|
function clampSelection(index) {
|
||||||
|
return model.apps.length === 0 ? 0 : Math.max(0, Math.min(model.apps.length - 1, index));
|
||||||
|
}
|
||||||
|
function move(delta) {
|
||||||
|
const count = model.apps.length;
|
||||||
|
if (count === 0)
|
||||||
|
return;
|
||||||
|
selectedIndex = ((selectedIndex + delta) % count + count) % count;
|
||||||
|
}
|
||||||
|
function launch(index) {
|
||||||
|
if (model.launch(index))
|
||||||
|
root.active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (visible) {
|
||||||
|
content.clearSearch();
|
||||||
|
selectedIndex = 0;
|
||||||
|
content.focusSearch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AppModel { id: model; search: content.query }
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Theme.surface
|
||||||
|
opacity: 0.78
|
||||||
|
MouseArea { anchors.fill: parent; onClicked: root.active = false }
|
||||||
|
}
|
||||||
|
|
||||||
|
BladeLauncherContent {
|
||||||
|
id: content
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 1120
|
||||||
|
height: 640
|
||||||
|
scale: Math.min(1, (parent.width - 56) / width, (parent.height - 56) / height)
|
||||||
|
transformOrigin: Item.Center
|
||||||
|
apps: model.apps
|
||||||
|
selectedIndex: win.selectedIndex
|
||||||
|
onSelectionRequested: index => win.selectedIndex = win.clampSelection(index)
|
||||||
|
onMoveRequested: delta => win.move(delta)
|
||||||
|
onLaunchRequested: index => win.launch(index)
|
||||||
|
onDismissRequested: root.active = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,473 @@
|
|||||||
|
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" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Hyprland
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import QtQuick
|
||||||
|
import qs.widgets.theme
|
||||||
|
|
||||||
|
// Variant 11 — cyberpunk bottom dock inspired by V5's rising carousel.
|
||||||
|
Scope {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool active: false
|
||||||
|
function toggle() { root.active = !root.active; }
|
||||||
|
|
||||||
|
onActiveChanged: {
|
||||||
|
LauncherState.dockOpen = root.active;
|
||||||
|
if (root.active)
|
||||||
|
win.prepareOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalShortcut {
|
||||||
|
name: "launcher11"
|
||||||
|
description: "Toggle app launcher (Cyber Dock)"
|
||||||
|
onPressed: root.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
PanelWindow {
|
||||||
|
id: win
|
||||||
|
visible: root.active || deck.y < height
|
||||||
|
WlrLayershell.layer: WlrLayer.Overlay
|
||||||
|
WlrLayershell.keyboardFocus: root.active ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
||||||
|
exclusionMode: ExclusionMode.Ignore
|
||||||
|
color: Theme.textAlpha(0)
|
||||||
|
|
||||||
|
anchors { bottom: true; left: true; right: true }
|
||||||
|
margins { bottom: 10; left: 12; right: 12 }
|
||||||
|
implicitHeight: 290
|
||||||
|
|
||||||
|
property int selectedIndex: 0
|
||||||
|
|
||||||
|
function prepareOpen() {
|
||||||
|
deck.clearSearch();
|
||||||
|
selectedIndex = 0;
|
||||||
|
deck.focusSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clampSelection(index) {
|
||||||
|
return model.apps.length === 0 ? 0 : Math.max(0, Math.min(model.apps.length - 1, index));
|
||||||
|
}
|
||||||
|
function move(delta) {
|
||||||
|
const count = model.apps.length;
|
||||||
|
if (count === 0)
|
||||||
|
return;
|
||||||
|
selectedIndex = ((selectedIndex + delta) % count + count) % count;
|
||||||
|
}
|
||||||
|
function launch(index) {
|
||||||
|
if (model.launch(index))
|
||||||
|
root.active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppModel { id: model; search: deck.query }
|
||||||
|
|
||||||
|
CyberDockContent {
|
||||||
|
id: deck
|
||||||
|
width: 1440
|
||||||
|
height: 272
|
||||||
|
x: (parent.width - width) / 2
|
||||||
|
y: root.active ? parent.height - height : parent.height + 4
|
||||||
|
scale: Math.min(1, (parent.width - 16) / width)
|
||||||
|
transformOrigin: Item.Bottom
|
||||||
|
apps: model.apps
|
||||||
|
selectedIndex: win.selectedIndex
|
||||||
|
|
||||||
|
Behavior on y {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 240
|
||||||
|
easing.type: root.active ? Easing.OutCubic : Easing.InCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectionRequested: index => win.selectedIndex = win.clampSelection(index)
|
||||||
|
onMoveRequested: delta => win.move(delta)
|
||||||
|
onLaunchRequested: index => win.launch(index)
|
||||||
|
onDismissRequested: root.active = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,300 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Widgets
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import QtQuick.Shapes
|
||||||
|
import qs.widgets.theme
|
||||||
|
|
||||||
|
// Headlessly renderable visual core for variant 11. Inspired by V5's bottom
|
||||||
|
// deck and horizontal carousel, but owns a denser industrial cyberpunk chassis.
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var apps: []
|
||||||
|
property int selectedIndex: 0
|
||||||
|
property bool showIcons: true
|
||||||
|
property alias query: dockInput.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() { dockInput.forceActiveFocus(); }
|
||||||
|
function clearSearch() { dockInput.text = ""; }
|
||||||
|
|
||||||
|
implicitWidth: 1440
|
||||||
|
implicitHeight: 272
|
||||||
|
|
||||||
|
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.textAlpha(0) }
|
||||||
|
|
||||||
|
// Main dock chassis with clipped upper corners and heavier lower edge.
|
||||||
|
Shape {
|
||||||
|
id: chassis
|
||||||
|
anchors.fill: parent
|
||||||
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
|
||||||
|
ShapePath {
|
||||||
|
fillColor: Theme.surface
|
||||||
|
strokeColor: Theme.hair
|
||||||
|
strokeWidth: 1
|
||||||
|
startX: 28; startY: 0
|
||||||
|
PathLine { x: chassis.width - 28; y: 0 }
|
||||||
|
PathLine { x: chassis.width; y: 28 }
|
||||||
|
PathLine { x: chassis.width; y: chassis.height }
|
||||||
|
PathLine { x: 0; y: chassis.height }
|
||||||
|
PathLine { x: 0; y: 28 }
|
||||||
|
PathLine { x: 28; y: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
ShapePath {
|
||||||
|
fillColor: Theme.accent
|
||||||
|
strokeWidth: 0
|
||||||
|
startX: 28; startY: 0
|
||||||
|
PathLine { x: 250; y: 0 }
|
||||||
|
PathLine { x: 244; y: 4 }
|
||||||
|
PathLine { x: 32; y: 4 }
|
||||||
|
PathLine { x: 4; y: 32 }
|
||||||
|
PathLine { x: 4; y: 96 }
|
||||||
|
PathLine { x: 0; y: 96 }
|
||||||
|
PathLine { x: 0; y: 28 }
|
||||||
|
PathLine { x: 28; y: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
ShapePath {
|
||||||
|
fillColor: Theme.accent
|
||||||
|
strokeWidth: 0
|
||||||
|
startX: 0; startY: chassis.height - 6
|
||||||
|
PathLine { x: chassis.width; y: chassis.height - 6 }
|
||||||
|
PathLine { x: chassis.width; y: chassis.height }
|
||||||
|
PathLine { x: 0; y: chassis.height }
|
||||||
|
PathLine { x: 0; y: chassis.height - 6 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Top caution seam.
|
||||||
|
Row {
|
||||||
|
x: 270; y: 1; spacing: 5
|
||||||
|
Repeater {
|
||||||
|
model: 58
|
||||||
|
Rectangle {
|
||||||
|
required property int index
|
||||||
|
width: 10; height: 3
|
||||||
|
color: index % 4 === 0 ? Theme.accent : Theme.hair
|
||||||
|
transform: Rotation { angle: -32 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Left system coupler.
|
||||||
|
Item {
|
||||||
|
id: coupler
|
||||||
|
x: 18; y: 18
|
||||||
|
width: 156; height: 184
|
||||||
|
|
||||||
|
Rectangle { anchors.centerIn: parent; width: 112; height: 112; radius: 56; color: Theme.textAlpha(0.012); border.width: 1; border.color: Theme.accentAlpha(0.55) }
|
||||||
|
Rectangle { anchors.centerIn: parent; width: 78; height: 78; radius: 39; color: Theme.textAlpha(0); border.width: 1; border.color: Theme.hair }
|
||||||
|
Rectangle { anchors.centerIn: parent; width: 118; height: 1; color: Theme.hair }
|
||||||
|
Rectangle { anchors.centerIn: parent; width: 1; height: 118; color: Theme.hair }
|
||||||
|
Rectangle { anchors.centerIn: parent; width: 38; height: 38; color: Theme.accentAlpha(0.16); border.width: 1; border.color: Theme.accent; transform: Rotation { angle: 45 } }
|
||||||
|
Text { anchors.centerIn: parent; text: "11"; color: Theme.accent; font.family: Theme.displayFont; font.pixelSize: 13; font.bold: true }
|
||||||
|
MicroText { anchors.horizontalCenter: parent.horizontalCenter; anchors.bottom: parent.bottom; anchors.bottomMargin: 15; text: "DOCK BUS"; color: Theme.accent }
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
x: 24; y: 16
|
||||||
|
text: "CYBER//DOCK"
|
||||||
|
color: Theme.text
|
||||||
|
font.family: Theme.displayFont
|
||||||
|
font.pixelSize: 9
|
||||||
|
font.bold: true
|
||||||
|
font.letterSpacing: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Horizontal cartridge conveyor.
|
||||||
|
ListView {
|
||||||
|
id: cartridgeList
|
||||||
|
x: 178; y: 20
|
||||||
|
width: root.width - 356
|
||||||
|
height: 180
|
||||||
|
orientation: ListView.Horizontal
|
||||||
|
model: root.apps
|
||||||
|
currentIndex: root.selectedIndex
|
||||||
|
spacing: 8
|
||||||
|
clip: true
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
onCurrentIndexChanged: positionViewAtIndex(currentIndex, ListView.Contain)
|
||||||
|
|
||||||
|
delegate: MouseArea {
|
||||||
|
id: cartridge
|
||||||
|
required property int index
|
||||||
|
required property var modelData
|
||||||
|
readonly property bool selected: index === root.selectedIndex
|
||||||
|
|
||||||
|
width: selected ? 128 : 108
|
||||||
|
height: selected ? 176 : 148
|
||||||
|
y: selected ? 0 : 24
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onEntered: root.selectionRequested(index)
|
||||||
|
onClicked: root.launchRequested(index)
|
||||||
|
|
||||||
|
Behavior on y { NumberAnimation { duration: 120; easing.type: Easing.OutCubic } }
|
||||||
|
Behavior on height { NumberAnimation { duration: 120; easing.type: Easing.OutCubic } }
|
||||||
|
|
||||||
|
Shape {
|
||||||
|
id: cartridgeShape
|
||||||
|
anchors.fill: parent
|
||||||
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
ShapePath {
|
||||||
|
fillColor: cartridge.selected ? Theme.selection : Theme.textAlpha(0.022)
|
||||||
|
strokeColor: cartridge.selected ? Theme.accent : Theme.hair
|
||||||
|
strokeWidth: cartridge.selected ? 2 : 1
|
||||||
|
startX: 12; startY: 0
|
||||||
|
PathLine { x: cartridgeShape.width - 8; y: 0 }
|
||||||
|
PathLine { x: cartridgeShape.width; y: 8 }
|
||||||
|
PathLine { x: cartridgeShape.width; y: cartridgeShape.height - 14 }
|
||||||
|
PathLine { x: cartridgeShape.width - 14; y: cartridgeShape.height }
|
||||||
|
PathLine { x: 0; y: cartridgeShape.height }
|
||||||
|
PathLine { x: 0; y: 12 }
|
||||||
|
PathLine { x: 12; y: 0 }
|
||||||
|
}
|
||||||
|
ShapePath {
|
||||||
|
fillColor: cartridge.selected ? Theme.accent : Theme.textAlpha(0.12)
|
||||||
|
strokeWidth: 0
|
||||||
|
startX: 0; startY: cartridgeShape.height - 6
|
||||||
|
PathLine { x: cartridgeShape.width - 14; y: cartridgeShape.height - 6 }
|
||||||
|
PathLine { x: cartridgeShape.width - 20; y: cartridgeShape.height }
|
||||||
|
PathLine { x: 0; y: cartridgeShape.height }
|
||||||
|
PathLine { x: 0; y: cartridgeShape.height - 6 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MicroText { x: 9; y: 8; text: "C" + String(cartridge.index + 1).padStart(2, "0"); color: cartridge.selected ? Theme.accent : Theme.muted }
|
||||||
|
Rectangle {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
y: cartridge.selected ? 30 : 27
|
||||||
|
width: cartridge.selected ? 68 : 54
|
||||||
|
height: width
|
||||||
|
color: cartridge.selected ? Theme.accentAlpha(0.12) : Theme.textAlpha(0.018)
|
||||||
|
border.width: 1
|
||||||
|
border.color: cartridge.selected ? Theme.accent : Theme.hair
|
||||||
|
IconImage { visible: root.showIcons; anchors.centerIn: parent; implicitSize: cartridge.selected ? 48 : 38; source: root.showIcons ? Quickshell.iconPath(cartridge.modelData.icon || "application-x-executable", "application-x-executable") : "" }
|
||||||
|
Text { visible: !root.showIcons; anchors.centerIn: parent; text: String(cartridge.modelData.name || "?").charAt(0).toUpperCase(); color: cartridge.selected ? Theme.accent : Theme.text; font.family: Theme.displayFont; font.pixelSize: cartridge.selected ? 23 : 18; font.bold: true }
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
x: 8; anchors.bottom: generic.top; anchors.bottomMargin: 4
|
||||||
|
width: parent.width - 16
|
||||||
|
text: cartridge.modelData.name || "UNKNOWN"
|
||||||
|
color: cartridge.selected ? Theme.accent : Theme.text
|
||||||
|
font.family: Theme.displayFont
|
||||||
|
font.pixelSize: cartridge.selected ? 10 : 9
|
||||||
|
font.bold: cartridge.selected
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
MicroText {
|
||||||
|
id: generic
|
||||||
|
x: 8; anchors.bottom: parent.bottom; anchors.bottomMargin: 13
|
||||||
|
width: parent.width - 16
|
||||||
|
text: cartridge.modelData.genericName || "APPLICATION"
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text { anchors.centerIn: parent; visible: root.apps.length === 0; text: "NO CARTRIDGES MATCH QUERY"; color: Theme.muted; font.family: Theme.microFont; font.pixelSize: 9; font.letterSpacing: 1.2 }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Right telemetry clamp.
|
||||||
|
Item {
|
||||||
|
id: clamp
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 18
|
||||||
|
y: 20; width: 150; height: 180
|
||||||
|
|
||||||
|
Shape {
|
||||||
|
anchors.fill: parent
|
||||||
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
ShapePath {
|
||||||
|
fillColor: Theme.textAlpha(0.018)
|
||||||
|
strokeColor: Theme.hair
|
||||||
|
strokeWidth: 1
|
||||||
|
startX: 0; startY: 0
|
||||||
|
PathLine { x: 128; y: 0 }
|
||||||
|
PathLine { x: 150; y: 22 }
|
||||||
|
PathLine { x: 150; y: 180 }
|
||||||
|
PathLine { x: 0; y: 180 }
|
||||||
|
PathLine { x: 0; y: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MicroText { x: 12; y: 12; text: "ACTIVE SLOT"; color: Theme.accent }
|
||||||
|
Text { x: 12; y: 35; width: parent.width - 24; text: String(Math.max(0, root.selectedIndex + 1)).padStart(3, "0"); color: Theme.text; font.family: Theme.displayFont; font.pixelSize: 28; font.bold: true }
|
||||||
|
MicroText { x: 12; y: 75; width: parent.width - 24; text: root.selectedApp ? root.selectedApp.name : "NO TARGET"; color: Theme.text }
|
||||||
|
MicroText { x: 12; y: 96; width: parent.width - 24; text: root.apps.length + " AVAILABLE" }
|
||||||
|
Row { x: 12; y: 120; spacing: 3; Repeater { model: 12; Rectangle { required property int index; width: 7; height: 4; color: index < Math.min(12, root.apps.length) ? Theme.accent : Theme.hair } } }
|
||||||
|
MicroText { x: 12; anchors.bottom: parent.bottom; anchors.bottomMargin: 12; text: "BUS // ARMED"; color: Theme.accent }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bottom command spine.
|
||||||
|
Shape {
|
||||||
|
id: commandSpine
|
||||||
|
x: 176; y: 211
|
||||||
|
width: root.width - 352; height: 48
|
||||||
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
ShapePath {
|
||||||
|
fillColor: Theme.accentAlpha(0.10)
|
||||||
|
strokeColor: Theme.accent
|
||||||
|
strokeWidth: 1
|
||||||
|
startX: 14; startY: 0
|
||||||
|
PathLine { x: commandSpine.width; y: 0 }
|
||||||
|
PathLine { x: commandSpine.width - 14; y: commandSpine.height }
|
||||||
|
PathLine { x: 0; y: commandSpine.height }
|
||||||
|
PathLine { x: 14; y: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text { x: 196; y: 221; text: ">_"; color: Theme.accent; font.family: Theme.displayFont; font.pixelSize: 17; font.bold: true }
|
||||||
|
TextInput {
|
||||||
|
id: dockInput
|
||||||
|
x: 230; y: 219; width: root.width - 650; height: 34
|
||||||
|
verticalAlignment: TextInput.AlignVCenter
|
||||||
|
color: Theme.text
|
||||||
|
selectionColor: Theme.accent
|
||||||
|
selectedTextColor: Theme.surface
|
||||||
|
font.family: Theme.displayFont
|
||||||
|
font.pixelSize: 14
|
||||||
|
font.letterSpacing: 1
|
||||||
|
clip: true
|
||||||
|
onTextChanged: root.selectionRequested(0)
|
||||||
|
Keys.onPressed: event => {
|
||||||
|
switch (event.key) {
|
||||||
|
case Qt.Key_Right: case Qt.Key_Tab: root.moveRequested(1); event.accepted = true; break;
|
||||||
|
case Qt.Key_Left: 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: dockInput.text.length === 0; text: "FILTER DOCK CARTRIDGES"; color: Theme.muted; font: dockInput.font }
|
||||||
|
}
|
||||||
|
MicroText { x: root.width - 492; y: 228; text: "← → SELECT // ENTER LAUNCH // ESC RETRACT"; color: Theme.accent }
|
||||||
|
|
||||||
|
// Lower rail hardware.
|
||||||
|
Row {
|
||||||
|
x: 18; anchors.bottom: parent.bottom; anchors.bottomMargin: 1; spacing: 4
|
||||||
|
Repeater { model: 70; Rectangle { required property int index; width: 10; height: 4; color: index % 5 === 0 ? Theme.accent : Theme.hair; transform: Rotation { angle: -32 } } }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import Quickshell.Wayland
|
|||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import qs.widgets.theme
|
||||||
|
|
||||||
// Variant 4 — "Console": a full-width command deck that unfolds down out of
|
// Variant 4 — "Console": a full-width command deck that unfolds down out of
|
||||||
// the top bar, with a horizontal carousel of app cards. While open it drives
|
// the top bar, with a horizontal carousel of app cards. While open it drives
|
||||||
@@ -99,7 +100,7 @@ Scope {
|
|||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
|
||||||
clip: true
|
clip: true
|
||||||
color: "#0F1012"
|
color: Theme.surface
|
||||||
|
|
||||||
height: root.active ? win.openHeight : 0
|
height: root.active ? win.openHeight : 0
|
||||||
|
|
||||||
@@ -116,7 +117,7 @@ Scope {
|
|||||||
|
|
||||||
// Accent lid — mirrors the top bar strip, reads as its extension.
|
// Accent lid — mirrors the top bar strip, reads as its extension.
|
||||||
Rectangle {
|
Rectangle {
|
||||||
color: "#FFD063"
|
color: Theme.accent
|
||||||
implicitHeight: 4
|
implicitHeight: 4
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
@@ -132,8 +133,8 @@ Scope {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: ">_"
|
text: ">_"
|
||||||
color: "#FFD063"
|
color: Theme.accent
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 22
|
font.pointSize: 22
|
||||||
font.bold: true
|
font.bold: true
|
||||||
}
|
}
|
||||||
@@ -147,7 +148,7 @@ Scope {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
verticalAlignment: TextInput.AlignVCenter
|
verticalAlignment: TextInput.AlignVCenter
|
||||||
|
|
||||||
color: "#EEEEEE"
|
color: Theme.text
|
||||||
font.pointSize: 16
|
font.pointSize: 16
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
@@ -182,7 +183,7 @@ Scope {
|
|||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
visible: input.text.length === 0
|
visible: input.text.length === 0
|
||||||
text: "launch application…"
|
text: "launch application…"
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font: input.font
|
font: input.font
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,14 +191,14 @@ Scope {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: model.apps.length + " apps"
|
text: model.apps.length + " apps"
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 15
|
font.pointSize: 15
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
color: "#292C30"
|
color: Theme.raised
|
||||||
implicitHeight: 1
|
implicitHeight: 1
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
@@ -236,9 +237,9 @@ Scope {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: card.selected ? "#292C30" : "transparent"
|
color: card.selected ? Theme.raised : "transparent"
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: card.selected ? "#FFD063" : "#292C30"
|
border.color: card.selected ? Theme.accent : Theme.raised
|
||||||
|
|
||||||
Behavior on border.color {
|
Behavior on border.color {
|
||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
@@ -261,7 +262,7 @@ Scope {
|
|||||||
Text {
|
Text {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: card.modelData.name
|
text: card.modelData.name
|
||||||
color: card.selected ? "#FFD063" : "#EEEEEE"
|
color: card.selected ? Theme.accent : Theme.text
|
||||||
font.pointSize: 9
|
font.pointSize: 9
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
@@ -277,8 +278,8 @@ Scope {
|
|||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
visible: model.apps.length === 0
|
visible: model.apps.length === 0
|
||||||
text: "no matches"
|
text: "no matches"
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 16
|
font.pointSize: 16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import Quickshell.Widgets
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import QtQuick.Shapes
|
import QtQuick.Shapes
|
||||||
|
import qs.widgets.theme
|
||||||
|
|
||||||
// Variant 7 — a copy of the "Slant" (V6) launcher, plus a small floating
|
// Variant 7 — a copy of the "Slant" (V6) launcher, plus a small floating
|
||||||
// power panel docked to its right with Shutdown / Reboot buttons.
|
// power panel docked to its right with Shutdown / Reboot buttons.
|
||||||
@@ -77,7 +78,7 @@ Scope {
|
|||||||
// Dim backdrop — click to dismiss.
|
// Dim backdrop — click to dismiss.
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: "#0A0A0C"
|
color: Theme.surface
|
||||||
opacity: 0.55
|
opacity: 0.55
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
@@ -107,8 +108,8 @@ Scope {
|
|||||||
preferredRendererType: Shape.CurveRenderer
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
fillColor: "#0F1012"
|
fillColor: Theme.surface
|
||||||
strokeColor: "#FFD063"
|
strokeColor: Theme.accent
|
||||||
strokeWidth: 2
|
strokeWidth: 2
|
||||||
|
|
||||||
startX: frame.chamfer
|
startX: frame.chamfer
|
||||||
@@ -153,7 +154,7 @@ Scope {
|
|||||||
// run out to the top and left edges to meet the straight borders.
|
// run out to the top and left edges to meet the straight borders.
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
|
|
||||||
startX: 0
|
startX: 0
|
||||||
startY: frame.chamfer
|
startY: frame.chamfer
|
||||||
@@ -179,7 +180,7 @@ Scope {
|
|||||||
// bottom and right edges.
|
// bottom and right edges.
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
|
|
||||||
startX: panelShape.width
|
startX: panelShape.width
|
||||||
startY: panelShape.height - frame.chamfer
|
startY: panelShape.height - frame.chamfer
|
||||||
@@ -205,7 +206,7 @@ Scope {
|
|||||||
// detached from the panel by the width of the cut.
|
// detached from the panel by the width of the cut.
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
|
|
||||||
startX: panelShape.width
|
startX: panelShape.width
|
||||||
startY: panelShape.height
|
startY: panelShape.height
|
||||||
@@ -228,7 +229,7 @@ Scope {
|
|||||||
// slanted end pieces on both arms.
|
// slanted end pieces on both arms.
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
|
|
||||||
// inner, right end of the bottom arm
|
// inner, right end of the bottom arm
|
||||||
startX: panelShape.width / 3
|
startX: panelShape.width / 3
|
||||||
@@ -277,7 +278,7 @@ Scope {
|
|||||||
// slanted end pieces on both arms.
|
// slanted end pieces on both arms.
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
|
|
||||||
// inner, bottom of the right arm
|
// inner, bottom of the right arm
|
||||||
startX: panelShape.width
|
startX: panelShape.width
|
||||||
@@ -345,7 +346,7 @@ Scope {
|
|||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
startX: 6
|
startX: 6
|
||||||
startY: 0
|
startY: 0
|
||||||
PathLine {
|
PathLine {
|
||||||
@@ -367,7 +368,7 @@ Scope {
|
|||||||
}
|
}
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
startX: 15
|
startX: 15
|
||||||
startY: 0
|
startY: 0
|
||||||
PathLine {
|
PathLine {
|
||||||
@@ -389,7 +390,7 @@ Scope {
|
|||||||
}
|
}
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
startX: 24
|
startX: 24
|
||||||
startY: 0
|
startY: 0
|
||||||
PathLine {
|
PathLine {
|
||||||
@@ -420,8 +421,8 @@ Scope {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
verticalAlignment: TextInput.AlignVCenter
|
verticalAlignment: TextInput.AlignVCenter
|
||||||
|
|
||||||
color: "#EEEEEE"
|
color: Theme.text
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 20
|
font.pointSize: 20
|
||||||
font.letterSpacing: 1
|
font.letterSpacing: 1
|
||||||
clip: true
|
clip: true
|
||||||
@@ -457,7 +458,7 @@ Scope {
|
|||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
visible: input.text.length === 0
|
visible: input.text.length === 0
|
||||||
text: "run"
|
text: "run"
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font: input.font
|
font: input.font
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -465,8 +466,8 @@ Scope {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: model.apps.length
|
text: model.apps.length
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 18
|
font.pointSize: 18
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -489,7 +490,7 @@ Scope {
|
|||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
startX: divider.slant
|
startX: divider.slant
|
||||||
startY: 0
|
startY: 0
|
||||||
PathLine {
|
PathLine {
|
||||||
@@ -512,7 +513,7 @@ Scope {
|
|||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
startX: 0
|
startX: 0
|
||||||
startY: divider.height
|
startY: divider.height
|
||||||
PathLine {
|
PathLine {
|
||||||
@@ -535,7 +536,7 @@ Scope {
|
|||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
startX: divider.width
|
startX: divider.width
|
||||||
startY: 0
|
startY: 0
|
||||||
PathLine {
|
PathLine {
|
||||||
@@ -597,7 +598,7 @@ Scope {
|
|||||||
// Body
|
// Body
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#22262C"
|
fillColor: Theme.raised
|
||||||
startX: appRow.shear
|
startX: appRow.shear
|
||||||
startY: 0
|
startY: 0
|
||||||
PathLine {
|
PathLine {
|
||||||
@@ -620,7 +621,7 @@ Scope {
|
|||||||
// Left accent edge
|
// Left accent edge
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
startX: appRow.shear
|
startX: appRow.shear
|
||||||
startY: 0
|
startY: 0
|
||||||
PathLine {
|
PathLine {
|
||||||
@@ -655,7 +656,7 @@ Scope {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: appRow.modelData.name
|
text: appRow.modelData.name
|
||||||
color: appRow.selected ? "#FFD063" : "#EEEEEE"
|
color: appRow.selected ? Theme.accent : Theme.text
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -663,7 +664,7 @@ Scope {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: appRow.modelData.genericName || ""
|
text: appRow.modelData.genericName || ""
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font.pointSize: 10
|
font.pointSize: 10
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
Layout.maximumWidth: 220
|
Layout.maximumWidth: 220
|
||||||
@@ -694,8 +695,8 @@ Scope {
|
|||||||
preferredRendererType: Shape.CurveRenderer
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
strokeColor: "#FFD063"
|
strokeColor: Theme.accent
|
||||||
strokeWidth: 2
|
strokeWidth: 2
|
||||||
|
|
||||||
startX: 0
|
startX: 0
|
||||||
@@ -732,9 +733,9 @@ Scope {
|
|||||||
Text {
|
Text {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
text: "POWER"
|
text: "POWER"
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 11
|
font.pointSize: 11
|
||||||
color: "#0F1012"
|
color: Theme.surface
|
||||||
}
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
@@ -762,8 +763,8 @@ Scope {
|
|||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 1
|
strokeWidth: 1
|
||||||
strokeColor: powerButton.containsMouse ? "#FFD063" : "#7A7B7D"
|
strokeColor: powerButton.containsMouse ? Theme.accent : Theme.muted
|
||||||
fillColor: "#292C30"
|
fillColor: Theme.raised
|
||||||
|
|
||||||
startX: powerButton.chamfer
|
startX: powerButton.chamfer
|
||||||
startY: 0
|
startY: 0
|
||||||
@@ -785,8 +786,8 @@ Scope {
|
|||||||
Text {
|
Text {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: powerButton.modelData.label
|
text: powerButton.modelData.label
|
||||||
color: powerButton.containsMouse ? "#FFD063" : "#EEEEEE"
|
color: powerButton.containsMouse ? Theme.accent : Theme.text
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 11
|
font.pointSize: 11
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import Quickshell.Wayland
|
|||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import qs.widgets.theme
|
||||||
|
|
||||||
// Variant 5 — "Dock": the Console concept mirrored to the bottom edge. A
|
// Variant 5 — "Dock": the Console concept mirrored to the bottom edge. A
|
||||||
// full-width deck rises up out of the bottom bar, which energizes via
|
// full-width deck rises up out of the bottom bar, which energizes via
|
||||||
@@ -98,7 +99,7 @@ Scope {
|
|||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
|
||||||
clip: true
|
clip: true
|
||||||
color: "#0F1012"
|
color: Theme.surface
|
||||||
|
|
||||||
height: root.active ? win.openHeight : 0
|
height: root.active ? win.openHeight : 0
|
||||||
|
|
||||||
@@ -153,9 +154,9 @@ Scope {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: card.selected ? "#292C30" : "transparent"
|
color: card.selected ? Theme.raised : "transparent"
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: card.selected ? "#FFD063" : "#292C30"
|
border.color: card.selected ? Theme.accent : Theme.raised
|
||||||
|
|
||||||
Behavior on border.color {
|
Behavior on border.color {
|
||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
@@ -178,7 +179,7 @@ Scope {
|
|||||||
Text {
|
Text {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: card.modelData.name
|
text: card.modelData.name
|
||||||
color: card.selected ? "#FFD063" : "#EEEEEE"
|
color: card.selected ? Theme.accent : Theme.text
|
||||||
font.pointSize: 9
|
font.pointSize: 9
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
@@ -194,14 +195,14 @@ Scope {
|
|||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
visible: model.apps.length === 0
|
visible: model.apps.length === 0
|
||||||
text: "no matches"
|
text: "no matches"
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 16
|
font.pointSize: 16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
color: "#292C30"
|
color: Theme.raised
|
||||||
implicitHeight: 1
|
implicitHeight: 1
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
@@ -217,8 +218,8 @@ Scope {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: ">_"
|
text: ">_"
|
||||||
color: "#FFD063"
|
color: Theme.accent
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 22
|
font.pointSize: 22
|
||||||
font.bold: true
|
font.bold: true
|
||||||
}
|
}
|
||||||
@@ -232,7 +233,7 @@ Scope {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
verticalAlignment: TextInput.AlignVCenter
|
verticalAlignment: TextInput.AlignVCenter
|
||||||
|
|
||||||
color: "#EEEEEE"
|
color: Theme.text
|
||||||
font.pointSize: 16
|
font.pointSize: 16
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
@@ -267,7 +268,7 @@ Scope {
|
|||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
visible: input.text.length === 0
|
visible: input.text.length === 0
|
||||||
text: "launch application…"
|
text: "launch application…"
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font: input.font
|
font: input.font
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -275,15 +276,15 @@ Scope {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: model.apps.length + " apps"
|
text: model.apps.length + " apps"
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 15
|
font.pointSize: 15
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accent lid — mirrors the bottom bar strip.
|
// Accent lid — mirrors the bottom bar strip.
|
||||||
Rectangle {
|
Rectangle {
|
||||||
color: "#FFD063"
|
color: Theme.accent
|
||||||
implicitHeight: 4
|
implicitHeight: 4
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import Quickshell.Wayland
|
|||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import qs.widgets.theme
|
||||||
|
|
||||||
// Variant 2 — "Grid": centered app-drawer with a dim backdrop and icon tiles.
|
// Variant 2 — "Grid": centered app-drawer with a dim backdrop and icon tiles.
|
||||||
Scope {
|
Scope {
|
||||||
@@ -78,7 +79,7 @@ Scope {
|
|||||||
// Dim backdrop — click anywhere outside to dismiss.
|
// Dim backdrop — click anywhere outside to dismiss.
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: "#0A0A0C"
|
color: Theme.surface
|
||||||
opacity: 0.55
|
opacity: 0.55
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
@@ -93,9 +94,9 @@ Scope {
|
|||||||
width: 760
|
width: 760
|
||||||
height: 560
|
height: 560
|
||||||
|
|
||||||
color: "#0F1012"
|
color: Theme.surface
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: "#FFD063"
|
border.color: Theme.accent
|
||||||
opacity: 0.98
|
opacity: 0.98
|
||||||
|
|
||||||
// Accent corner brackets for the cyberpunk frame.
|
// Accent corner brackets for the cyberpunk frame.
|
||||||
@@ -104,14 +105,14 @@ Scope {
|
|||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
width: 5
|
width: 5
|
||||||
height: 28
|
height: 28
|
||||||
color: "#FFD063"
|
color: Theme.accent
|
||||||
}
|
}
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
width: 28
|
width: 28
|
||||||
height: 5
|
height: 5
|
||||||
color: "#FFD063"
|
color: Theme.accent
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
@@ -126,8 +127,8 @@ Scope {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "APPS"
|
text: "APPS"
|
||||||
color: "#FFD063"
|
color: Theme.accent
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 22
|
font.pointSize: 22
|
||||||
font.letterSpacing: 3
|
font.letterSpacing: 3
|
||||||
}
|
}
|
||||||
@@ -138,9 +139,9 @@ Scope {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: "#292C30"
|
color: Theme.raised
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: input.activeFocus ? "#FFD063" : "#7A7B7D"
|
border.color: input.activeFocus ? Theme.accent : Theme.muted
|
||||||
|
|
||||||
Behavior on border.color {
|
Behavior on border.color {
|
||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
@@ -155,7 +156,7 @@ Scope {
|
|||||||
anchors.rightMargin: 12
|
anchors.rightMargin: 12
|
||||||
verticalAlignment: TextInput.AlignVCenter
|
verticalAlignment: TextInput.AlignVCenter
|
||||||
|
|
||||||
color: "#EEEEEE"
|
color: Theme.text
|
||||||
font.pointSize: 13
|
font.pointSize: 13
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
@@ -198,7 +199,7 @@ Scope {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: input.text.length === 0
|
visible: input.text.length === 0
|
||||||
text: "Type to search…"
|
text: "Type to search…"
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font: input.font
|
font: input.font
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,7 +208,7 @@ Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
color: "#FFD063"
|
color: Theme.accent
|
||||||
implicitHeight: 3
|
implicitHeight: 3
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
@@ -242,9 +243,9 @@ Scope {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 6
|
anchors.margins: 6
|
||||||
color: tile.selected ? "#292C30" : "transparent"
|
color: tile.selected ? Theme.raised : "transparent"
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: tile.selected ? "#FFD063" : "transparent"
|
border.color: tile.selected ? Theme.accent : "transparent"
|
||||||
|
|
||||||
Behavior on border.color {
|
Behavior on border.color {
|
||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
@@ -266,7 +267,7 @@ Scope {
|
|||||||
Text {
|
Text {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: tile.modelData.name
|
text: tile.modelData.name
|
||||||
color: tile.selected ? "#FFD063" : "#EEEEEE"
|
color: tile.selected ? Theme.accent : Theme.text
|
||||||
font.pointSize: 10
|
font.pointSize: 10
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import Quickshell.Widgets
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import QtQuick.Shapes
|
import QtQuick.Shapes
|
||||||
|
import qs.widgets.theme
|
||||||
|
|
||||||
// Variant 6 — "Slant": breaks up the rectangular layout with angled geometry —
|
// Variant 6 — "Slant": breaks up the rectangular layout with angled geometry —
|
||||||
// a chamfered panel, a slanted header divider and sheared row highlights.
|
// a chamfered panel, a slanted header divider and sheared row highlights.
|
||||||
@@ -77,7 +78,7 @@ Scope {
|
|||||||
// Dim backdrop — click to dismiss.
|
// Dim backdrop — click to dismiss.
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: "#0A0A0C"
|
color: Theme.surface
|
||||||
opacity: 0.55
|
opacity: 0.55
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
@@ -107,8 +108,8 @@ Scope {
|
|||||||
preferredRendererType: Shape.CurveRenderer
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
fillColor: "#0F1012"
|
fillColor: Theme.surface
|
||||||
strokeColor: "#FFD063"
|
strokeColor: Theme.accent
|
||||||
strokeWidth: 2
|
strokeWidth: 2
|
||||||
|
|
||||||
startX: frame.chamfer
|
startX: frame.chamfer
|
||||||
@@ -153,7 +154,7 @@ Scope {
|
|||||||
// run out to the top and left edges to meet the straight borders.
|
// run out to the top and left edges to meet the straight borders.
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
|
|
||||||
startX: 0
|
startX: 0
|
||||||
startY: frame.chamfer
|
startY: frame.chamfer
|
||||||
@@ -179,7 +180,7 @@ Scope {
|
|||||||
// bottom and right edges.
|
// bottom and right edges.
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
|
|
||||||
startX: panelShape.width
|
startX: panelShape.width
|
||||||
startY: panelShape.height - frame.chamfer
|
startY: panelShape.height - frame.chamfer
|
||||||
@@ -205,7 +206,7 @@ Scope {
|
|||||||
// detached from the panel by the width of the cut.
|
// detached from the panel by the width of the cut.
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
|
|
||||||
startX: panelShape.width
|
startX: panelShape.width
|
||||||
startY: panelShape.height
|
startY: panelShape.height
|
||||||
@@ -228,7 +229,7 @@ Scope {
|
|||||||
// slanted end pieces on both arms.
|
// slanted end pieces on both arms.
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
|
|
||||||
// inner, right end of the bottom arm
|
// inner, right end of the bottom arm
|
||||||
startX: panelShape.width / 3
|
startX: panelShape.width / 3
|
||||||
@@ -277,7 +278,7 @@ Scope {
|
|||||||
// slanted end pieces on both arms.
|
// slanted end pieces on both arms.
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
|
|
||||||
// inner, bottom of the right arm
|
// inner, bottom of the right arm
|
||||||
startX: panelShape.width
|
startX: panelShape.width
|
||||||
@@ -345,7 +346,7 @@ Scope {
|
|||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
startX: 6
|
startX: 6
|
||||||
startY: 0
|
startY: 0
|
||||||
PathLine {
|
PathLine {
|
||||||
@@ -367,7 +368,7 @@ Scope {
|
|||||||
}
|
}
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
startX: 15
|
startX: 15
|
||||||
startY: 0
|
startY: 0
|
||||||
PathLine {
|
PathLine {
|
||||||
@@ -389,7 +390,7 @@ Scope {
|
|||||||
}
|
}
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
startX: 24
|
startX: 24
|
||||||
startY: 0
|
startY: 0
|
||||||
PathLine {
|
PathLine {
|
||||||
@@ -420,8 +421,8 @@ Scope {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
verticalAlignment: TextInput.AlignVCenter
|
verticalAlignment: TextInput.AlignVCenter
|
||||||
|
|
||||||
color: "#EEEEEE"
|
color: Theme.text
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 20
|
font.pointSize: 20
|
||||||
font.letterSpacing: 1
|
font.letterSpacing: 1
|
||||||
clip: true
|
clip: true
|
||||||
@@ -457,7 +458,7 @@ Scope {
|
|||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
visible: input.text.length === 0
|
visible: input.text.length === 0
|
||||||
text: "run"
|
text: "run"
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font: input.font
|
font: input.font
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -465,8 +466,8 @@ Scope {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: model.apps.length
|
text: model.apps.length
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 18
|
font.pointSize: 18
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -489,7 +490,7 @@ Scope {
|
|||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
startX: divider.slant
|
startX: divider.slant
|
||||||
startY: 0
|
startY: 0
|
||||||
PathLine {
|
PathLine {
|
||||||
@@ -512,7 +513,7 @@ Scope {
|
|||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
startX: 0
|
startX: 0
|
||||||
startY: divider.height
|
startY: divider.height
|
||||||
PathLine {
|
PathLine {
|
||||||
@@ -535,7 +536,7 @@ Scope {
|
|||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
startX: divider.width
|
startX: divider.width
|
||||||
startY: 0
|
startY: 0
|
||||||
PathLine {
|
PathLine {
|
||||||
@@ -597,7 +598,7 @@ Scope {
|
|||||||
// Body
|
// Body
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#22262C"
|
fillColor: Theme.raised
|
||||||
startX: appRow.shear
|
startX: appRow.shear
|
||||||
startY: 0
|
startY: 0
|
||||||
PathLine {
|
PathLine {
|
||||||
@@ -620,7 +621,7 @@ Scope {
|
|||||||
// Left accent edge
|
// Left accent edge
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
startX: appRow.shear
|
startX: appRow.shear
|
||||||
startY: 0
|
startY: 0
|
||||||
PathLine {
|
PathLine {
|
||||||
@@ -655,7 +656,7 @@ Scope {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: appRow.modelData.name
|
text: appRow.modelData.name
|
||||||
color: appRow.selected ? "#FFD063" : "#EEEEEE"
|
color: appRow.selected ? Theme.accent : Theme.text
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -663,7 +664,7 @@ Scope {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: appRow.modelData.genericName || ""
|
text: appRow.modelData.genericName || ""
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font.pointSize: 10
|
font.pointSize: 10
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
Layout.maximumWidth: 220
|
Layout.maximumWidth: 220
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import Quickshell.Wayland
|
|||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import qs.widgets.theme
|
||||||
|
|
||||||
// Variant 3 — "Spotlight": top-center command bar with a compact result list.
|
// Variant 3 — "Spotlight": top-center command bar with a compact result list.
|
||||||
Scope {
|
Scope {
|
||||||
@@ -91,9 +92,9 @@ Scope {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
implicitHeight: 60
|
implicitHeight: 60
|
||||||
color: "#0F1012"
|
color: Theme.surface
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: "#FFD063"
|
border.color: Theme.accent
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -103,8 +104,8 @@ Scope {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: ">"
|
text: ">"
|
||||||
color: "#FFD063"
|
color: Theme.accent
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 26
|
font.pointSize: 26
|
||||||
font.bold: true
|
font.bold: true
|
||||||
}
|
}
|
||||||
@@ -118,7 +119,7 @@ Scope {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
verticalAlignment: TextInput.AlignVCenter
|
verticalAlignment: TextInput.AlignVCenter
|
||||||
|
|
||||||
color: "#EEEEEE"
|
color: Theme.text
|
||||||
font.pointSize: 17
|
font.pointSize: 17
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
@@ -153,7 +154,7 @@ Scope {
|
|||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
visible: input.text.length === 0
|
visible: input.text.length === 0
|
||||||
text: "run application"
|
text: "run application"
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font: input.font
|
font: input.font
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,8 +162,8 @@ Scope {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: model.apps.length + " ▸"
|
text: model.apps.length + " ▸"
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 16
|
font.pointSize: 16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,7 +173,7 @@ Scope {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
implicitHeight: 3
|
implicitHeight: 3
|
||||||
color: "#FFD063"
|
color: Theme.accent
|
||||||
visible: model.apps.length > 0
|
visible: model.apps.length > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,11 +182,11 @@ Scope {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
// Cap the visible height to maxRows; scroll beyond that.
|
// Cap the visible height to maxRows; scroll beyond that.
|
||||||
implicitHeight: Math.min(model.apps.length, win.maxRows) * 44 + 2
|
implicitHeight: Math.min(model.apps.length, win.maxRows) * 44 + 2
|
||||||
color: "#0F1012"
|
color: Theme.surface
|
||||||
opacity: 0.97
|
opacity: 0.97
|
||||||
visible: model.apps.length > 0
|
visible: model.apps.length > 0
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: "#292C30"
|
border.color: Theme.raised
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
id: list
|
id: list
|
||||||
@@ -215,14 +216,14 @@ Scope {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: entry.selected ? "#1A1C1F" : "transparent"
|
color: entry.selected ? Theme.selection : "transparent"
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
width: 3
|
width: 3
|
||||||
color: "#FFD063"
|
color: Theme.accent
|
||||||
visible: entry.selected
|
visible: entry.selected
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,7 +240,7 @@ Scope {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: entry.modelData.name
|
text: entry.modelData.name
|
||||||
color: entry.selected ? "#FFD063" : "#EEEEEE"
|
color: entry.selected ? Theme.accent : Theme.text
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -247,7 +248,7 @@ Scope {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: entry.modelData.genericName || ""
|
text: entry.modelData.genericName || ""
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font.pointSize: 10
|
font.pointSize: 10
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
Layout.maximumWidth: 200
|
Layout.maximumWidth: 200
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import Quickshell.Wayland
|
|||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import qs.widgets.theme
|
||||||
|
|
||||||
// Variant 1 — "Stack": left-anchored vertical list launcher.
|
// Variant 1 — "Stack": left-anchored vertical list launcher.
|
||||||
// Framed with the top/bottom accent strips used by the main Bar.
|
// Framed with the top/bottom accent strips used by the main Bar.
|
||||||
@@ -82,7 +83,7 @@ Scope {
|
|||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
color: "#FFD063"
|
color: Theme.accent
|
||||||
implicitHeight: 5
|
implicitHeight: 5
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
@@ -93,8 +94,8 @@ Scope {
|
|||||||
|
|
||||||
margin: 12
|
margin: 12
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: "#FFD063"
|
border.color: Theme.accent
|
||||||
color: "#0F1012"
|
color: Theme.surface
|
||||||
opacity: 0.95
|
opacity: 0.95
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
@@ -107,7 +108,7 @@ Scope {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "▚"
|
text: "▚"
|
||||||
color: "#FFD063"
|
color: Theme.accent
|
||||||
font.pointSize: 14
|
font.pointSize: 14
|
||||||
font.bold: true
|
font.bold: true
|
||||||
}
|
}
|
||||||
@@ -121,8 +122,8 @@ Scope {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
verticalAlignment: TextInput.AlignVCenter
|
verticalAlignment: TextInput.AlignVCenter
|
||||||
|
|
||||||
color: "#EEEEEE"
|
color: Theme.text
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 16
|
font.pointSize: 16
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
@@ -158,7 +159,7 @@ Scope {
|
|||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
visible: input.text.length === 0
|
visible: input.text.length === 0
|
||||||
text: "search"
|
text: "search"
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font: input.font
|
font: input.font
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,14 +167,14 @@ Scope {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: model.apps.length
|
text: model.apps.length
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 14
|
font.pointSize: 14
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
color: "#FFD063"
|
color: Theme.accent
|
||||||
implicitHeight: 2
|
implicitHeight: 2
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
@@ -207,7 +208,7 @@ Scope {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: appRow.selected ? "#292C30" : "transparent"
|
color: appRow.selected ? Theme.raised : "transparent"
|
||||||
|
|
||||||
// Accent bar on the selected row.
|
// Accent bar on the selected row.
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -215,7 +216,7 @@ Scope {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
width: 3
|
width: 3
|
||||||
color: "#FFD063"
|
color: Theme.accent
|
||||||
visible: appRow.selected
|
visible: appRow.selected
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,7 +237,7 @@ Scope {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: appRow.modelData.name
|
text: appRow.modelData.name
|
||||||
color: appRow.selected ? "#FFD063" : "#EEEEEE"
|
color: appRow.selected ? Theme.accent : Theme.text
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -245,7 +246,7 @@ Scope {
|
|||||||
Text {
|
Text {
|
||||||
visible: text.length > 0
|
visible: text.length > 0
|
||||||
text: appRow.modelData.genericName || appRow.modelData.comment || ""
|
text: appRow.modelData.genericName || appRow.modelData.comment || ""
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font.pointSize: 9
|
font.pointSize: 9
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -259,7 +260,7 @@ Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
color: "#FFD063"
|
color: Theme.accent
|
||||||
implicitHeight: 5
|
implicitHeight: 5
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Hyprland
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import QtQuick
|
||||||
|
import qs.widgets.theme
|
||||||
|
|
||||||
|
// Variant 10 — radial Orbit application launcher.
|
||||||
|
Scope {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool active: false
|
||||||
|
function toggle() { root.active = !root.active; }
|
||||||
|
|
||||||
|
GlobalShortcut {
|
||||||
|
name: "launcher10"
|
||||||
|
description: "Toggle app launcher (Orbit targeting)"
|
||||||
|
onPressed: root.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
PanelWindow {
|
||||||
|
id: win
|
||||||
|
visible: root.active
|
||||||
|
WlrLayershell.layer: WlrLayer.Overlay
|
||||||
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||||
|
exclusionMode: ExclusionMode.Ignore
|
||||||
|
color: Theme.textAlpha(0)
|
||||||
|
anchors { top: true; left: true; right: true; bottom: true }
|
||||||
|
|
||||||
|
property int selectedIndex: 0
|
||||||
|
|
||||||
|
function clampSelection(index) {
|
||||||
|
return model.apps.length === 0 ? 0 : Math.max(0, Math.min(model.apps.length - 1, index));
|
||||||
|
}
|
||||||
|
function move(delta) {
|
||||||
|
const count = model.apps.length;
|
||||||
|
if (count === 0)
|
||||||
|
return;
|
||||||
|
selectedIndex = ((selectedIndex + delta) % count + count) % count;
|
||||||
|
}
|
||||||
|
function launch(index) {
|
||||||
|
if (model.launch(index))
|
||||||
|
root.active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (visible) {
|
||||||
|
content.clearSearch();
|
||||||
|
selectedIndex = 0;
|
||||||
|
content.focusSearch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AppModel { id: model; search: content.query }
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Theme.surface
|
||||||
|
opacity: 0.80
|
||||||
|
MouseArea { anchors.fill: parent; onClicked: root.active = false }
|
||||||
|
}
|
||||||
|
|
||||||
|
OrbitLauncherContent {
|
||||||
|
id: content
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 1120
|
||||||
|
height: 660
|
||||||
|
scale: Math.min(1, (parent.width - 56) / width, (parent.height - 56) / height)
|
||||||
|
transformOrigin: Item.Center
|
||||||
|
apps: model.apps
|
||||||
|
selectedIndex: win.selectedIndex
|
||||||
|
onSelectionRequested: index => win.selectedIndex = win.clampSelection(index)
|
||||||
|
onMoveRequested: delta => win.move(delta)
|
||||||
|
onLaunchRequested: index => win.launch(index)
|
||||||
|
onDismissRequested: root.active = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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" }
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import Quickshell.Services.Notifications
|
|||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import qs.widgets.theme
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: root
|
id: root
|
||||||
@@ -12,7 +13,7 @@ ColumnLayout {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
color: "#FFD063"
|
color: Theme.accent
|
||||||
implicitHeight: 5
|
implicitHeight: 5
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -23,8 +24,8 @@ ColumnLayout {
|
|||||||
|
|
||||||
margin: 12
|
margin: 12
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: "#FFD063"
|
border.color: Theme.accent
|
||||||
color: "#0F1012"
|
color: Theme.surface
|
||||||
opacity: .9
|
opacity: .9
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
@@ -45,7 +46,7 @@ ColumnLayout {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: root.modelData.summary
|
text: root.modelData.summary
|
||||||
color: "#EEEEEE"
|
color: Theme.text
|
||||||
font.pointSize: 14
|
font.pointSize: 14
|
||||||
font.bold: true
|
font.bold: true
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
@@ -57,7 +58,7 @@ ColumnLayout {
|
|||||||
id: dismissButton
|
id: dismissButton
|
||||||
|
|
||||||
text: "×"
|
text: "×"
|
||||||
color: "#FFD063"
|
color: Theme.accent
|
||||||
font.pointSize: 18
|
font.pointSize: 18
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
@@ -71,7 +72,7 @@ ColumnLayout {
|
|||||||
Text {
|
Text {
|
||||||
visible: root.modelData.body !== ""
|
visible: root.modelData.body !== ""
|
||||||
text: root.modelData.body
|
text: root.modelData.body
|
||||||
color: "#EEEEEE"
|
color: Theme.text
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
|
|
||||||
@@ -92,9 +93,9 @@ ColumnLayout {
|
|||||||
|
|
||||||
required property NotificationAction modelData
|
required property NotificationAction modelData
|
||||||
|
|
||||||
color: "#292C30"
|
color: Theme.raised
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: "#FFD063"
|
border.color: Theme.accent
|
||||||
implicitHeight: 28
|
implicitHeight: 28
|
||||||
implicitWidth: actionText.implicitWidth + 16
|
implicitWidth: actionText.implicitWidth + 16
|
||||||
|
|
||||||
@@ -102,7 +103,7 @@ ColumnLayout {
|
|||||||
id: actionText
|
id: actionText
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: actionButton.modelData.text
|
text: actionButton.modelData.text
|
||||||
color: "#EEEEEE"
|
color: Theme.text
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import Quickshell
|
|||||||
import Quickshell.Services.Pipewire
|
import Quickshell.Services.Pipewire
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
import qs.widgets.theme
|
||||||
|
|
||||||
Scope {
|
Scope {
|
||||||
id: root
|
id: root
|
||||||
@@ -68,7 +69,7 @@ Scope {
|
|||||||
anchors.bottomMargin: 4
|
anchors.bottomMargin: 4
|
||||||
|
|
||||||
text: Math.round(Math.min(root.volume, root.maxVolume) * 100) + "%"
|
text: Math.round(Math.min(root.volume, root.maxVolume) * 100) + "%"
|
||||||
color: !root.muted && root.volume > 1 ? "#FF6B4A" : "#FFD063"
|
color: !root.muted && root.volume > 1 ? Theme.hot : Theme.accent
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
font.bold: true
|
font.bold: true
|
||||||
}
|
}
|
||||||
@@ -84,7 +85,7 @@ Scope {
|
|||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
implicitHeight: 6
|
implicitHeight: 6
|
||||||
width: parent.width * Math.min(root.volume, root.maxVolume) / root.maxVolume
|
width: parent.width * Math.min(root.volume, root.maxVolume) / root.maxVolume
|
||||||
color: "#FF6B4A"
|
color: Theme.hot
|
||||||
visible: !root.muted && root.volume > 1
|
visible: !root.muted && root.volume > 1
|
||||||
|
|
||||||
Behavior on width {
|
Behavior on width {
|
||||||
@@ -98,7 +99,7 @@ Scope {
|
|||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
implicitHeight: 6
|
implicitHeight: 6
|
||||||
width: parent.width * Math.min(root.volume, 1) / root.maxVolume
|
width: parent.width * Math.min(root.volume, 1) / root.maxVolume
|
||||||
color: root.muted ? "#7A7B7D" : "#FFD063"
|
color: root.muted ? Theme.muted : Theme.accent
|
||||||
|
|
||||||
Behavior on width {
|
Behavior on width {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
|
|||||||
@@ -1,476 +0,0 @@
|
|||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Hyprland
|
|
||||||
import Quickshell.Services.Pipewire
|
|
||||||
import Quickshell.Wayland
|
|
||||||
import Quickshell.Widgets
|
|
||||||
import QtQuick
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import QtQuick.Shapes
|
|
||||||
|
|
||||||
// A vertical sidebar carrying the "Slant" (launcher V6) visual language —
|
|
||||||
// chamfered panel, thick trapezoid bevel accents, an outward bracket, a
|
|
||||||
// floating triangle cap and a slanted divider. Right-anchored, mirrored.
|
|
||||||
Scope {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property bool active: true
|
|
||||||
|
|
||||||
function toggle() {
|
|
||||||
root.active = !root.active;
|
|
||||||
}
|
|
||||||
|
|
||||||
GlobalShortcut {
|
|
||||||
name: "sidebar"
|
|
||||||
description: "Toggle the Slant sidebar"
|
|
||||||
onPressed: root.toggle()
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property PwNode sink: Pipewire.defaultAudioSink
|
|
||||||
readonly property real volume: sink?.audio?.volume ?? 0
|
|
||||||
readonly property bool muted: sink?.audio?.muted ?? false
|
|
||||||
readonly property real maxVolume: 1.5
|
|
||||||
|
|
||||||
PwObjectTracker {
|
|
||||||
objects: [root.sink]
|
|
||||||
}
|
|
||||||
|
|
||||||
PanelWindow {
|
|
||||||
id: win
|
|
||||||
|
|
||||||
visible: root.active
|
|
||||||
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
anchors {
|
|
||||||
top: true
|
|
||||||
right: true
|
|
||||||
bottom: true
|
|
||||||
}
|
|
||||||
|
|
||||||
margins {
|
|
||||||
top: 8
|
|
||||||
}
|
|
||||||
|
|
||||||
// Frame width plus the gutter the outward bracket grows into.
|
|
||||||
readonly property int pad: 10
|
|
||||||
implicitWidth: 72 + 2 * pad
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: frame
|
|
||||||
|
|
||||||
// Inset so the outward bracket has room in the gutter.
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: win.pad
|
|
||||||
|
|
||||||
readonly property int chamfer: 18 // big cut corners (top-right, bottom-left)
|
|
||||||
readonly property int smallChamfer: 7 // small bevel (top-left, bottom-right)
|
|
||||||
readonly property int bevel: 4 // inward thickness of the trapezoid accents
|
|
||||||
readonly property int chunkThick: 7 // outward thickness of the bracket
|
|
||||||
readonly property int chunkSlant: 10 // slant of the bracket end pieces
|
|
||||||
readonly property int capSize: 10 // floating triangle cap
|
|
||||||
readonly property int armSide: 58 // bracket arm down the left edge
|
|
||||||
readonly property int armTop: 34 // bracket arm along the top edge
|
|
||||||
|
|
||||||
Shape {
|
|
||||||
id: panelShape
|
|
||||||
anchors.fill: parent
|
|
||||||
preferredRendererType: Shape.CurveRenderer
|
|
||||||
|
|
||||||
// Panel: big chamfers on top-right & bottom-left, small bevels
|
|
||||||
// on top-left & bottom-right (mirror of the left-anchored panel).
|
|
||||||
ShapePath {
|
|
||||||
fillColor: "#0F1012"
|
|
||||||
strokeColor: "#FFD063"
|
|
||||||
strokeWidth: 2
|
|
||||||
|
|
||||||
startX: panelShape.width - frame.chamfer
|
|
||||||
startY: 0
|
|
||||||
PathLine {
|
|
||||||
x: frame.smallChamfer
|
|
||||||
y: 0
|
|
||||||
}
|
|
||||||
PathLine {
|
|
||||||
x: 0
|
|
||||||
y: frame.smallChamfer
|
|
||||||
}
|
|
||||||
PathLine {
|
|
||||||
x: 0
|
|
||||||
y: panelShape.height - frame.chamfer
|
|
||||||
}
|
|
||||||
PathLine {
|
|
||||||
x: frame.chamfer
|
|
||||||
y: panelShape.height
|
|
||||||
}
|
|
||||||
PathLine {
|
|
||||||
x: panelShape.width - frame.smallChamfer
|
|
||||||
y: panelShape.height
|
|
||||||
}
|
|
||||||
PathLine {
|
|
||||||
x: panelShape.width
|
|
||||||
y: panelShape.height - frame.smallChamfer
|
|
||||||
}
|
|
||||||
PathLine {
|
|
||||||
x: panelShape.width
|
|
||||||
y: frame.chamfer
|
|
||||||
}
|
|
||||||
PathLine {
|
|
||||||
x: panelShape.width - frame.chamfer
|
|
||||||
y: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Thick top-right bevel accent (trapezoid to the edges).
|
|
||||||
ShapePath {
|
|
||||||
strokeWidth: 0
|
|
||||||
fillColor: "#FFD063"
|
|
||||||
|
|
||||||
startX: panelShape.width
|
|
||||||
startY: frame.chamfer
|
|
||||||
PathLine {
|
|
||||||
x: panelShape.width - frame.chamfer
|
|
||||||
y: 0
|
|
||||||
}
|
|
||||||
PathLine {
|
|
||||||
x: panelShape.width - frame.chamfer - 2 * frame.bevel
|
|
||||||
y: 0
|
|
||||||
}
|
|
||||||
PathLine {
|
|
||||||
x: panelShape.width
|
|
||||||
y: frame.chamfer + 2 * frame.bevel
|
|
||||||
}
|
|
||||||
PathLine {
|
|
||||||
x: panelShape.width
|
|
||||||
y: frame.chamfer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Thick bottom-left bevel accent.
|
|
||||||
ShapePath {
|
|
||||||
strokeWidth: 0
|
|
||||||
fillColor: "#FFD063"
|
|
||||||
|
|
||||||
startX: 0
|
|
||||||
startY: panelShape.height - frame.chamfer
|
|
||||||
PathLine {
|
|
||||||
x: frame.chamfer
|
|
||||||
y: panelShape.height
|
|
||||||
}
|
|
||||||
PathLine {
|
|
||||||
x: frame.chamfer + 2 * frame.bevel
|
|
||||||
y: panelShape.height
|
|
||||||
}
|
|
||||||
PathLine {
|
|
||||||
x: 0
|
|
||||||
y: panelShape.height - frame.chamfer - 2 * frame.bevel
|
|
||||||
}
|
|
||||||
PathLine {
|
|
||||||
x: 0
|
|
||||||
y: panelShape.height - frame.chamfer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Floating triangle cap in the bottom-left notch.
|
|
||||||
ShapePath {
|
|
||||||
strokeWidth: 0
|
|
||||||
fillColor: "#FFD063"
|
|
||||||
|
|
||||||
startX: 0
|
|
||||||
startY: panelShape.height
|
|
||||||
PathLine {
|
|
||||||
x: 0
|
|
||||||
y: panelShape.height - frame.capSize
|
|
||||||
}
|
|
||||||
PathLine {
|
|
||||||
x: frame.capSize
|
|
||||||
y: panelShape.height
|
|
||||||
}
|
|
||||||
PathLine {
|
|
||||||
x: 0
|
|
||||||
y: panelShape.height
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Outward bracket wrapping the top-left corner: down the left
|
|
||||||
// edge and along the top edge, with slanted ends and a beveled
|
|
||||||
// corner following the small chamfer.
|
|
||||||
ShapePath {
|
|
||||||
strokeWidth: 0
|
|
||||||
fillColor: "#FFD063"
|
|
||||||
|
|
||||||
startX: 0
|
|
||||||
startY: frame.armSide
|
|
||||||
PathLine {
|
|
||||||
x: -frame.chunkThick
|
|
||||||
y: frame.armSide - frame.chunkSlant
|
|
||||||
}
|
|
||||||
PathLine {
|
|
||||||
x: -frame.chunkThick
|
|
||||||
y: frame.smallChamfer
|
|
||||||
}
|
|
||||||
PathLine {
|
|
||||||
x: frame.smallChamfer
|
|
||||||
y: -frame.chunkThick
|
|
||||||
}
|
|
||||||
PathLine {
|
|
||||||
x: frame.armTop - frame.chunkSlant
|
|
||||||
y: -frame.chunkThick
|
|
||||||
}
|
|
||||||
PathLine {
|
|
||||||
x: frame.armTop
|
|
||||||
y: 0
|
|
||||||
}
|
|
||||||
PathLine {
|
|
||||||
x: frame.smallChamfer
|
|
||||||
y: 0
|
|
||||||
}
|
|
||||||
PathLine {
|
|
||||||
x: 0
|
|
||||||
y: frame.smallChamfer
|
|
||||||
}
|
|
||||||
PathLine {
|
|
||||||
x: 0
|
|
||||||
y: frame.armSide
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Content ────────────────────────────────────────────────────
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.topMargin: 16
|
|
||||||
anchors.bottomMargin: 16
|
|
||||||
anchors.leftMargin: 10
|
|
||||||
anchors.rightMargin: 8
|
|
||||||
spacing: 10
|
|
||||||
|
|
||||||
// Clock — Digital-7, hh over mm
|
|
||||||
Text {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
text: Qt.formatDateTime(clock.date, "hh\nmm")
|
|
||||||
font.family: "Digital-7 Mono"
|
|
||||||
font.pointSize: 22
|
|
||||||
font.letterSpacing: 1
|
|
||||||
color: "#EEEEEE"
|
|
||||||
|
|
||||||
SystemClock {
|
|
||||||
id: clock
|
|
||||||
precision: SystemClock.Minutes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Date
|
|
||||||
Text {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
text: Qt.formatDateTime(clock.date, "dd\nMMM").toUpperCase()
|
|
||||||
font.family: "Digital-7 Mono"
|
|
||||||
font.pointSize: 13
|
|
||||||
color: "#FFD063"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slanted divider
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
implicitHeight: 4
|
|
||||||
|
|
||||||
Shape {
|
|
||||||
id: divider
|
|
||||||
anchors.fill: parent
|
|
||||||
preferredRendererType: Shape.CurveRenderer
|
|
||||||
ShapePath {
|
|
||||||
strokeWidth: 0
|
|
||||||
fillColor: "#FFD063"
|
|
||||||
startX: 8
|
|
||||||
startY: 0
|
|
||||||
PathLine { x: divider.width; y: 0 }
|
|
||||||
PathLine { x: divider.width - 8; y: divider.height }
|
|
||||||
PathLine { x: 0; y: divider.height }
|
|
||||||
PathLine { x: 8; y: 0 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Volume — vertical meter with a sheared fill and a % readout
|
|
||||||
Text {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
text: "VOL"
|
|
||||||
font.family: "Digital-7 Mono"
|
|
||||||
font.pointSize: 10
|
|
||||||
color: "#7A7B7D"
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
spacing: 3
|
|
||||||
|
|
||||||
// Overflow — three floating slanted segments for volume
|
|
||||||
// pushed above 100%, lit with the same overload color as
|
|
||||||
// the volume OSD.
|
|
||||||
Repeater {
|
|
||||||
model: 3
|
|
||||||
|
|
||||||
delegate: Item {
|
|
||||||
id: seg
|
|
||||||
required property int index
|
|
||||||
|
|
||||||
readonly property real segStart: 1 + (2 - index) / 3 * (root.maxVolume - 1)
|
|
||||||
readonly property real segEnd: 1 + (3 - index) / 3 * (root.maxVolume - 1)
|
|
||||||
readonly property real frac: root.muted ? 0 : Math.max(0, Math.min(1, (root.volume - segStart) / (segEnd - segStart)))
|
|
||||||
readonly property int chamfer: 4
|
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
implicitWidth: 16
|
|
||||||
implicitHeight: 12
|
|
||||||
|
|
||||||
// Empty track, chamfered to match the main meter.
|
|
||||||
Shape {
|
|
||||||
anchors.fill: parent
|
|
||||||
preferredRendererType: Shape.CurveRenderer
|
|
||||||
|
|
||||||
ShapePath {
|
|
||||||
strokeWidth: 1
|
|
||||||
strokeColor: "#7A7B7D"
|
|
||||||
fillColor: "#292C30"
|
|
||||||
|
|
||||||
startX: seg.chamfer
|
|
||||||
startY: 0
|
|
||||||
PathLine { x: seg.width; y: 0 }
|
|
||||||
PathLine { x: seg.width; y: seg.height - seg.chamfer }
|
|
||||||
PathLine { x: seg.width - seg.chamfer; y: seg.height }
|
|
||||||
PathLine { x: 0; y: seg.height }
|
|
||||||
PathLine { x: 0; y: seg.chamfer }
|
|
||||||
PathLine { x: seg.chamfer; y: 0 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overload fill — reveals a fixed copy of the same
|
|
||||||
// chamfered hexagon from the bottom, so filled and
|
|
||||||
// empty states always share one silhouette.
|
|
||||||
Item {
|
|
||||||
id: segFillClip
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: 2
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: 2
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.bottomMargin: 2
|
|
||||||
clip: true
|
|
||||||
height: seg.frac * (seg.height - 4)
|
|
||||||
|
|
||||||
Shape {
|
|
||||||
id: segFill
|
|
||||||
width: seg.width - 4
|
|
||||||
height: seg.height - 4
|
|
||||||
y: segFillClip.height - height
|
|
||||||
preferredRendererType: Shape.CurveRenderer
|
|
||||||
|
|
||||||
readonly property int chamfer: seg.chamfer - 2
|
|
||||||
|
|
||||||
ShapePath {
|
|
||||||
strokeWidth: 0
|
|
||||||
fillColor: "#FF6B4A"
|
|
||||||
startX: segFill.chamfer
|
|
||||||
startY: 0
|
|
||||||
PathLine { x: segFill.width; y: 0 }
|
|
||||||
PathLine { x: segFill.width; y: segFill.height - segFill.chamfer }
|
|
||||||
PathLine { x: segFill.width - segFill.chamfer; y: segFill.height }
|
|
||||||
PathLine { x: 0; y: segFill.height }
|
|
||||||
PathLine { x: 0; y: segFill.chamfer }
|
|
||||||
PathLine { x: segFill.chamfer; y: 0 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: volMeter
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
implicitWidth: 16
|
|
||||||
implicitHeight: 96
|
|
||||||
|
|
||||||
readonly property int chamfer: 6
|
|
||||||
|
|
||||||
// Track — chamfered top-left/bottom-right to match the
|
|
||||||
// panel's slant, so the sheared fill sits in a shape that
|
|
||||||
// agrees with it rather than a plain rectangle.
|
|
||||||
Shape {
|
|
||||||
anchors.fill: parent
|
|
||||||
preferredRendererType: Shape.CurveRenderer
|
|
||||||
|
|
||||||
ShapePath {
|
|
||||||
strokeWidth: 1
|
|
||||||
strokeColor: "#7A7B7D"
|
|
||||||
fillColor: "#292C30"
|
|
||||||
|
|
||||||
startX: volMeter.chamfer
|
|
||||||
startY: 0
|
|
||||||
PathLine { x: volMeter.width; y: 0 }
|
|
||||||
PathLine { x: volMeter.width; y: volMeter.height - volMeter.chamfer }
|
|
||||||
PathLine { x: volMeter.width - volMeter.chamfer; y: volMeter.height }
|
|
||||||
PathLine { x: 0; y: volMeter.height }
|
|
||||||
PathLine { x: 0; y: volMeter.chamfer }
|
|
||||||
PathLine { x: volMeter.chamfer; y: 0 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accent fill — reveals a fixed copy of the same
|
|
||||||
// chamfered hexagon as the track from the bottom, so
|
|
||||||
// the fill and background always share one silhouette.
|
|
||||||
Item {
|
|
||||||
id: fillClip
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: 2
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: 2
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.bottomMargin: 2
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
readonly property real frac: root.muted ? 0 : Math.min(root.volume, 1)
|
|
||||||
height: frac * (volMeter.height - 4)
|
|
||||||
|
|
||||||
Shape {
|
|
||||||
id: vol
|
|
||||||
width: volMeter.width - 4
|
|
||||||
height: volMeter.height - 4
|
|
||||||
y: fillClip.height - height
|
|
||||||
preferredRendererType: Shape.CurveRenderer
|
|
||||||
|
|
||||||
readonly property int chamfer: volMeter.chamfer - 2
|
|
||||||
|
|
||||||
ShapePath {
|
|
||||||
strokeWidth: 0
|
|
||||||
fillColor: root.muted ? "#7A7B7D" : "#FFD063"
|
|
||||||
startX: vol.chamfer
|
|
||||||
startY: 0
|
|
||||||
PathLine { x: vol.width; y: 0 }
|
|
||||||
PathLine { x: vol.width; y: vol.height - vol.chamfer }
|
|
||||||
PathLine { x: vol.width - vol.chamfer; y: vol.height }
|
|
||||||
PathLine { x: 0; y: vol.height }
|
|
||||||
PathLine { x: 0; y: vol.chamfer }
|
|
||||||
PathLine { x: vol.chamfer; y: 0 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
text: root.muted ? "--" : Math.round(root.volume * 100)
|
|
||||||
font.family: "Digital-7 Mono"
|
|
||||||
font.pointSize: 14
|
|
||||||
color: root.muted ? "#7A7B7D" : "#EEEEEE"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,10 +6,11 @@ import Quickshell.Widgets
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import QtQuick.Shapes
|
import QtQuick.Shapes
|
||||||
|
import qs.widgets.theme
|
||||||
|
|
||||||
// The right bar: a compact utility strip for the SystemTray icons, styled
|
// The right bar: a compact utility strip for the SystemTray icons, styled
|
||||||
// in the same family as the Launcher (V6 "Slant") and SideBar — accent
|
// in the same family as the Launcher (V6 "Slant") and SideBar — accent
|
||||||
// color, chamfered corners, Digital-7 Mono, the slash-trio motif — but with
|
// color, chamfered corners, the readout font, the slash-trio motif — but with
|
||||||
// its own plain, evenly-chamfered panel rather than their ornate
|
// its own plain, evenly-chamfered panel rather than their ornate
|
||||||
// bracket-and-cap silhouette, since this is a secondary/utility bar rather
|
// bracket-and-cap silhouette, since this is a secondary/utility bar rather
|
||||||
// than a hero surface.
|
// than a hero surface.
|
||||||
@@ -54,8 +55,8 @@ Scope {
|
|||||||
preferredRendererType: Shape.CurveRenderer
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
fillColor: "#0F1012"
|
fillColor: Theme.surface
|
||||||
strokeColor: "#FFD063"
|
strokeColor: Theme.accent
|
||||||
strokeWidth: 2
|
strokeWidth: 2
|
||||||
|
|
||||||
startX: frame.chamfer
|
startX: frame.chamfer
|
||||||
@@ -114,7 +115,7 @@ Scope {
|
|||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
startX: 6
|
startX: 6
|
||||||
startY: 0
|
startY: 0
|
||||||
PathLine { x: 10; y: 0 }
|
PathLine { x: 10; y: 0 }
|
||||||
@@ -124,7 +125,7 @@ Scope {
|
|||||||
}
|
}
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
startX: 15
|
startX: 15
|
||||||
startY: 0
|
startY: 0
|
||||||
PathLine { x: 19; y: 0 }
|
PathLine { x: 19; y: 0 }
|
||||||
@@ -134,7 +135,7 @@ Scope {
|
|||||||
}
|
}
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
startX: 24
|
startX: 24
|
||||||
startY: 0
|
startY: 0
|
||||||
PathLine { x: 28; y: 0 }
|
PathLine { x: 28; y: 0 }
|
||||||
@@ -155,7 +156,7 @@ Scope {
|
|||||||
preferredRendererType: Shape.CurveRenderer
|
preferredRendererType: Shape.CurveRenderer
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
startX: 8
|
startX: 8
|
||||||
startY: 0
|
startY: 0
|
||||||
PathLine { x: divider.width; y: 0 }
|
PathLine { x: divider.width; y: 0 }
|
||||||
@@ -169,9 +170,9 @@ Scope {
|
|||||||
Text {
|
Text {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
text: "TRAY"
|
text: "TRAY"
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
}
|
}
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
@@ -201,17 +202,17 @@ Scope {
|
|||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
visible: SystemTray.items.values.length === 0
|
visible: SystemTray.items.values.length === 0
|
||||||
text: "--"
|
text: "--"
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 14
|
font.pointSize: 14
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
text: SystemTray.items.values.length
|
text: SystemTray.items.values.length
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 16
|
font.pointSize: 16
|
||||||
color: "#EEEEEE"
|
color: Theme.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import Quickshell
|
|||||||
import Quickshell.Services.SystemTray
|
import Quickshell.Services.SystemTray
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Shapes
|
import QtQuick.Shapes
|
||||||
|
import qs.widgets.theme
|
||||||
|
|
||||||
// A single tray icon, chamfered to match the Slant (launcher V6 / SideBar)
|
// A single tray icon, chamfered to match the Slant (launcher V6 / SideBar)
|
||||||
// visual language instead of a plain rectangular hit target.
|
// visual language instead of a plain rectangular hit target.
|
||||||
@@ -35,8 +36,8 @@ MouseArea {
|
|||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 1
|
strokeWidth: 1
|
||||||
strokeColor: root.containsMouse ? "#FFD063" : "#7A7B7D"
|
strokeColor: root.containsMouse ? Theme.accent : Theme.muted
|
||||||
fillColor: "#292C30"
|
fillColor: Theme.raised
|
||||||
|
|
||||||
startX: root.chamfer
|
startX: root.chamfer
|
||||||
startY: 0
|
startY: 0
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
// Single source of truth for the shell's palette and font families.
|
||||||
|
//
|
||||||
|
// The shell previously ran two unrelated palettes: an amber one (#FFD063) used
|
||||||
|
// by the launchers, sidebar, systray, vitals and notifications, and an orange
|
||||||
|
// one (#e8722a) that only the dense bar had, tokenized as per-file properties.
|
||||||
|
// This unifies on the ORANGE values under the AMBER naming scheme.
|
||||||
|
//
|
||||||
|
// `surface` deliberately takes the dense bar's void (#0a0a0a) rather than the
|
||||||
|
// old panel background (#0F1012), which also absorbs the near-identical
|
||||||
|
// #0A0A0C scrim.
|
||||||
|
Singleton {
|
||||||
|
// ---- core ----
|
||||||
|
readonly property color accent: "#e8722a" // was #FFD063 (amber) / #e8722a (bar)
|
||||||
|
readonly property color text: "#dedede" // was #EEEEEE / #dedede
|
||||||
|
readonly property color muted: "#858585" // was #7A7B7D / #858585
|
||||||
|
readonly property color surface: "#0a0a0a" // was #0F1012 + #0A0A0C + #0a0a0a
|
||||||
|
readonly property color hot: "#ff6b4a" // alert/hot; no bar equivalent, kept
|
||||||
|
|
||||||
|
// ---- supporting darks ----
|
||||||
|
// A three-step ramp above `surface`. `raised` also absorbs #22262C, which
|
||||||
|
// differed from #292C30 by an imperceptible amount across two call sites.
|
||||||
|
readonly property color selection: "#1a1c1f" // selected row fill
|
||||||
|
readonly property color raised: "#292c30" // raised surface / border
|
||||||
|
readonly property color disabled: "#3a3d42" // unknown / disabled stroke
|
||||||
|
|
||||||
|
// ---- accents ----
|
||||||
|
// The pale "flash" the top/bottom bars show while a launcher is open. Was a
|
||||||
|
// hand-picked #FFF3C0 against amber; derived here so it tracks `accent`.
|
||||||
|
// 55% toward white reproduces the original amber relationship closely
|
||||||
|
// (#FFD063 -> #FFE9B8 vs the hand-picked #FFF3C0).
|
||||||
|
readonly property color accentSoft: Qt.tint(accent, Qt.rgba(1, 1, 1, 0.55))
|
||||||
|
readonly property color highlight: "#ffffff"
|
||||||
|
|
||||||
|
// ---- fonts ----
|
||||||
|
// Two faces. `readoutFont` is an alias rather than a second literal so the
|
||||||
|
// two roles cannot silently drift apart; point it at a different family if
|
||||||
|
// the readouts should ever diverge from the headings again.
|
||||||
|
//
|
||||||
|
// Installed by services/desktop/desktop-apps.nix (nerd-fonts.departure-mono).
|
||||||
|
// The former readout face, Digital-7 Mono, was never packaged — it relied on
|
||||||
|
// a manual ~/.dots/fonts/digital_7 install, so dropping it also removes an
|
||||||
|
// undeclared external dependency.
|
||||||
|
readonly property string displayFont: "DepartureMono Nerd Font" // headings, large values
|
||||||
|
readonly property string readoutFont: displayFont // seven-segment readouts: launchers, sidebar, systray, vitals
|
||||||
|
readonly property string microFont: "DejaVu Sans Mono" // dense bar micro labels
|
||||||
|
|
||||||
|
// ---- derived alpha variants ----
|
||||||
|
// The dense bar hand-encoded these as Qt.rgba(0.87,0.87,0.87,a) = text and
|
||||||
|
// Qt.rgba(0.91,0.45,0.16,a) = accent. Expressed as functions so the
|
||||||
|
// relationship survives a palette change.
|
||||||
|
function textAlpha(a) { return Qt.rgba(text.r, text.g, text.b, a); }
|
||||||
|
function accentAlpha(a) { return Qt.rgba(accent.r, accent.g, accent.b, a); }
|
||||||
|
|
||||||
|
// Hairline rule / panel outline: text at 28%.
|
||||||
|
readonly property color hair: textAlpha(0.28)
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Shapes
|
import QtQuick.Shapes
|
||||||
|
import qs.widgets.theme
|
||||||
|
|
||||||
// Horizontal meter in the Slant language: a chamfered track whose fill is a
|
// Horizontal meter in the Slant language: a chamfered track whose fill is a
|
||||||
// clipped copy of the SAME hexagon, so empty and full always share one
|
// clipped copy of the SAME hexagon, so empty and full always share one
|
||||||
@@ -26,8 +27,8 @@ Item {
|
|||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 1
|
strokeWidth: 1
|
||||||
strokeColor: bar.unknown ? "#3A3D42" : "#7A7B7D"
|
strokeColor: bar.unknown ? Theme.disabled : Theme.muted
|
||||||
fillColor: "#292C30"
|
fillColor: Theme.raised
|
||||||
|
|
||||||
startX: bar.chamfer
|
startX: bar.chamfer
|
||||||
startY: 0
|
startY: 0
|
||||||
@@ -72,7 +73,7 @@ Item {
|
|||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: bar.hot ? "#FF6B4A" : "#FFD063"
|
fillColor: bar.hot ? Theme.hot : Theme.accent
|
||||||
|
|
||||||
Behavior on fillColor {
|
Behavior on fillColor {
|
||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import Quickshell.Wayland
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import QtQuick.Shapes
|
import QtQuick.Shapes
|
||||||
|
import qs.widgets.theme
|
||||||
|
|
||||||
// Host vitals HUD — the "Slant" language (chamfered panel, trapezoid bevels,
|
// Host vitals HUD — the "Slant" language (chamfered panel, trapezoid bevels,
|
||||||
// outward corner chunks, floating cap, slanted dividers) carried over from the
|
// outward corner chunks, floating cap, slanted dividers) carried over from the
|
||||||
@@ -59,7 +60,7 @@ Scope {
|
|||||||
// Dim backdrop — click anywhere to dismiss.
|
// Dim backdrop — click anywhere to dismiss.
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: "#0A0A0C"
|
color: Theme.surface
|
||||||
opacity: 0.5
|
opacity: 0.5
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
@@ -102,8 +103,8 @@ Scope {
|
|||||||
preferredRendererType: Shape.CurveRenderer
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
fillColor: "#0F1012"
|
fillColor: Theme.surface
|
||||||
strokeColor: "#FFD063"
|
strokeColor: Theme.accent
|
||||||
strokeWidth: 2
|
strokeWidth: 2
|
||||||
|
|
||||||
startX: frame.chamfer
|
startX: frame.chamfer
|
||||||
@@ -121,7 +122,7 @@ Scope {
|
|||||||
// Thick top-left bevel accent.
|
// Thick top-left bevel accent.
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
|
|
||||||
startX: 0
|
startX: 0
|
||||||
startY: frame.chamfer
|
startY: frame.chamfer
|
||||||
@@ -134,7 +135,7 @@ Scope {
|
|||||||
// Thick bottom-right bevel accent.
|
// Thick bottom-right bevel accent.
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
|
|
||||||
startX: panelShape.width
|
startX: panelShape.width
|
||||||
startY: panelShape.height - frame.chamfer
|
startY: panelShape.height - frame.chamfer
|
||||||
@@ -147,7 +148,7 @@ Scope {
|
|||||||
// Floating triangle cap in the bottom-right notch.
|
// Floating triangle cap in the bottom-right notch.
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
|
|
||||||
startX: panelShape.width
|
startX: panelShape.width
|
||||||
startY: panelShape.height
|
startY: panelShape.height
|
||||||
@@ -159,7 +160,7 @@ Scope {
|
|||||||
// Heavy outward chunk wrapping the bottom-left corner.
|
// Heavy outward chunk wrapping the bottom-left corner.
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
|
|
||||||
startX: panelShape.width / 3
|
startX: panelShape.width / 3
|
||||||
startY: panelShape.height
|
startY: panelShape.height
|
||||||
@@ -176,7 +177,7 @@ Scope {
|
|||||||
// Heavy outward chunk wrapping the top-right corner.
|
// Heavy outward chunk wrapping the top-right corner.
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
|
|
||||||
startX: panelShape.width
|
startX: panelShape.width
|
||||||
startY: panelShape.height / 3
|
startY: panelShape.height / 3
|
||||||
@@ -217,7 +218,7 @@ Scope {
|
|||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
startX: 6
|
startX: 6
|
||||||
startY: 0
|
startY: 0
|
||||||
PathLine { x: 10; y: 0 }
|
PathLine { x: 10; y: 0 }
|
||||||
@@ -227,7 +228,7 @@ Scope {
|
|||||||
}
|
}
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
startX: 15
|
startX: 15
|
||||||
startY: 0
|
startY: 0
|
||||||
PathLine { x: 19; y: 0 }
|
PathLine { x: 19; y: 0 }
|
||||||
@@ -237,7 +238,7 @@ Scope {
|
|||||||
}
|
}
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
startX: 24
|
startX: 24
|
||||||
startY: 0
|
startY: 0
|
||||||
PathLine { x: 28; y: 0 }
|
PathLine { x: 28; y: 0 }
|
||||||
@@ -249,8 +250,8 @@ Scope {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: (vitals.host || "vitals").toUpperCase()
|
text: (vitals.host || "vitals").toUpperCase()
|
||||||
color: "#EEEEEE"
|
color: Theme.text
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 20
|
font.pointSize: 20
|
||||||
font.letterSpacing: 2
|
font.letterSpacing: 2
|
||||||
}
|
}
|
||||||
@@ -261,15 +262,15 @@ Scope {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "UP"
|
text: "UP"
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 11
|
font.pointSize: 11
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: vitals.fmtUptime(vitals.uptime)
|
text: vitals.fmtUptime(vitals.uptime)
|
||||||
color: "#FFD063"
|
color: Theme.accent
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 16
|
font.pointSize: 16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -281,8 +282,8 @@ Scope {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
visible: vitals.failed
|
visible: vitals.failed
|
||||||
text: "NODE_EXPORTER UNREACHABLE ON :" + vitals.port
|
text: "NODE_EXPORTER UNREACHABLE ON :" + vitals.port
|
||||||
color: "#FF6B4A"
|
color: Theme.hot
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 13
|
font.pointSize: 13
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,7 +354,7 @@ Scope {
|
|||||||
Text {
|
Text {
|
||||||
Layout.preferredWidth: 96
|
Layout.preferredWidth: 96
|
||||||
text: diskRow.modelData.mount
|
text: diskRow.modelData.mount
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font.pointSize: 9
|
font.pointSize: 9
|
||||||
elide: Text.ElideMiddle
|
elide: Text.ElideMiddle
|
||||||
}
|
}
|
||||||
@@ -369,8 +370,8 @@ Scope {
|
|||||||
Layout.preferredWidth: 48
|
Layout.preferredWidth: 48
|
||||||
horizontalAlignment: Text.AlignRight
|
horizontalAlignment: Text.AlignRight
|
||||||
text: Math.round(diskRow.frac * 100) + "%"
|
text: Math.round(diskRow.frac * 100) + "%"
|
||||||
color: diskRow.frac >= 0.9 ? "#FF6B4A" : "#EEEEEE"
|
color: diskRow.frac >= 0.9 ? Theme.hot : Theme.text
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 13
|
font.pointSize: 13
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,8 +379,8 @@ Scope {
|
|||||||
Layout.preferredWidth: 104
|
Layout.preferredWidth: 104
|
||||||
horizontalAlignment: Text.AlignRight
|
horizontalAlignment: Text.AlignRight
|
||||||
text: vitals.fmtBytes(diskRow.modelData.size - diskRow.modelData.used) + " FREE"
|
text: vitals.fmtBytes(diskRow.modelData.size - diskRow.modelData.used) + " FREE"
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 11
|
font.pointSize: 11
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -399,21 +400,21 @@ Scope {
|
|||||||
Text {
|
Text {
|
||||||
Layout.preferredWidth: 96
|
Layout.preferredWidth: 96
|
||||||
text: vitals.netIface || "NET"
|
text: vitals.netIface || "NET"
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font.pointSize: 9
|
font.pointSize: 9
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "RX"
|
text: "RX"
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 11
|
font.pointSize: 11
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: vitals.fmtRate(vitals.netRx)
|
text: vitals.fmtRate(vitals.netRx)
|
||||||
color: "#FFD063"
|
color: Theme.accent
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 14
|
font.pointSize: 14
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,15 +424,15 @@ Scope {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "TX"
|
text: "TX"
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 11
|
font.pointSize: 11
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: vitals.fmtRate(vitals.netTx)
|
text: vitals.fmtRate(vitals.netTx)
|
||||||
color: "#FFD063"
|
color: Theme.accent
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 14
|
font.pointSize: 14
|
||||||
|
|
||||||
// Right-align the TX readout against the panel edge the
|
// Right-align the TX readout against the panel edge the
|
||||||
@@ -469,8 +470,8 @@ Scope {
|
|||||||
Text {
|
Text {
|
||||||
Layout.preferredWidth: 46
|
Layout.preferredWidth: 46
|
||||||
text: metric.label
|
text: metric.label
|
||||||
color: "#FFD063"
|
color: Theme.accent
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 15
|
font.pointSize: 15
|
||||||
font.letterSpacing: 1
|
font.letterSpacing: 1
|
||||||
}
|
}
|
||||||
@@ -486,8 +487,8 @@ Scope {
|
|||||||
Layout.preferredWidth: 54
|
Layout.preferredWidth: 54
|
||||||
horizontalAlignment: Text.AlignRight
|
horizontalAlignment: Text.AlignRight
|
||||||
text: metric.readout
|
text: metric.readout
|
||||||
color: "#EEEEEE"
|
color: Theme.text
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 16
|
font.pointSize: 16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -499,8 +500,8 @@ Scope {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: metric.detail
|
text: metric.detail
|
||||||
color: "#7A7B7D"
|
color: Theme.muted
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 11
|
font.pointSize: 11
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -511,8 +512,8 @@ Scope {
|
|||||||
Text {
|
Text {
|
||||||
visible: metric.aside.length > 0
|
visible: metric.aside.length > 0
|
||||||
text: metric.aside
|
text: metric.aside
|
||||||
color: metric.asideHot ? "#FF6B4A" : "#7A7B7D"
|
color: metric.asideHot ? Theme.hot : Theme.muted
|
||||||
font.family: "Digital-7 Mono"
|
font.family: Theme.readoutFont
|
||||||
font.pointSize: 11
|
font.pointSize: 11
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -530,7 +531,7 @@ Scope {
|
|||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: "#FFD063"
|
fillColor: Theme.accent
|
||||||
startX: 6
|
startX: 6
|
||||||
startY: 0
|
startY: 0
|
||||||
PathLine { x: divider.width; y: 0 }
|
PathLine { x: divider.width; y: 0 }
|
||||||
|
|||||||
Generated
+18
-18
@@ -14,11 +14,11 @@
|
|||||||
"uv2nix": "uv2nix"
|
"uv2nix": "uv2nix"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1786986906,
|
"lastModified": 1787577519,
|
||||||
"narHash": "sha256-DJ1oU9szQJNdEM0dysh4NnKOB1HwOKtNukrUYKpawVs=",
|
"narHash": "sha256-YNAXQTgR26RJiX2vtYjk6OtBu2jMWeq4qUN6sUrt6Lc=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "authentik-nix",
|
"repo": "authentik-nix",
|
||||||
"rev": "afdb2eeca1e0b38fabb93c4a8944be73d3581268",
|
"rev": "30c37930450d7a5fefa8ffec613f037fc75c3071",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -270,11 +270,11 @@
|
|||||||
"treefmt-nix": "treefmt-nix"
|
"treefmt-nix": "treefmt-nix"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1787124618,
|
"lastModified": 1787728766,
|
||||||
"narHash": "sha256-aKf1k2hvYgaxP9oxDPRiv9npEJLODC9eKxk7nR69lzQ=",
|
"narHash": "sha256-g2oZlrBU3AI2ubCiY/UyE9ALTIDueTcF//QP3vaY9IQ=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "nixos-anywhere",
|
"repo": "nixos-anywhere",
|
||||||
"rev": "ad8fa24e11eef167fd72d49fafefa3f840312d71",
|
"rev": "6b77f26ec4538ced04bf1d02f374b0ec02e9c27e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -291,11 +291,11 @@
|
|||||||
"nixos-unstable": "nixos-unstable"
|
"nixos-unstable": "nixos-unstable"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1787222173,
|
"lastModified": 1787826771,
|
||||||
"narHash": "sha256-acp6QJnWVnLvnanC59CMkiDC/i0ZhdFKiB7prru9SHw=",
|
"narHash": "sha256-gWkyr3I/cg4SHWGkAoUo24+BQaqoi+S0T2JxDjsA+pw=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "nixos-images",
|
"repo": "nixos-images",
|
||||||
"rev": "e17386d9193d6d5a90f1b4b6a8a5cd2620d34b56",
|
"rev": "f6714acc84ce92df7286c89a571ad1e946057a5b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -354,11 +354,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-unstable": {
|
"nixpkgs-unstable": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1787209939,
|
"lastModified": 1787814960,
|
||||||
"narHash": "sha256-WvvHR4kSQLAbtouMC/ruZ5UpLwlUcY3K4FAllMN+yGk=",
|
"narHash": "sha256-PYZq1qzCJXC2zGI0mH07vrZBsw6DRBAOX0jN1pPtqOQ=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "391b592eb44808b3bd0cb80bb71b63a5a118b8bb",
|
"rev": "c27cdad491a991b11ed731760aa2ef8db0cb0410",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -370,11 +370,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1787204541,
|
"lastModified": 1787753485,
|
||||||
"narHash": "sha256-OURZPknrTjQrlNyxPdqzyqmU/81Wes1CUP/Ft1Rv/YI=",
|
"narHash": "sha256-BZWCi9ZRJiARTuKTbbtvFTj7t1TK4G3UEckT3HyNfRg=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "5880666fd9eb563038431edb35c2d0aa595884e6",
|
"rev": "062346a6d85bc4b49dfaa61c986e9c5be21217d1",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -400,11 +400,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1785730568,
|
"lastModified": 1786936886,
|
||||||
"narHash": "sha256-NjSPsgjJ7MSpBtTkUcmNhRe6AFZ96+zsca2M8YuQi8Y=",
|
"narHash": "sha256-8SFyOdmcG6Nsh/JzlH812lTI/v+ur06fpQhj/acNn8k=",
|
||||||
"owner": "pyproject-nix",
|
"owner": "pyproject-nix",
|
||||||
"repo": "build-system-pkgs",
|
"repo": "build-system-pkgs",
|
||||||
"rev": "90fde00db3687922d39d95fc591475fd0bbbcd72",
|
"rev": "90ffdeee1a4929b231913df067448cd9803d3e07",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -552,5 +552,113 @@
|
|||||||
machine.crash()
|
machine.crash()
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# `nix develop` — hot-reload loop for dotfiles/quickshell.
|
||||||
|
#
|
||||||
|
# hosts/terra/home.nix ships the shell via `xdg.configFile."quickshell"`,
|
||||||
|
# which COPIES the tree into the store, so ~/.config/quickshell is a
|
||||||
|
# read-only symlink into /nix/store and every QML tweak costs a
|
||||||
|
# nixos-rebuild. quickshell DOES hot-reload on file save — but only for
|
||||||
|
# the files it is watching, which are those frozen store copies. Pointing
|
||||||
|
# it at the working tree with `qs -p` restores edit-save-see, no rebuild.
|
||||||
|
#
|
||||||
|
# quickshell keys instance identity on the CONFIG PATH, so a working-tree
|
||||||
|
# instance and the store-backed one are two different instances that would
|
||||||
|
# both map layer-shell bars onto every output. Hence a swap, not a second
|
||||||
|
# instance — and the swap starts dev FIRST, killing the packaged shell
|
||||||
|
# only once dev is confirmed up, so a QML error in the working tree leaves
|
||||||
|
# you on your normal bar instead of no bar at all.
|
||||||
|
#
|
||||||
|
# Every kill is scoped to one config (`qs kill` = default only, `qs kill
|
||||||
|
# -p` = that path only). A blanket kill would also take out unrelated
|
||||||
|
# quickshell instances — pkgs/rishot.nix is one.
|
||||||
|
#
|
||||||
|
# Deliberately NOT wired to direnv (no .envrc in this repo): programs.direnv
|
||||||
|
# is enabled for this user, so a `use flake` would swap the running desktop
|
||||||
|
# shell on every `cd` into the checkout, including over ssh.
|
||||||
|
devShells.${system}.default =
|
||||||
|
let
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
# Same nixpkgs terra's home.nix takes pkgs.quickshell from, so the dev
|
||||||
|
# instance is the identical build to the packaged one.
|
||||||
|
qs = "${nixpkgs.legacyPackages.${system}.quickshell}/bin/qs";
|
||||||
|
git = "${nixpkgs.legacyPackages.${system}.git}/bin/git";
|
||||||
|
|
||||||
|
# Resolved at RUN time, not build time: the entire point is to run the
|
||||||
|
# working tree, and `self` here is only a store snapshot of it.
|
||||||
|
preamble = ''
|
||||||
|
root="$(${git} rev-parse --show-toplevel 2>/dev/null || pwd)"
|
||||||
|
cfg="$root/dotfiles/quickshell"
|
||||||
|
if [ ! -f "$cfg/shell.qml" ]; then
|
||||||
|
echo "no shell.qml under $cfg — run this from the homelab checkout" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# `qs list` exits 0 whether or not it found anything, and only emits
|
||||||
|
# json when it did — so "json came back" is the liveness test.
|
||||||
|
running() { ${qs} list -p "$1" -j 2>/dev/null | grep -q '"id"'; }
|
||||||
|
prod_running() { ${qs} list -j 2>/dev/null | grep -q '"id"'; }
|
||||||
|
'';
|
||||||
|
|
||||||
|
qs-dev = pkgs.writeShellScriptBin "qs-dev" ''
|
||||||
|
set -uo pipefail
|
||||||
|
${preamble}
|
||||||
|
|
||||||
|
if [ -z "''${WAYLAND_DISPLAY:-}" ]; then
|
||||||
|
echo "qs-dev: no WAYLAND_DISPLAY — refusing to swap the desktop shell" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if running "$cfg"; then
|
||||||
|
echo "qs-dev: already running from $cfg"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
${qs} -d -p "$cfg"
|
||||||
|
|
||||||
|
# Confirm it came up before touching the packaged shell.
|
||||||
|
for _ in $(seq 1 50); do
|
||||||
|
running "$cfg" && break
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! running "$cfg"; then
|
||||||
|
echo "qs-dev: dev shell failed to start — packaged shell left alone" >&2
|
||||||
|
echo "qs-dev: run 'qs -p $cfg' in the foreground to see the QML error" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
${qs} kill || true
|
||||||
|
echo "qs-dev: live on $cfg — edits there now hot-reload"
|
||||||
|
'';
|
||||||
|
|
||||||
|
qs-prod = pkgs.writeShellScriptBin "qs-prod" ''
|
||||||
|
set -uo pipefail
|
||||||
|
${preamble}
|
||||||
|
|
||||||
|
running "$cfg" && ${qs} kill -p "$cfg" || true
|
||||||
|
prod_running || ${qs} -d
|
||||||
|
echo "qs-prod: back on ~/.config/quickshell"
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
pkgs.mkShell {
|
||||||
|
packages = [ pkgs.quickshell qs-dev qs-prod ];
|
||||||
|
|
||||||
|
# Swap on entry, swap back on exit. Three guards:
|
||||||
|
# - interactive only ($- has i). `nix develop --command X` EXECs X,
|
||||||
|
# replacing the shell that set the trap, so the restore would
|
||||||
|
# never run and you'd be left on the dev instance. Non-interactive
|
||||||
|
# use gets the explicit `nix develop -c qs-dev` instead.
|
||||||
|
# - WAYLAND_DISPLAY, so entering the shell over ssh cannot kill the
|
||||||
|
# desktop's bar and leave nothing in its place.
|
||||||
|
# - a sentinel, so a nested `nix develop` does not swap (and then
|
||||||
|
# restore) a second time.
|
||||||
|
shellHook = ''
|
||||||
|
if [[ $- == *i* ]] && [ -n "''${WAYLAND_DISPLAY:-}" ] && [ -z "''${HOMELAB_QS_DEV:-}" ]; then
|
||||||
|
export HOMELAB_QS_DEV=1
|
||||||
|
qs-dev && trap qs-prod EXIT
|
||||||
|
fi
|
||||||
|
echo "homelab devshell — qs-dev (working tree) / qs-prod (packaged); exit restores"
|
||||||
|
'';
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ in
|
|||||||
pkgs.hyprcursor
|
pkgs.hyprcursor
|
||||||
pkgs.bibata-cursors
|
pkgs.bibata-cursors
|
||||||
pkgs.papirus-icon-theme
|
pkgs.papirus-icon-theme
|
||||||
tome
|
|
||||||
];
|
];
|
||||||
|
|
||||||
xdg.desktopEntries.btop = {
|
xdg.desktopEntries.btop = {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ let
|
|||||||
# here rather than at runtime, since hyprpaper has no built-in "random"
|
# here rather than at runtime, since hyprpaper has no built-in "random"
|
||||||
# mode; re-pick and rebuild (or swap in real per-monitor selection) when
|
# mode; re-pick and rebuild (or swap in real per-monitor selection) when
|
||||||
# this stops being a placeholder.
|
# this stops being a placeholder.
|
||||||
wallpaper = "/mnt/hdd_01/data/Pictures/Wallhaven/wallhaven-4yjyd4.png";
|
wallpaper = "/mnt/hdd_01/data/Pictures/Wallhaven/wallhaven-ym81rl.png";
|
||||||
|
|
||||||
# Dispatchers → the new hl.dsp.* API (signatures verified against hyprland
|
# Dispatchers → the new hl.dsp.* API (signatures verified against hyprland
|
||||||
# 0.55's src/config/lua/bindings/LuaBindingsDispatchers.cpp).
|
# 0.55's src/config/lua/bindings/LuaBindingsDispatchers.cpp).
|
||||||
@@ -198,8 +198,12 @@ in
|
|||||||
|
|
||||||
# quickshell app-launcher variants (evaluating — pick one)
|
# quickshell app-launcher variants (evaluating — pick one)
|
||||||
]
|
]
|
||||||
++ (map (n: bind "SUPER + CTRL + ${toString n}" (dsp.global "quickshell:launcher${toString n}")) (lib.range 1 7))
|
++ (map (n: bind "SUPER + CTRL + ${toString n}" (dsp.global "quickshell:launcher${toString n}")) (lib.range 1 9))
|
||||||
++ [
|
++ [
|
||||||
|
# variant 10 (CTRL+0 is already the quickshell restart binding below)
|
||||||
|
(bind "SUPER + CTRL + SHIFT + 0" (dsp.global "quickshell:launcher10"))
|
||||||
|
# variant 11 cyber dock
|
||||||
|
(bind "SUPER + CTRL + SHIFT + D" (dsp.global "quickshell:launcher11"))
|
||||||
# restart quickshell (also starts it if not running)
|
# restart quickshell (also starts it if not running)
|
||||||
(bind "SUPER + CTRL + 0" (dsp.exec "qs kill; sleep 0.3; qs"))
|
(bind "SUPER + CTRL + 0" (dsp.exec "qs kill; sleep 0.3; qs"))
|
||||||
# toggle the Slant sidebar
|
# toggle the Slant sidebar
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
FROM nixos/nix:2.30.2
|
||||||
|
|
||||||
|
ARG NIXPKGS_REV=e5bdc4a41d4c072fe1e3787eaa0320a384741d44
|
||||||
|
ARG QSMCP_REV=76b1b008f0352064121af9334065012e31d80fc2
|
||||||
|
|
||||||
|
ENV NIX_CONFIG="experimental-features = nix-command flakes" \
|
||||||
|
PATH="/nix/var/nix/profiles/default/bin:${PATH}"
|
||||||
|
|
||||||
|
RUN nix profile install --profile /nix/var/nix/profiles/default \
|
||||||
|
"github:NixOS/nixpkgs/${NIXPKGS_REV}#quickshell" \
|
||||||
|
"github:NixOS/nixpkgs/${NIXPKGS_REV}#nodejs_22"
|
||||||
|
|
||||||
|
RUN mkdir -p /opt/qsmcp \
|
||||||
|
&& git init /opt/qsmcp \
|
||||||
|
&& git -C /opt/qsmcp remote add origin https://github.com/fedsfarm/qsmcp.git \
|
||||||
|
&& git -C /opt/qsmcp fetch --depth 1 origin "${QSMCP_REV}" \
|
||||||
|
&& git -C /opt/qsmcp checkout --detach FETCH_HEAD \
|
||||||
|
&& test "$(git -C /opt/qsmcp rev-parse HEAD)" = "${QSMCP_REV}" \
|
||||||
|
&& cd /opt/qsmcp \
|
||||||
|
&& npm ci --omit=dev \
|
||||||
|
&& rm -rf /opt/qsmcp/.git
|
||||||
|
|
||||||
|
# qsmcp's CLI hard-codes a 1568px safety cap. Desktop bar previews need their
|
||||||
|
# true target width so responsive layout and text collisions are testable.
|
||||||
|
RUN node -e 'const fs=require("fs"); const p="/opt/qsmcp/src/index.ts"; const s=fs.readFileSync(p,"utf8"); const old="max_dimension: 1568"; if (!s.includes(old)) throw new Error("qsmcp max_dimension source changed"); fs.writeFileSync(p,s.replace(old,"max_dimension: 4096"))'
|
||||||
|
|
||||||
|
ENV QSMCP_SHELL_ROOT=/workspace/quickshell \
|
||||||
|
QSMCP_QS_BIN=/nix/var/nix/profiles/default/bin/qs \
|
||||||
|
QT_QUICK_BACKEND=software
|
||||||
|
|
||||||
|
COPY entrypoint.sh /usr/local/bin/quickshell-preview
|
||||||
|
RUN chmod +x /usr/local/bin/quickshell-preview
|
||||||
|
|
||||||
|
RUN mkdir -p /workspace/quickshell
|
||||||
|
WORKDIR /workspace/quickshell
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/local/bin/quickshell-preview"]
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Headless Quickshell previews
|
||||||
|
|
||||||
|
This directory provides a reproducible Docker wrapper around
|
||||||
|
[qsmcp](https://github.com/fedsfarm/qsmcp). It renders a Quickshell `Item` to a
|
||||||
|
PNG with Qt's `grabToImage()` without connecting to the host Wayland session.
|
||||||
|
|
||||||
|
Both nixpkgs and qsmcp are pinned in `Dockerfile`. The runner uses
|
||||||
|
`docker create` plus `docker cp` rather than bind mounts, so it also works with
|
||||||
|
rootless/remote daemons whose mount namespace cannot see the checkout.
|
||||||
|
|
||||||
|
## Smoke test
|
||||||
|
|
||||||
|
Start Docker, then run from the repository root:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./tools/quickshell-preview/render.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The default output is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/tmp/homelab-quickshell-preview/smoke.png
|
||||||
|
```
|
||||||
|
|
||||||
|
## Render another Item-rooted component
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./tools/quickshell-preview/render.sh \
|
||||||
|
tests/DenseBarHeadless.qml \
|
||||||
|
.artifacts/quickshell-preview/dense-bar-1920.png \
|
||||||
|
1920 164
|
||||||
|
```
|
||||||
|
|
||||||
|
The component path is relative to `dotfiles/quickshell`. Keep production
|
||||||
|
`PanelWindow` wrappers thin and put their visual content in an `Item` component
|
||||||
|
so it can be rendered through this offscreen path.
|
||||||
|
|
||||||
|
`PanelWindow`-rooted previews require a nested compositor and are intentionally
|
||||||
|
outside this first smoke-test image.
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
mkdir -p /tmp/qsmcp-runtime /tmp/qsmcp-home /output
|
||||||
|
chmod 700 /tmp/qsmcp-runtime
|
||||||
|
|
||||||
|
export HOME=/tmp/qsmcp-home
|
||||||
|
export XDG_RUNTIME_DIR=/tmp/qsmcp-runtime
|
||||||
|
|
||||||
|
exec /nix/var/nix/profiles/default/bin/node \
|
||||||
|
/opt/qsmcp/src/index.ts render "$@"
|
||||||
Executable
+58
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
repo_root="$(git -C "$(dirname "${BASH_SOURCE[0]}")" rev-parse --show-toplevel)"
|
||||||
|
tool_dir="$repo_root/tools/quickshell-preview"
|
||||||
|
shell_root="$repo_root/dotfiles/quickshell"
|
||||||
|
image="homelab-quickshell-preview:qsmcp-76b1b008"
|
||||||
|
|
||||||
|
component="${1:-tests/HeadlessSmoke.qml}"
|
||||||
|
output="${2:-${TMPDIR:-/tmp}/homelab-quickshell-preview/smoke.png}"
|
||||||
|
width="${3:-720}"
|
||||||
|
height="${4:-120}"
|
||||||
|
|
||||||
|
if ! docker info >/dev/null 2>&1; then
|
||||||
|
echo "error: Docker-compatible daemon is unavailable" >&2
|
||||||
|
exit 69
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$output" in
|
||||||
|
/*) ;;
|
||||||
|
*) output="$PWD/$output" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
output_dir="$(dirname "$output")"
|
||||||
|
output_name="$(basename "$output")"
|
||||||
|
mkdir -p "$output_dir"
|
||||||
|
|
||||||
|
container_id=""
|
||||||
|
cleanup() {
|
||||||
|
if [[ -n "$container_id" ]]; then
|
||||||
|
docker rm --force "$container_id" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
docker build --tag "$image" --file "$tool_dir/Dockerfile" "$tool_dir"
|
||||||
|
|
||||||
|
container_id="$(docker create \
|
||||||
|
--network none \
|
||||||
|
--tmpfs /tmp:rw,noexec,nosuid,size=256m \
|
||||||
|
"$image" \
|
||||||
|
"$component" \
|
||||||
|
--width "$width" \
|
||||||
|
--height "$height" \
|
||||||
|
--dpr 1 \
|
||||||
|
--padding 0 \
|
||||||
|
--background solid \
|
||||||
|
--bg '#0a0a0a' \
|
||||||
|
--out "/output/$output_name")"
|
||||||
|
|
||||||
|
# docker cp streams files through the API, so this works with rootless and
|
||||||
|
# remote daemons whose filesystem cannot see the client's repository path.
|
||||||
|
docker cp "$shell_root/." "$container_id:/workspace/quickshell"
|
||||||
|
docker start --attach "$container_id"
|
||||||
|
docker cp "$container_id:/output/$output_name" "$output"
|
||||||
|
|
||||||
|
test -s "$output"
|
||||||
|
printf 'rendered=%s\n' "$output"
|
||||||
Reference in New Issue
Block a user