Compare commits

9 Commits
Author SHA1 Message Date
luna abccf536f6 [verified] feat(quickshell): add cyber dock launcher 2026-08-30 16:54:19 +00:00
darman 6406e06330 Auto stash before merge of "feat/quickshell-dense-bar" and "origin/feat/quickshell-dense-bar" 2026-08-28 23:03:36 +02:00
luna 9b6b799a5d [verified] feat(quickshell): add orbit launcher variant 2026-08-28 20:58:37 +00:00
luna 5ecc73f084 [verified] feat(quickshell): add blade launcher variant 2026-08-28 20:48:23 +00:00
luna fdc00a8574 [verified] feat(quickshell): add dense application launcher 2026-08-28 07:35:06 +00:00
darmanandClaude Opus 5 b16cc93ff6 refactor(quickshell): move every color and font into a Theme singleton
The shell was running two unrelated palettes: an amber one (#FFD063 accent,
#EEEEEE text, #0F1012 panels) hardcoded as ~200 raw hex literals across 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, and moves the
lot into widgets/theme/Theme.qml. `surface` takes the dense bar's void
(#0a0a0a) rather than the old panel background. Zero color and font literals
remain anywhere under widgets/ outside Theme.qml.

Collisions resolved, all near-duplicates that wanted to be one token:
  - #0F1012 + #0A0A0C + #0a0a0a -> surface
  - #EEEEEE + #dedede           -> text
  - #7A7B7D + #858585           -> muted
  - #292C30 + #22262C           -> raised
  - #FFD063 + #e8722a           -> accent

Two derived things rather than literals. accentSoft (the pale flash the
top/bottom bars show while a launcher is open) was a hand-picked #FFF3C0
against amber, which is simply wrong against orange; it is now
Qt.tint(accent, white 55%), a ratio checked against the original (amber tinted
55% gives #FFE9B8 vs the hand-picked #FFF3C0). And the dense bar had been
hand-encoding Qt.rgba(0.87,0.87,0.87,a) and Qt.rgba(0.91,0.45,0.16,a), which
are just text and accent at alpha -- now textAlpha(a)/accentAlpha(a), so they
track a palette change instead of silently drifting.

Fonts came along too. Digital-7 Mono is dropped for DepartureMono: it was
never packaged, relying on a manual ~/.dots/fonts/digital_7 install that does
not exist on terra, so `fc-match "Digital-7 Mono"` resolved to DejaVu Sans and
all 38 of those sites -- the launcher lists, sidebar clock, systray labels,
every vitals readout -- were silently rendering in a PROPORTIONAL fallback.
Numeric columns should visibly improve. readoutFont is an alias of displayFont
rather than a second literal so the two roles cannot drift apart.

quickshell/CLAUDE.md updated: it said "No shared theme/tokens file yet" and
told contributors to grep for the existing hex color, which would now
reintroduce exactly what this removes.

Verified: no file references Theme. without the import, none imports it
unused, and the whole shell -- launchers, sidebar, vitals, systray,
notifications, not just the harness -- hot-reloaded clean on terra.
tests/HeadlessSmoke.qml deliberately keeps its own copies; its value is having
no dependencies.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgcWkm3t6BDQktYHQvb8Hx
2026-08-28 07:56:25 +02:00
darmanandClaude Opus 5 01bc169180 refactor(quickshell): rework the panel chrome, extract it as StatusBarPanel
The dense bar's panel chrome gets a squarer outline (top-right and bottom-left
chamfers only, square on the other two corners) and a second accent line in
the lower right to balance the existing upper-left one.

The chrome then moves out of DenseBarContent's inline `component
TelemetryPanel` into its own file. The call sites are unchanged apart from the
name -- children still come from the default property -- and the widgets they
pass in (RadarGauge, NetworkTrace, MetricBlock) stay declared in
DenseBarContent, so their scope is unaffected by moving only the definition.

Two things could not come along and had to be reproduced locally, because a
component in its own file has no access to the enclosing scope:

  - the palette and the two font families, previously read off `root`. They
    are properties with defaults matching DenseBarContent's, which is this
    repo's per-component convention. Duplicated on purpose for now; a shared
    theme singleton is the place to collapse it.
  - MicroText, which is an *inline* component of DenseBarContent and so
    invisible from another file. Expanded to the Text it desugars to.

Also qualified the bare offsetY/chamfer/accentLineThickness references as
panel.*; they resolved through the component scope before, but being explicit
avoids ComponentBehavior: Bound warnings in the new file.

Verified the move was verbatim by normalising the old inline block and the new
file body and diffing them -- the only differences are the relocated property
block, the panel.* qualification and the MicroText expansion. Loads clean both
headlessly and in the real layer-shell shell.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgcWkm3t6BDQktYHQvb8Hx
2026-08-28 07:55:59 +02:00
darmanandClaude Opus 5 54896033c6 feat(flake): add a quickshell hot-reload devShell
hosts/terra/home.nix ships dotfiles/quickshell via xdg.configFile, which
copies the tree into the store: ~/.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 watches, which are those
frozen store copies. `nix develop` now swaps the running shell to the working
tree (`qs -p`) and swaps it back on exit, so QML edits need no rebuild at all.

The swap starts the dev instance FIRST and kills the packaged one only once
dev is confirmed up. A QML error in the working tree then leaves you on your
normal bar instead of no bar, which matters because a broken save is exactly
when you would be running this. Liveness is "did `qs list -j` return json" --
it exits 0 whether or not it found anything, so the exit code says nothing.

Every kill is scoped to one config (`qs kill` = default, `qs kill -p` = that
path). A blanket kill would also take out unrelated instances; pkgs/rishot.nix
is one.

Three guards on the auto-swap, all learned by testing it:
  - interactive only. `nix develop --command X` EXECs X, replacing the shell
    that set the `trap ... EXIT`, so the restore never runs and you are left
    on the dev instance. Non-interactive use gets `nix develop -c qs-dev`.
  - 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 restore twice.

Deliberately not wired to direnv (no .envrc): programs.direnv is enabled for
this user, so a `use flake` would swap the running desktop shell on every `cd`
into the checkout.

Verified end to end on terra: swap, hot-reload of a working-tree edit, and
restore, plus both the interactive and non-interactive paths.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgcWkm3t6BDQktYHQvb8Hx
2026-08-28 07:55:03 +02:00
luna 22fe8ab778 feat(quickshell): add dense telemetry bar 2026-08-27 23:52:47 +00:00
48 changed files with 3833 additions and 870 deletions
+3
View File
@@ -14,3 +14,6 @@ keys.txt
# local env (PATH etc.)
.env
# local visual-verification output
.artifacts/
+8 -3
View File
@@ -27,14 +27,19 @@ Quickshell hot-reloads QML on file save when already running, so for most edits
**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/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 17 remain available on `SUPER CTRL 17` 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/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.
- `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.
**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 -1
View File
@@ -1,3 +1,3 @@
#!/bin/bash
hyprctl dispatch 'hl.dsp.global ("quickshell:launcher7")'
hyprctl dispatch 'hl.dsp.global ("quickshell:launcher8")'
+7 -6
View File
@@ -5,17 +5,14 @@ import qs.widgets.bar
import qs.widgets.launcher
import qs.widgets.notifications
import qs.widgets.osd
import qs.widgets.sidebar
import qs.widgets.systray
import qs.widgets.vitals
Scope {
// Left sidebar in the Slant (V6) style — toggle with SUPER CTRL S.
SideBar {}
BarBottom {}
// Dense multi-monitor status rail; visual core is headlessly renderable.
DenseBar {}
// App launcher variants — toggled via Hyprland global shortcuts
// (SUPER CTRL 1/2/3). Try each and keep the one you like.
// App launcher variants — 111; variant 8 remains the primary HUD.
LauncherStack {} // 1 — left vertical list
LauncherGrid {} // 2 — centered icon grid
LauncherSpotlight {} // 3 — top-center command bar
@@ -23,6 +20,10 @@ Scope {
LauncherDock {} // 5 — deck rising from the bottom bar
LauncherSlant {} // 6 — angular / sheared panel
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 {}
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
}
+155
View File
@@ -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.Shapes
import qs.widgets.theme
Shape {
preferredRendererType: Shape.CurveRenderer
ShapePath {
fillColor: "#FFD063"
fillColor: Theme.accent
strokeWidth: 0
startX: 0
@@ -5,6 +5,7 @@ import QtQuick.Layouts
import QtQuick.Shapes
import qs.widgets.decoration
import qs.widgets.theme
WrapperItem {
RowLayout {
@@ -24,7 +25,7 @@ WrapperItem {
ShapePath {
strokeWidth: 0
fillColor: "#0F1012"
fillColor: Theme.surface
startX: 8
startY: 0
@@ -52,7 +53,7 @@ WrapperItem {
ShapePath {
strokeWidth: 0
fillColor: "#0F1012"
fillColor: Theme.surface
startX: 16
startY: 0
@@ -83,7 +84,7 @@ WrapperItem {
startY: 0
strokeWidth: 0
fillColor: "#0F1012"
fillColor: Theme.surface
PathLine {
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" : "AZ / LOCAL"
ListView {
id: appList
x: 9
y: 29
width: parent.width - 18
height: parent.height - 38
clip: true
spacing: 3
model: root.apps
currentIndex: root.selectedIndex
boundsBehavior: Flickable.StopAtBounds
onCurrentIndexChanged: positionViewAtIndex(currentIndex, ListView.Contain)
delegate: MouseArea {
id: row
required property int index
required property var modelData
width: ListView.view.width
height: 48
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
readonly property bool selected: index === root.selectedIndex
onEntered: root.selectionRequested(index)
onClicked: root.launchRequested(index)
Shape {
id: rowShape
anchors.fill: parent
preferredRendererType: Shape.CurveRenderer
ShapePath {
fillColor: row.selected ? Theme.selection : Theme.textAlpha(0.025)
strokeColor: row.selected ? Theme.accent : Theme.textAlpha(0.10)
strokeWidth: 1
startX: 9; startY: 0
PathLine { x: rowShape.width; y: 0 }
PathLine { x: rowShape.width - 9; y: rowShape.height }
PathLine { x: 0; y: rowShape.height }
PathLine { x: 9; y: 0 }
}
ShapePath {
fillColor: row.selected ? Theme.accent : Theme.textAlpha(0.12)
strokeWidth: 0
startX: 9; startY: 0
PathLine { x: 13; y: 0 }
PathLine { x: 4; y: rowShape.height }
PathLine { x: 0; y: rowShape.height }
PathLine { x: 9; y: 0 }
}
}
RowLayout {
anchors.fill: parent
anchors.leftMargin: 18
anchors.rightMargin: 18
spacing: 12
Text {
Layout.preferredWidth: 28
text: String(row.index + 1).padStart(2, "0")
color: row.selected ? Theme.accent : Theme.muted
font.family: Theme.microFont
font.pixelSize: 8
font.bold: row.selected
}
Rectangle {
Layout.preferredWidth: 34
Layout.preferredHeight: 34
color: row.selected ? Theme.accentAlpha(0.14) : Theme.textAlpha(0.035)
border.width: 1
border.color: row.selected ? Theme.accent : Theme.hair
IconImage {
visible: root.showIcons
anchors.centerIn: parent
implicitSize: 24
source: root.showIcons
? Quickshell.iconPath(row.modelData.icon || "application-x-executable", "application-x-executable")
: ""
}
Text {
visible: !root.showIcons
anchors.centerIn: parent
text: String(row.modelData.name || "?").charAt(0).toUpperCase()
color: row.selected ? Theme.accent : Theme.text
font.family: Theme.displayFont
font.pixelSize: 14
font.bold: true
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 1
Text {
Layout.fillWidth: true
text: row.modelData.name || "UNKNOWN TARGET"
color: row.selected ? Theme.accent : Theme.text
font.family: Theme.displayFont
font.pixelSize: 11
font.bold: row.selected
font.letterSpacing: 0.5
elide: Text.ElideRight
}
MicroText {
Layout.fillWidth: true
text: row.modelData.genericName || row.modelData.comment || "DESKTOP APPLICATION"
}
}
MicroText {
Layout.preferredWidth: 100
horizontalAlignment: Text.AlignRight
text: row.index === root.selectedIndex ? "LOCKED" : "AVAILABLE"
color: row.selected ? Theme.accent : Theme.muted
}
Rectangle {
Layout.preferredWidth: 38
Layout.preferredHeight: 6
color: Theme.textAlpha(0.05)
border.width: 1
border.color: Theme.hair
Rectangle {
width: row.selected ? parent.width : Math.max(5, parent.width * (1 - row.index / Math.max(1, root.apps.length)))
height: parent.height
color: row.selected ? Theme.accent : Theme.textAlpha(0.20)
}
}
}
}
Text {
anchors.centerIn: parent
visible: root.apps.length === 0
text: "NO EXECUTABLE TARGETS MATCH QUERY"
color: Theme.muted
font.family: Theme.microFont
font.pixelSize: 9
font.letterSpacing: 1.2
}
}
}
// Selected application inspector.
StatusBarPanel {
id: inspector
x: 700
y: 34
width: 362
height: 508
panelId: "SEL"
title: "TARGET INSPECTOR"
meta: root.selectedApp ? "LOCKED" : "NO TARGET"
Item {
id: reticle
anchors.horizontalCenter: parent.horizontalCenter
y: 39
width: 126
height: 126
Rectangle {
anchors.centerIn: parent
width: 112
height: 112
radius: 56
color: Theme.textAlpha(0.015)
border.width: 1
border.color: Theme.accentAlpha(0.52)
}
Rectangle {
anchors.centerIn: parent
width: 84
height: 84
radius: 42
color: Theme.textAlpha(0)
border.width: 1
border.color: Theme.hair
}
Rectangle { anchors.centerIn: parent; width: 126; height: 1; color: Theme.hair }
Rectangle { anchors.centerIn: parent; width: 1; height: 126; color: Theme.hair }
Rectangle {
anchors.centerIn: parent
width: 60
height: 60
color: Theme.surface
border.width: 1
border.color: Theme.accent
IconImage {
visible: root.showIcons
anchors.centerIn: parent
implicitSize: 44
source: root.showIcons && root.selectedApp
? Quickshell.iconPath(root.selectedApp.icon || "application-x-executable", "application-x-executable")
: ""
}
Text {
visible: !root.showIcons
anchors.centerIn: parent
text: root.selectedApp ? String(root.selectedApp.name || "?").charAt(0).toUpperCase() : "?"
color: Theme.accent
font.family: Theme.displayFont
font.pixelSize: 28
font.bold: true
}
}
Repeater {
model: 4
Rectangle {
required property int index
width: 5; height: 5
x: index % 2 === 0 ? 8 : reticle.width - 13
y: index < 2 ? 8 : reticle.height - 13
color: Theme.accent
}
}
}
Text {
anchors.top: reticle.bottom
anchors.topMargin: 14
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width - 34
horizontalAlignment: Text.AlignHCenter
text: root.selectedApp ? root.selectedApp.name : "NO TARGET"
color: Theme.text
font.family: Theme.displayFont
font.pixelSize: 19
font.bold: true
font.letterSpacing: 1
elide: Text.ElideRight
}
MicroText {
id: genericLabel
anchors.top: reticle.bottom
anchors.topMargin: 42
x: 18
width: parent.width - 36
horizontalAlignment: Text.AlignHCenter
text: root.selectedApp ? (root.selectedApp.genericName || "DESKTOP APPLICATION") : "AWAITING SEARCH VECTOR"
color: Theme.accent
}
Rectangle {
x: 18
anchors.top: genericLabel.bottom
anchors.topMargin: 12
width: parent.width - 36
height: 1
color: Theme.hair
}
MicroText {
id: description
x: 22
anchors.top: genericLabel.bottom
anchors.topMargin: 27
width: parent.width - 44
height: 42
text: root.selectedApp ? (root.selectedApp.comment || "No application description supplied by desktop entry.") : "Enter a designation to acquire an executable target."
wrapMode: Text.Wrap
elide: Text.ElideRight
maximumLineCount: 3
horizontalAlignment: Text.AlignHCenter
}
GridLayout {
id: telemetryGrid
x: 18
anchors.top: description.bottom
anchors.topMargin: 15
width: parent.width - 36
columns: 2
rowSpacing: 7
columnSpacing: 7
Repeater {
model: [
{ label: "ENTRY", value: "DESKTOP" },
{ label: "MODE", value: "DETACHED" },
{ label: "AUTH", value: "LOCAL USER" },
{ label: "INDEX", value: String(Math.max(0, root.selectedIndex + 1)).padStart(3, "0") }
]
Rectangle {
required property var modelData
Layout.fillWidth: true
Layout.preferredHeight: 46
color: Theme.textAlpha(0.025)
border.width: 1
border.color: Theme.hair
MicroText { x: 8; y: 7; width: parent.width - 16; text: modelData.label }
Text {
x: 8; y: 22; width: parent.width - 16
text: modelData.value
color: Theme.text
font.family: Theme.displayFont
font.pixelSize: 9
font.bold: true
font.letterSpacing: 0.6
elide: Text.ElideRight
}
}
}
}
Item {
x: 18
anchors.bottom: executeTile.top
anchors.bottomMargin: 12
width: parent.width - 36
height: 18
Row {
spacing: 5
Repeater {
model: 24
Rectangle {
required property int index
width: 7; height: 3
y: index % 3 === 0 ? 0 : 4
color: index < 16 ? Theme.accent : Theme.hair
transform: Rotation { angle: -35 }
}
}
}
}
MouseArea {
id: executeTile
x: 18
anchors.bottom: parent.bottom
anchors.bottomMargin: 14
width: parent.width - 36
height: 42
enabled: root.selectedApp !== null
hoverEnabled: true
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: root.launchRequested(root.selectedIndex)
Rectangle {
anchors.fill: parent
color: executeTile.containsMouse ? Theme.accent : Theme.accentAlpha(0.16)
border.width: 1
border.color: Theme.accent
Text {
anchors.centerIn: parent
text: root.selectedApp ? "EXECUTE SELECTED TARGET" : "NO TARGET ACQUIRED"
color: executeTile.containsMouse ? Theme.surface : Theme.accent
font.family: Theme.displayFont
font.pixelSize: 10
font.bold: true
font.letterSpacing: 1.2
}
}
}
}
// Dense command footer.
StatusBarPanel {
x: 18
y: 550
width: 1044
height: 52
showHeader: false
chamfer: 9
RowLayout {
anchors.fill: parent
anchors.leftMargin: 16
anchors.rightMargin: 16
spacing: 18
MicroText { text: "INPUT CHANNEL // KEYBOARD"; color: Theme.accent }
Rectangle { Layout.preferredWidth: 1; Layout.preferredHeight: 22; color: Theme.hair }
KeyHint { keyText: "↑ ↓"; actionText: "SELECT" }
KeyHint { keyText: "ENTER"; actionText: "EXECUTE" }
KeyHint { keyText: "ESC"; actionText: "ABORT" }
Item { Layout.fillWidth: true }
MicroText { text: "QRY:" + (root.query.length > 0 ? "ACTIVE" : "IDLE") }
MicroText { text: "CRC // A7F2" }
Rectangle {
Layout.preferredWidth: 76
Layout.preferredHeight: 4
color: Theme.accent
}
}
}
}
}
@@ -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 QtQuick
import QtQuick.Layouts
import qs.widgets.theme
// 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
@@ -99,7 +100,7 @@ Scope {
anchors.right: parent.right
clip: true
color: "#0F1012"
color: Theme.surface
height: root.active ? win.openHeight : 0
@@ -116,7 +117,7 @@ Scope {
// Accent lid — mirrors the top bar strip, reads as its extension.
Rectangle {
color: "#FFD063"
color: Theme.accent
implicitHeight: 4
Layout.fillWidth: true
}
@@ -132,8 +133,8 @@ Scope {
Text {
text: ">_"
color: "#FFD063"
font.family: "Digital-7 Mono"
color: Theme.accent
font.family: Theme.readoutFont
font.pointSize: 22
font.bold: true
}
@@ -147,7 +148,7 @@ Scope {
anchors.fill: parent
verticalAlignment: TextInput.AlignVCenter
color: "#EEEEEE"
color: Theme.text
font.pointSize: 16
clip: true
@@ -182,7 +183,7 @@ Scope {
verticalAlignment: Text.AlignVCenter
visible: input.text.length === 0
text: "launch application…"
color: "#7A7B7D"
color: Theme.muted
font: input.font
}
}
@@ -190,14 +191,14 @@ Scope {
Text {
text: model.apps.length + " apps"
color: "#7A7B7D"
font.family: "Digital-7 Mono"
color: Theme.muted
font.family: Theme.readoutFont
font.pointSize: 15
}
}
Rectangle {
color: "#292C30"
color: Theme.raised
implicitHeight: 1
Layout.fillWidth: true
}
@@ -236,9 +237,9 @@ Scope {
Rectangle {
anchors.fill: parent
color: card.selected ? "#292C30" : "transparent"
color: card.selected ? Theme.raised : "transparent"
border.width: 1
border.color: card.selected ? "#FFD063" : "#292C30"
border.color: card.selected ? Theme.accent : Theme.raised
Behavior on border.color {
ColorAnimation {
@@ -261,7 +262,7 @@ Scope {
Text {
Layout.fillWidth: true
text: card.modelData.name
color: card.selected ? "#FFD063" : "#EEEEEE"
color: card.selected ? Theme.accent : Theme.text
font.pointSize: 9
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
@@ -277,8 +278,8 @@ Scope {
anchors.centerIn: parent
visible: model.apps.length === 0
text: "no matches"
color: "#7A7B7D"
font.family: "Digital-7 Mono"
color: Theme.muted
font.family: Theme.readoutFont
font.pointSize: 16
}
}
@@ -7,6 +7,7 @@ import Quickshell.Widgets
import QtQuick
import QtQuick.Layouts
import QtQuick.Shapes
import qs.widgets.theme
// Variant 7 — a copy of the "Slant" (V6) launcher, plus a small floating
// power panel docked to its right with Shutdown / Reboot buttons.
@@ -77,7 +78,7 @@ Scope {
// Dim backdrop — click to dismiss.
Rectangle {
anchors.fill: parent
color: "#0A0A0C"
color: Theme.surface
opacity: 0.55
MouseArea {
@@ -107,8 +108,8 @@ Scope {
preferredRendererType: Shape.CurveRenderer
ShapePath {
fillColor: "#0F1012"
strokeColor: "#FFD063"
fillColor: Theme.surface
strokeColor: Theme.accent
strokeWidth: 2
startX: frame.chamfer
@@ -153,7 +154,7 @@ Scope {
// run out to the top and left edges to meet the straight borders.
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: 0
startY: frame.chamfer
@@ -179,7 +180,7 @@ Scope {
// bottom and right edges.
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: panelShape.width
startY: panelShape.height - frame.chamfer
@@ -205,7 +206,7 @@ Scope {
// detached from the panel by the width of the cut.
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: panelShape.width
startY: panelShape.height
@@ -228,7 +229,7 @@ Scope {
// slanted end pieces on both arms.
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
// inner, right end of the bottom arm
startX: panelShape.width / 3
@@ -277,7 +278,7 @@ Scope {
// slanted end pieces on both arms.
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
// inner, bottom of the right arm
startX: panelShape.width
@@ -345,7 +346,7 @@ Scope {
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: 6
startY: 0
PathLine {
@@ -367,7 +368,7 @@ Scope {
}
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: 15
startY: 0
PathLine {
@@ -389,7 +390,7 @@ Scope {
}
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: 24
startY: 0
PathLine {
@@ -420,8 +421,8 @@ Scope {
anchors.fill: parent
verticalAlignment: TextInput.AlignVCenter
color: "#EEEEEE"
font.family: "Digital-7 Mono"
color: Theme.text
font.family: Theme.readoutFont
font.pointSize: 20
font.letterSpacing: 1
clip: true
@@ -457,7 +458,7 @@ Scope {
verticalAlignment: Text.AlignVCenter
visible: input.text.length === 0
text: "run"
color: "#7A7B7D"
color: Theme.muted
font: input.font
}
}
@@ -465,8 +466,8 @@ Scope {
Text {
text: model.apps.length
color: "#7A7B7D"
font.family: "Digital-7 Mono"
color: Theme.muted
font.family: Theme.readoutFont
font.pointSize: 18
}
}
@@ -489,7 +490,7 @@ Scope {
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: divider.slant
startY: 0
PathLine {
@@ -512,7 +513,7 @@ Scope {
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: 0
startY: divider.height
PathLine {
@@ -535,7 +536,7 @@ Scope {
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: divider.width
startY: 0
PathLine {
@@ -597,7 +598,7 @@ Scope {
// Body
ShapePath {
strokeWidth: 0
fillColor: "#22262C"
fillColor: Theme.raised
startX: appRow.shear
startY: 0
PathLine {
@@ -620,7 +621,7 @@ Scope {
// Left accent edge
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: appRow.shear
startY: 0
PathLine {
@@ -655,7 +656,7 @@ Scope {
Text {
text: appRow.modelData.name
color: appRow.selected ? "#FFD063" : "#EEEEEE"
color: appRow.selected ? Theme.accent : Theme.text
font.pointSize: 12
elide: Text.ElideRight
Layout.fillWidth: true
@@ -663,7 +664,7 @@ Scope {
Text {
text: appRow.modelData.genericName || ""
color: "#7A7B7D"
color: Theme.muted
font.pointSize: 10
elide: Text.ElideRight
Layout.maximumWidth: 220
@@ -694,8 +695,8 @@ Scope {
preferredRendererType: Shape.CurveRenderer
ShapePath {
fillColor: "#FFD063"
strokeColor: "#FFD063"
fillColor: Theme.accent
strokeColor: Theme.accent
strokeWidth: 2
startX: 0
@@ -732,9 +733,9 @@ Scope {
Text {
Layout.alignment: Qt.AlignHCenter
text: "POWER"
font.family: "Digital-7 Mono"
font.family: Theme.readoutFont
font.pointSize: 11
color: "#0F1012"
color: Theme.surface
}
Repeater {
@@ -762,8 +763,8 @@ Scope {
ShapePath {
strokeWidth: 1
strokeColor: powerButton.containsMouse ? "#FFD063" : "#7A7B7D"
fillColor: "#292C30"
strokeColor: powerButton.containsMouse ? Theme.accent : Theme.muted
fillColor: Theme.raised
startX: powerButton.chamfer
startY: 0
@@ -785,8 +786,8 @@ Scope {
Text {
anchors.centerIn: parent
text: powerButton.modelData.label
color: powerButton.containsMouse ? "#FFD063" : "#EEEEEE"
font.family: "Digital-7 Mono"
color: powerButton.containsMouse ? Theme.accent : Theme.text
font.family: Theme.readoutFont
font.pointSize: 11
}
}
@@ -6,6 +6,7 @@ import Quickshell.Wayland
import Quickshell.Widgets
import QtQuick
import QtQuick.Layouts
import qs.widgets.theme
// 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
@@ -98,7 +99,7 @@ Scope {
anchors.right: parent.right
clip: true
color: "#0F1012"
color: Theme.surface
height: root.active ? win.openHeight : 0
@@ -153,9 +154,9 @@ Scope {
Rectangle {
anchors.fill: parent
color: card.selected ? "#292C30" : "transparent"
color: card.selected ? Theme.raised : "transparent"
border.width: 1
border.color: card.selected ? "#FFD063" : "#292C30"
border.color: card.selected ? Theme.accent : Theme.raised
Behavior on border.color {
ColorAnimation {
@@ -178,7 +179,7 @@ Scope {
Text {
Layout.fillWidth: true
text: card.modelData.name
color: card.selected ? "#FFD063" : "#EEEEEE"
color: card.selected ? Theme.accent : Theme.text
font.pointSize: 9
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
@@ -194,14 +195,14 @@ Scope {
anchors.centerIn: parent
visible: model.apps.length === 0
text: "no matches"
color: "#7A7B7D"
font.family: "Digital-7 Mono"
color: Theme.muted
font.family: Theme.readoutFont
font.pointSize: 16
}
}
Rectangle {
color: "#292C30"
color: Theme.raised
implicitHeight: 1
Layout.fillWidth: true
}
@@ -217,8 +218,8 @@ Scope {
Text {
text: ">_"
color: "#FFD063"
font.family: "Digital-7 Mono"
color: Theme.accent
font.family: Theme.readoutFont
font.pointSize: 22
font.bold: true
}
@@ -232,7 +233,7 @@ Scope {
anchors.fill: parent
verticalAlignment: TextInput.AlignVCenter
color: "#EEEEEE"
color: Theme.text
font.pointSize: 16
clip: true
@@ -267,7 +268,7 @@ Scope {
verticalAlignment: Text.AlignVCenter
visible: input.text.length === 0
text: "launch application…"
color: "#7A7B7D"
color: Theme.muted
font: input.font
}
}
@@ -275,15 +276,15 @@ Scope {
Text {
text: model.apps.length + " apps"
color: "#7A7B7D"
font.family: "Digital-7 Mono"
color: Theme.muted
font.family: Theme.readoutFont
font.pointSize: 15
}
}
// Accent lid — mirrors the bottom bar strip.
Rectangle {
color: "#FFD063"
color: Theme.accent
implicitHeight: 4
Layout.fillWidth: true
}
@@ -6,6 +6,7 @@ import Quickshell.Wayland
import Quickshell.Widgets
import QtQuick
import QtQuick.Layouts
import qs.widgets.theme
// Variant 2 — "Grid": centered app-drawer with a dim backdrop and icon tiles.
Scope {
@@ -78,7 +79,7 @@ Scope {
// Dim backdrop — click anywhere outside to dismiss.
Rectangle {
anchors.fill: parent
color: "#0A0A0C"
color: Theme.surface
opacity: 0.55
MouseArea {
@@ -93,9 +94,9 @@ Scope {
width: 760
height: 560
color: "#0F1012"
color: Theme.surface
border.width: 1
border.color: "#FFD063"
border.color: Theme.accent
opacity: 0.98
// Accent corner brackets for the cyberpunk frame.
@@ -104,14 +105,14 @@ Scope {
anchors.left: parent.left
width: 5
height: 28
color: "#FFD063"
color: Theme.accent
}
Rectangle {
anchors.top: parent.top
anchors.right: parent.right
width: 28
height: 5
color: "#FFD063"
color: Theme.accent
}
ColumnLayout {
@@ -126,8 +127,8 @@ Scope {
Text {
text: "APPS"
color: "#FFD063"
font.family: "Digital-7 Mono"
color: Theme.accent
font.family: Theme.readoutFont
font.pointSize: 22
font.letterSpacing: 3
}
@@ -138,9 +139,9 @@ Scope {
Rectangle {
anchors.fill: parent
color: "#292C30"
color: Theme.raised
border.width: 1
border.color: input.activeFocus ? "#FFD063" : "#7A7B7D"
border.color: input.activeFocus ? Theme.accent : Theme.muted
Behavior on border.color {
ColorAnimation {
@@ -155,7 +156,7 @@ Scope {
anchors.rightMargin: 12
verticalAlignment: TextInput.AlignVCenter
color: "#EEEEEE"
color: Theme.text
font.pointSize: 13
clip: true
@@ -198,7 +199,7 @@ Scope {
anchors.verticalCenter: parent.verticalCenter
visible: input.text.length === 0
text: "Type to search…"
color: "#7A7B7D"
color: Theme.muted
font: input.font
}
}
@@ -207,7 +208,7 @@ Scope {
}
Rectangle {
color: "#FFD063"
color: Theme.accent
implicitHeight: 3
Layout.fillWidth: true
}
@@ -242,9 +243,9 @@ Scope {
Rectangle {
anchors.fill: parent
anchors.margins: 6
color: tile.selected ? "#292C30" : "transparent"
color: tile.selected ? Theme.raised : "transparent"
border.width: 1
border.color: tile.selected ? "#FFD063" : "transparent"
border.color: tile.selected ? Theme.accent : "transparent"
Behavior on border.color {
ColorAnimation {
@@ -266,7 +267,7 @@ Scope {
Text {
Layout.fillWidth: true
text: tile.modelData.name
color: tile.selected ? "#FFD063" : "#EEEEEE"
color: tile.selected ? Theme.accent : Theme.text
font.pointSize: 10
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
@@ -7,6 +7,7 @@ import Quickshell.Widgets
import QtQuick
import QtQuick.Layouts
import QtQuick.Shapes
import qs.widgets.theme
// Variant 6 — "Slant": breaks up the rectangular layout with angled geometry —
// a chamfered panel, a slanted header divider and sheared row highlights.
@@ -77,7 +78,7 @@ Scope {
// Dim backdrop — click to dismiss.
Rectangle {
anchors.fill: parent
color: "#0A0A0C"
color: Theme.surface
opacity: 0.55
MouseArea {
@@ -107,8 +108,8 @@ Scope {
preferredRendererType: Shape.CurveRenderer
ShapePath {
fillColor: "#0F1012"
strokeColor: "#FFD063"
fillColor: Theme.surface
strokeColor: Theme.accent
strokeWidth: 2
startX: frame.chamfer
@@ -153,7 +154,7 @@ Scope {
// run out to the top and left edges to meet the straight borders.
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: 0
startY: frame.chamfer
@@ -179,7 +180,7 @@ Scope {
// bottom and right edges.
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: panelShape.width
startY: panelShape.height - frame.chamfer
@@ -205,7 +206,7 @@ Scope {
// detached from the panel by the width of the cut.
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: panelShape.width
startY: panelShape.height
@@ -228,7 +229,7 @@ Scope {
// slanted end pieces on both arms.
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
// inner, right end of the bottom arm
startX: panelShape.width / 3
@@ -277,7 +278,7 @@ Scope {
// slanted end pieces on both arms.
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
// inner, bottom of the right arm
startX: panelShape.width
@@ -345,7 +346,7 @@ Scope {
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: 6
startY: 0
PathLine {
@@ -367,7 +368,7 @@ Scope {
}
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: 15
startY: 0
PathLine {
@@ -389,7 +390,7 @@ Scope {
}
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: 24
startY: 0
PathLine {
@@ -420,8 +421,8 @@ Scope {
anchors.fill: parent
verticalAlignment: TextInput.AlignVCenter
color: "#EEEEEE"
font.family: "Digital-7 Mono"
color: Theme.text
font.family: Theme.readoutFont
font.pointSize: 20
font.letterSpacing: 1
clip: true
@@ -457,7 +458,7 @@ Scope {
verticalAlignment: Text.AlignVCenter
visible: input.text.length === 0
text: "run"
color: "#7A7B7D"
color: Theme.muted
font: input.font
}
}
@@ -465,8 +466,8 @@ Scope {
Text {
text: model.apps.length
color: "#7A7B7D"
font.family: "Digital-7 Mono"
color: Theme.muted
font.family: Theme.readoutFont
font.pointSize: 18
}
}
@@ -489,7 +490,7 @@ Scope {
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: divider.slant
startY: 0
PathLine {
@@ -512,7 +513,7 @@ Scope {
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: 0
startY: divider.height
PathLine {
@@ -535,7 +536,7 @@ Scope {
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: divider.width
startY: 0
PathLine {
@@ -597,7 +598,7 @@ Scope {
// Body
ShapePath {
strokeWidth: 0
fillColor: "#22262C"
fillColor: Theme.raised
startX: appRow.shear
startY: 0
PathLine {
@@ -620,7 +621,7 @@ Scope {
// Left accent edge
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: appRow.shear
startY: 0
PathLine {
@@ -655,7 +656,7 @@ Scope {
Text {
text: appRow.modelData.name
color: appRow.selected ? "#FFD063" : "#EEEEEE"
color: appRow.selected ? Theme.accent : Theme.text
font.pointSize: 12
elide: Text.ElideRight
Layout.fillWidth: true
@@ -663,7 +664,7 @@ Scope {
Text {
text: appRow.modelData.genericName || ""
color: "#7A7B7D"
color: Theme.muted
font.pointSize: 10
elide: Text.ElideRight
Layout.maximumWidth: 220
@@ -6,6 +6,7 @@ import Quickshell.Wayland
import Quickshell.Widgets
import QtQuick
import QtQuick.Layouts
import qs.widgets.theme
// Variant 3 — "Spotlight": top-center command bar with a compact result list.
Scope {
@@ -91,9 +92,9 @@ Scope {
Rectangle {
Layout.fillWidth: true
implicitHeight: 60
color: "#0F1012"
color: Theme.surface
border.width: 1
border.color: "#FFD063"
border.color: Theme.accent
RowLayout {
anchors.fill: parent
@@ -103,8 +104,8 @@ Scope {
Text {
text: ">"
color: "#FFD063"
font.family: "Digital-7 Mono"
color: Theme.accent
font.family: Theme.readoutFont
font.pointSize: 26
font.bold: true
}
@@ -118,7 +119,7 @@ Scope {
anchors.fill: parent
verticalAlignment: TextInput.AlignVCenter
color: "#EEEEEE"
color: Theme.text
font.pointSize: 17
clip: true
@@ -153,7 +154,7 @@ Scope {
verticalAlignment: Text.AlignVCenter
visible: input.text.length === 0
text: "run application"
color: "#7A7B7D"
color: Theme.muted
font: input.font
}
}
@@ -161,8 +162,8 @@ Scope {
Text {
text: model.apps.length + " ▸"
color: "#7A7B7D"
font.family: "Digital-7 Mono"
color: Theme.muted
font.family: Theme.readoutFont
font.pointSize: 16
}
}
@@ -172,7 +173,7 @@ Scope {
Rectangle {
Layout.fillWidth: true
implicitHeight: 3
color: "#FFD063"
color: Theme.accent
visible: model.apps.length > 0
}
@@ -181,11 +182,11 @@ Scope {
Layout.fillWidth: true
// Cap the visible height to maxRows; scroll beyond that.
implicitHeight: Math.min(model.apps.length, win.maxRows) * 44 + 2
color: "#0F1012"
color: Theme.surface
opacity: 0.97
visible: model.apps.length > 0
border.width: 1
border.color: "#292C30"
border.color: Theme.raised
ListView {
id: list
@@ -215,14 +216,14 @@ Scope {
Rectangle {
anchors.fill: parent
color: entry.selected ? "#1A1C1F" : "transparent"
color: entry.selected ? Theme.selection : "transparent"
Rectangle {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: 3
color: "#FFD063"
color: Theme.accent
visible: entry.selected
}
@@ -239,7 +240,7 @@ Scope {
Text {
text: entry.modelData.name
color: entry.selected ? "#FFD063" : "#EEEEEE"
color: entry.selected ? Theme.accent : Theme.text
font.pointSize: 12
elide: Text.ElideRight
Layout.fillWidth: true
@@ -247,7 +248,7 @@ Scope {
Text {
text: entry.modelData.genericName || ""
color: "#7A7B7D"
color: Theme.muted
font.pointSize: 10
elide: Text.ElideRight
Layout.maximumWidth: 200
@@ -6,6 +6,7 @@ import Quickshell.Wayland
import Quickshell.Widgets
import QtQuick
import QtQuick.Layouts
import qs.widgets.theme
// Variant 1 — "Stack": left-anchored vertical list launcher.
// Framed with the top/bottom accent strips used by the main Bar.
@@ -82,7 +83,7 @@ Scope {
spacing: 0
Rectangle {
color: "#FFD063"
color: Theme.accent
implicitHeight: 5
Layout.fillWidth: true
}
@@ -93,8 +94,8 @@ Scope {
margin: 12
border.width: 1
border.color: "#FFD063"
color: "#0F1012"
border.color: Theme.accent
color: Theme.surface
opacity: 0.95
ColumnLayout {
@@ -107,7 +108,7 @@ Scope {
Text {
text: "▚"
color: "#FFD063"
color: Theme.accent
font.pointSize: 14
font.bold: true
}
@@ -121,8 +122,8 @@ Scope {
anchors.fill: parent
verticalAlignment: TextInput.AlignVCenter
color: "#EEEEEE"
font.family: "Digital-7 Mono"
color: Theme.text
font.family: Theme.readoutFont
font.pointSize: 16
clip: true
@@ -158,7 +159,7 @@ Scope {
verticalAlignment: Text.AlignVCenter
visible: input.text.length === 0
text: "search"
color: "#7A7B7D"
color: Theme.muted
font: input.font
}
}
@@ -166,14 +167,14 @@ Scope {
Text {
text: model.apps.length
color: "#7A7B7D"
font.family: "Digital-7 Mono"
color: Theme.muted
font.family: Theme.readoutFont
font.pointSize: 14
}
}
Rectangle {
color: "#FFD063"
color: Theme.accent
implicitHeight: 2
Layout.fillWidth: true
}
@@ -207,7 +208,7 @@ Scope {
Rectangle {
anchors.fill: parent
color: appRow.selected ? "#292C30" : "transparent"
color: appRow.selected ? Theme.raised : "transparent"
// Accent bar on the selected row.
Rectangle {
@@ -215,7 +216,7 @@ Scope {
anchors.top: parent.top
anchors.bottom: parent.bottom
width: 3
color: "#FFD063"
color: Theme.accent
visible: appRow.selected
}
@@ -236,7 +237,7 @@ Scope {
Text {
text: appRow.modelData.name
color: appRow.selected ? "#FFD063" : "#EEEEEE"
color: appRow.selected ? Theme.accent : Theme.text
font.pointSize: 12
elide: Text.ElideRight
Layout.fillWidth: true
@@ -245,7 +246,7 @@ Scope {
Text {
visible: text.length > 0
text: appRow.modelData.genericName || appRow.modelData.comment || ""
color: "#7A7B7D"
color: Theme.muted
font.pointSize: 9
elide: Text.ElideRight
Layout.fillWidth: true
@@ -259,7 +260,7 @@ Scope {
}
Rectangle {
color: "#FFD063"
color: Theme.accent
implicitHeight: 5
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 QtQuick
import QtQuick.Layouts
import qs.widgets.theme
ColumnLayout {
id: root
@@ -12,7 +13,7 @@ ColumnLayout {
Layout.fillWidth: true
Rectangle {
color: "#FFD063"
color: Theme.accent
implicitHeight: 5
Layout.fillWidth: true
@@ -23,8 +24,8 @@ ColumnLayout {
margin: 12
border.width: 1
border.color: "#FFD063"
color: "#0F1012"
border.color: Theme.accent
color: Theme.surface
opacity: .9
ColumnLayout {
@@ -45,7 +46,7 @@ ColumnLayout {
Text {
text: root.modelData.summary
color: "#EEEEEE"
color: Theme.text
font.pointSize: 14
font.bold: true
elide: Text.ElideRight
@@ -57,7 +58,7 @@ ColumnLayout {
id: dismissButton
text: "×"
color: "#FFD063"
color: Theme.accent
font.pointSize: 18
MouseArea {
@@ -71,7 +72,7 @@ ColumnLayout {
Text {
visible: root.modelData.body !== ""
text: root.modelData.body
color: "#EEEEEE"
color: Theme.text
font.pointSize: 12
wrapMode: Text.Wrap
@@ -92,9 +93,9 @@ ColumnLayout {
required property NotificationAction modelData
color: "#292C30"
color: Theme.raised
border.width: 1
border.color: "#FFD063"
border.color: Theme.accent
implicitHeight: 28
implicitWidth: actionText.implicitWidth + 16
@@ -102,7 +103,7 @@ ColumnLayout {
id: actionText
anchors.centerIn: parent
text: actionButton.modelData.text
color: "#EEEEEE"
color: Theme.text
font.pointSize: 12
}
@@ -2,6 +2,7 @@ import Quickshell
import Quickshell.Services.Pipewire
import Quickshell.Wayland
import QtQuick
import qs.widgets.theme
Scope {
id: root
@@ -68,7 +69,7 @@ Scope {
anchors.bottomMargin: 4
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.bold: true
}
@@ -84,7 +85,7 @@ Scope {
anchors.centerIn: parent
implicitHeight: 6
width: parent.width * Math.min(root.volume, root.maxVolume) / root.maxVolume
color: "#FF6B4A"
color: Theme.hot
visible: !root.muted && root.volume > 1
Behavior on width {
@@ -98,7 +99,7 @@ Scope {
anchors.centerIn: parent
implicitHeight: 6
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 {
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"
}
}
}
}
}
+14 -13
View File
@@ -6,10 +6,11 @@ import Quickshell.Widgets
import QtQuick
import QtQuick.Layouts
import QtQuick.Shapes
import qs.widgets.theme
// The right bar: a compact utility strip for the SystemTray icons, styled
// 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
// bracket-and-cap silhouette, since this is a secondary/utility bar rather
// than a hero surface.
@@ -54,8 +55,8 @@ Scope {
preferredRendererType: Shape.CurveRenderer
ShapePath {
fillColor: "#0F1012"
strokeColor: "#FFD063"
fillColor: Theme.surface
strokeColor: Theme.accent
strokeWidth: 2
startX: frame.chamfer
@@ -114,7 +115,7 @@ Scope {
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: 6
startY: 0
PathLine { x: 10; y: 0 }
@@ -124,7 +125,7 @@ Scope {
}
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: 15
startY: 0
PathLine { x: 19; y: 0 }
@@ -134,7 +135,7 @@ Scope {
}
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: 24
startY: 0
PathLine { x: 28; y: 0 }
@@ -155,7 +156,7 @@ Scope {
preferredRendererType: Shape.CurveRenderer
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: 8
startY: 0
PathLine { x: divider.width; y: 0 }
@@ -169,9 +170,9 @@ Scope {
Text {
Layout.alignment: Qt.AlignHCenter
text: "TRAY"
font.family: "Digital-7 Mono"
font.family: Theme.readoutFont
font.pointSize: 12
color: "#7A7B7D"
color: Theme.muted
}
ListView {
@@ -201,17 +202,17 @@ Scope {
Layout.alignment: Qt.AlignHCenter
visible: SystemTray.items.values.length === 0
text: "--"
font.family: "Digital-7 Mono"
font.family: Theme.readoutFont
font.pointSize: 14
color: "#7A7B7D"
color: Theme.muted
}
Text {
Layout.alignment: Qt.AlignHCenter
text: SystemTray.items.values.length
font.family: "Digital-7 Mono"
font.family: Theme.readoutFont
font.pointSize: 16
color: "#EEEEEE"
color: Theme.text
}
}
}
@@ -2,6 +2,7 @@ import Quickshell
import Quickshell.Services.SystemTray
import QtQuick
import QtQuick.Shapes
import qs.widgets.theme
// A single tray icon, chamfered to match the Slant (launcher V6 / SideBar)
// visual language instead of a plain rectangular hit target.
@@ -35,8 +36,8 @@ MouseArea {
ShapePath {
strokeWidth: 1
strokeColor: root.containsMouse ? "#FFD063" : "#7A7B7D"
fillColor: "#292C30"
strokeColor: root.containsMouse ? Theme.accent : Theme.muted
fillColor: Theme.raised
startX: root.chamfer
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.Shapes
import qs.widgets.theme
// 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
@@ -26,8 +27,8 @@ Item {
ShapePath {
strokeWidth: 1
strokeColor: bar.unknown ? "#3A3D42" : "#7A7B7D"
fillColor: "#292C30"
strokeColor: bar.unknown ? Theme.disabled : Theme.muted
fillColor: Theme.raised
startX: bar.chamfer
startY: 0
@@ -72,7 +73,7 @@ Item {
ShapePath {
strokeWidth: 0
fillColor: bar.hot ? "#FF6B4A" : "#FFD063"
fillColor: bar.hot ? Theme.hot : Theme.accent
Behavior on fillColor {
ColorAnimation {
+43 -42
View File
@@ -6,6 +6,7 @@ import Quickshell.Wayland
import QtQuick
import QtQuick.Layouts
import QtQuick.Shapes
import qs.widgets.theme
// Host vitals HUD — the "Slant" language (chamfered panel, trapezoid bevels,
// outward corner chunks, floating cap, slanted dividers) carried over from the
@@ -59,7 +60,7 @@ Scope {
// Dim backdrop — click anywhere to dismiss.
Rectangle {
anchors.fill: parent
color: "#0A0A0C"
color: Theme.surface
opacity: 0.5
MouseArea {
@@ -102,8 +103,8 @@ Scope {
preferredRendererType: Shape.CurveRenderer
ShapePath {
fillColor: "#0F1012"
strokeColor: "#FFD063"
fillColor: Theme.surface
strokeColor: Theme.accent
strokeWidth: 2
startX: frame.chamfer
@@ -121,7 +122,7 @@ Scope {
// Thick top-left bevel accent.
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: 0
startY: frame.chamfer
@@ -134,7 +135,7 @@ Scope {
// Thick bottom-right bevel accent.
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: panelShape.width
startY: panelShape.height - frame.chamfer
@@ -147,7 +148,7 @@ Scope {
// Floating triangle cap in the bottom-right notch.
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: panelShape.width
startY: panelShape.height
@@ -159,7 +160,7 @@ Scope {
// Heavy outward chunk wrapping the bottom-left corner.
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: panelShape.width / 3
startY: panelShape.height
@@ -176,7 +177,7 @@ Scope {
// Heavy outward chunk wrapping the top-right corner.
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: panelShape.width
startY: panelShape.height / 3
@@ -217,7 +218,7 @@ Scope {
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: 6
startY: 0
PathLine { x: 10; y: 0 }
@@ -227,7 +228,7 @@ Scope {
}
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: 15
startY: 0
PathLine { x: 19; y: 0 }
@@ -237,7 +238,7 @@ Scope {
}
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: 24
startY: 0
PathLine { x: 28; y: 0 }
@@ -249,8 +250,8 @@ Scope {
Text {
text: (vitals.host || "vitals").toUpperCase()
color: "#EEEEEE"
font.family: "Digital-7 Mono"
color: Theme.text
font.family: Theme.readoutFont
font.pointSize: 20
font.letterSpacing: 2
}
@@ -261,15 +262,15 @@ Scope {
Text {
text: "UP"
color: "#7A7B7D"
font.family: "Digital-7 Mono"
color: Theme.muted
font.family: Theme.readoutFont
font.pointSize: 11
}
Text {
text: vitals.fmtUptime(vitals.uptime)
color: "#FFD063"
font.family: "Digital-7 Mono"
color: Theme.accent
font.family: Theme.readoutFont
font.pointSize: 16
}
}
@@ -281,8 +282,8 @@ Scope {
Layout.fillWidth: true
visible: vitals.failed
text: "NODE_EXPORTER UNREACHABLE ON :" + vitals.port
color: "#FF6B4A"
font.family: "Digital-7 Mono"
color: Theme.hot
font.family: Theme.readoutFont
font.pointSize: 13
}
@@ -353,7 +354,7 @@ Scope {
Text {
Layout.preferredWidth: 96
text: diskRow.modelData.mount
color: "#7A7B7D"
color: Theme.muted
font.pointSize: 9
elide: Text.ElideMiddle
}
@@ -369,8 +370,8 @@ Scope {
Layout.preferredWidth: 48
horizontalAlignment: Text.AlignRight
text: Math.round(diskRow.frac * 100) + "%"
color: diskRow.frac >= 0.9 ? "#FF6B4A" : "#EEEEEE"
font.family: "Digital-7 Mono"
color: diskRow.frac >= 0.9 ? Theme.hot : Theme.text
font.family: Theme.readoutFont
font.pointSize: 13
}
@@ -378,8 +379,8 @@ Scope {
Layout.preferredWidth: 104
horizontalAlignment: Text.AlignRight
text: vitals.fmtBytes(diskRow.modelData.size - diskRow.modelData.used) + " FREE"
color: "#7A7B7D"
font.family: "Digital-7 Mono"
color: Theme.muted
font.family: Theme.readoutFont
font.pointSize: 11
}
}
@@ -399,21 +400,21 @@ Scope {
Text {
Layout.preferredWidth: 96
text: vitals.netIface || "NET"
color: "#7A7B7D"
color: Theme.muted
font.pointSize: 9
}
Text {
text: "RX"
color: "#7A7B7D"
font.family: "Digital-7 Mono"
color: Theme.muted
font.family: Theme.readoutFont
font.pointSize: 11
}
Text {
text: vitals.fmtRate(vitals.netRx)
color: "#FFD063"
font.family: "Digital-7 Mono"
color: Theme.accent
font.family: Theme.readoutFont
font.pointSize: 14
}
@@ -423,15 +424,15 @@ Scope {
Text {
text: "TX"
color: "#7A7B7D"
font.family: "Digital-7 Mono"
color: Theme.muted
font.family: Theme.readoutFont
font.pointSize: 11
}
Text {
text: vitals.fmtRate(vitals.netTx)
color: "#FFD063"
font.family: "Digital-7 Mono"
color: Theme.accent
font.family: Theme.readoutFont
font.pointSize: 14
// Right-align the TX readout against the panel edge the
@@ -469,8 +470,8 @@ Scope {
Text {
Layout.preferredWidth: 46
text: metric.label
color: "#FFD063"
font.family: "Digital-7 Mono"
color: Theme.accent
font.family: Theme.readoutFont
font.pointSize: 15
font.letterSpacing: 1
}
@@ -486,8 +487,8 @@ Scope {
Layout.preferredWidth: 54
horizontalAlignment: Text.AlignRight
text: metric.readout
color: "#EEEEEE"
font.family: "Digital-7 Mono"
color: Theme.text
font.family: Theme.readoutFont
font.pointSize: 16
}
}
@@ -499,8 +500,8 @@ Scope {
Text {
text: metric.detail
color: "#7A7B7D"
font.family: "Digital-7 Mono"
color: Theme.muted
font.family: Theme.readoutFont
font.pointSize: 11
}
@@ -511,8 +512,8 @@ Scope {
Text {
visible: metric.aside.length > 0
text: metric.aside
color: metric.asideHot ? "#FF6B4A" : "#7A7B7D"
font.family: "Digital-7 Mono"
color: metric.asideHot ? Theme.hot : Theme.muted
font.family: Theme.readoutFont
font.pointSize: 11
}
}
@@ -530,7 +531,7 @@ Scope {
ShapePath {
strokeWidth: 0
fillColor: "#FFD063"
fillColor: Theme.accent
startX: 6
startY: 0
PathLine { x: divider.width; y: 0 }
Generated
+18 -18
View File
@@ -14,11 +14,11 @@
"uv2nix": "uv2nix"
},
"locked": {
"lastModified": 1786986906,
"narHash": "sha256-DJ1oU9szQJNdEM0dysh4NnKOB1HwOKtNukrUYKpawVs=",
"lastModified": 1787577519,
"narHash": "sha256-YNAXQTgR26RJiX2vtYjk6OtBu2jMWeq4qUN6sUrt6Lc=",
"owner": "nix-community",
"repo": "authentik-nix",
"rev": "afdb2eeca1e0b38fabb93c4a8944be73d3581268",
"rev": "30c37930450d7a5fefa8ffec613f037fc75c3071",
"type": "github"
},
"original": {
@@ -270,11 +270,11 @@
"treefmt-nix": "treefmt-nix"
},
"locked": {
"lastModified": 1787124618,
"narHash": "sha256-aKf1k2hvYgaxP9oxDPRiv9npEJLODC9eKxk7nR69lzQ=",
"lastModified": 1787728766,
"narHash": "sha256-g2oZlrBU3AI2ubCiY/UyE9ALTIDueTcF//QP3vaY9IQ=",
"owner": "nix-community",
"repo": "nixos-anywhere",
"rev": "ad8fa24e11eef167fd72d49fafefa3f840312d71",
"rev": "6b77f26ec4538ced04bf1d02f374b0ec02e9c27e",
"type": "github"
},
"original": {
@@ -291,11 +291,11 @@
"nixos-unstable": "nixos-unstable"
},
"locked": {
"lastModified": 1787222173,
"narHash": "sha256-acp6QJnWVnLvnanC59CMkiDC/i0ZhdFKiB7prru9SHw=",
"lastModified": 1787826771,
"narHash": "sha256-gWkyr3I/cg4SHWGkAoUo24+BQaqoi+S0T2JxDjsA+pw=",
"owner": "nix-community",
"repo": "nixos-images",
"rev": "e17386d9193d6d5a90f1b4b6a8a5cd2620d34b56",
"rev": "f6714acc84ce92df7286c89a571ad1e946057a5b",
"type": "github"
},
"original": {
@@ -354,11 +354,11 @@
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1787209939,
"narHash": "sha256-WvvHR4kSQLAbtouMC/ruZ5UpLwlUcY3K4FAllMN+yGk=",
"lastModified": 1787814960,
"narHash": "sha256-PYZq1qzCJXC2zGI0mH07vrZBsw6DRBAOX0jN1pPtqOQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "391b592eb44808b3bd0cb80bb71b63a5a118b8bb",
"rev": "c27cdad491a991b11ed731760aa2ef8db0cb0410",
"type": "github"
},
"original": {
@@ -370,11 +370,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1787204541,
"narHash": "sha256-OURZPknrTjQrlNyxPdqzyqmU/81Wes1CUP/Ft1Rv/YI=",
"lastModified": 1787753485,
"narHash": "sha256-BZWCi9ZRJiARTuKTbbtvFTj7t1TK4G3UEckT3HyNfRg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "5880666fd9eb563038431edb35c2d0aa595884e6",
"rev": "062346a6d85bc4b49dfaa61c986e9c5be21217d1",
"type": "github"
},
"original": {
@@ -400,11 +400,11 @@
]
},
"locked": {
"lastModified": 1785730568,
"narHash": "sha256-NjSPsgjJ7MSpBtTkUcmNhRe6AFZ96+zsca2M8YuQi8Y=",
"lastModified": 1786936886,
"narHash": "sha256-8SFyOdmcG6Nsh/JzlH812lTI/v+ur06fpQhj/acNn8k=",
"owner": "pyproject-nix",
"repo": "build-system-pkgs",
"rev": "90fde00db3687922d39d95fc591475fd0bbbcd72",
"rev": "90ffdeee1a4929b231913df067448cd9803d3e07",
"type": "github"
},
"original": {
+108
View File
@@ -552,5 +552,113 @@
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"
'';
};
};
}
-1
View File
@@ -54,7 +54,6 @@ in
pkgs.hyprcursor
pkgs.bibata-cursors
pkgs.papirus-icon-theme
tome
];
xdg.desktopEntries.btop = {
+6 -2
View File
@@ -36,7 +36,7 @@ let
# 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
# 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
# 0.55's src/config/lua/bindings/LuaBindingsDispatchers.cpp).
@@ -198,8 +198,12 @@ in
# 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)
(bind "SUPER + CTRL + 0" (dsp.exec "qs kill; sleep 0.3; qs"))
# toggle the Slant sidebar
+38
View File
@@ -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"]
+39
View File
@@ -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.
+11
View File
@@ -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 "$@"
+58
View File
@@ -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"