WIP
This commit is contained in:
@@ -99,16 +99,21 @@ on it, hence the index loops in `HyprChromeShell`.
|
||||
grid) shared by the rail and the polkit prompt; `Widgets/Bar/` holds the rail
|
||||
and its panels, with `Bar/Panels/BarPanel.qml` the chamfered chrome they all
|
||||
extend; `Widgets/Polkit/` is the authentication agent and its dialog;
|
||||
`Widgets/Launcher/` is the primary application launcher (`SUPER_L`);
|
||||
`Theme/Theme.qml` is this tree's palette singleton. `DebugWindow.qml` stages a
|
||||
single widget on the secondary monitor for eyeballing it in isolation.
|
||||
|
||||
The prompt and the launcher are MODALS: each raises the shared scrim, lands
|
||||
on the focused monitor, and takes the keyboard off the rail. That is why the
|
||||
shell instantiates them rather than `shell.qml` — see `modalOpen` there, which
|
||||
is the one place a new modal has to be named.
|
||||
- `widgets/bar/` — `DenseBar` and `StatusBarPanel`, the rail's predecessor. Not
|
||||
instantiated by `shell.qml` any more; `StatusBarPanel` is still used by the
|
||||
launchers.
|
||||
- `widgets/launcher/` — shared `AppModel` search/execution plus eleven launcher
|
||||
variants. `ApplicationLauncher` (variant 8, and the primary `SUPER` launcher)
|
||||
keeps its visual core in the headlessly renderable
|
||||
`ApplicationLauncherContent`; the rest are available on `SUPER CTRL 1–11` for
|
||||
comparison.
|
||||
remaining launcher variants.
|
||||
- `widgets/launcher/` — shared `AppModel` search/execution plus the ten launcher
|
||||
variants still under evaluation, on `SUPER CTRL 1–11`. Variant 8 has moved to
|
||||
`HyprChrome/Widgets/Launcher/`; `AppModel.qml` is duplicated there so the
|
||||
HyprChrome tree stands alone, and this copy goes when the variants do.
|
||||
- `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.
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import QtQuick
|
||||
import qs.HyprChrome.Theme
|
||||
import qs.HyprChrome.Widgets.Polkit
|
||||
|
||||
// GUI password prompt for `sudo -A`, reusing the polkit dialog.
|
||||
//
|
||||
// sudo does NOT speak polkit — it is setuid + PAM reading your tty, and no
|
||||
// sudoers option bridges the two. What it does support is an ASKPASS helper: a
|
||||
// program it runs to obtain the password, which prints it on stdout. So this is
|
||||
// not the polkit agent serving sudo; it is a second, separate path that happens
|
||||
// to render the same dialog.
|
||||
//
|
||||
// Flow, driven by the helper in home.nix (`qs-askpass`):
|
||||
//
|
||||
// sudo -A
|
||||
// -> qs-askpass makes a 0600 fifo under XDG_RUNTIME_DIR
|
||||
// -> qs ipc call askpass prompt "<prompt>" "<fifo>" (returns at once)
|
||||
// -> this dialog opens, user types
|
||||
// -> a one-line writer is started here, secret written to its STDIN
|
||||
// -> qs-askpass reads the fifo and prints the secret on stdout
|
||||
// -> sudo reads it
|
||||
//
|
||||
// The secret travels on a pipe the whole way. It is never an argument and never
|
||||
// an environment variable, so it does not appear in /proc for any process — the
|
||||
// fifo PATH is in argv, which is not secret. It does cross more process
|
||||
// boundaries than the polkit path, where the password stays inside the PAM
|
||||
// conversation; that is the inherent cost of askpass, not of this design.
|
||||
//
|
||||
// Cancelling answers with an empty line, so the helper reads nothing, exits
|
||||
// non-zero, and sudo aborts rather than burning a retry on a blank password.
|
||||
Scope {
|
||||
id: root
|
||||
|
||||
// Which output to appear on; the shell puts it on the focused monitor.
|
||||
property var screen: null
|
||||
|
||||
// The fifo the helper is blocked reading. Non-empty means a request is in
|
||||
// flight, which is exactly what "a prompt is open" means here.
|
||||
property string fifoPath: ""
|
||||
property string promptText: ""
|
||||
property bool failed: false
|
||||
|
||||
readonly property bool active: root.fifoPath !== ""
|
||||
|
||||
// Held only between submit and the writer process actually starting: a
|
||||
// Process cannot be written to before it is running.
|
||||
property string pendingSecret: ""
|
||||
|
||||
IpcHandler {
|
||||
target: "askpass"
|
||||
|
||||
// Called by qs-askpass. Returns immediately — the helper blocks on the
|
||||
// fifo, not on this call, because an IpcHandler function runs on the
|
||||
// QML thread and blocking here would freeze the whole shell.
|
||||
function prompt(message: string, fifo: string): string {
|
||||
if (root.active)
|
||||
return "busy";
|
||||
|
||||
root.promptText = message === "" ? "Password:" : message;
|
||||
root.fifoPath = fifo;
|
||||
root.failed = false;
|
||||
return "ok";
|
||||
}
|
||||
|
||||
// So a helper that times out can take the dialog down with it rather
|
||||
// than leaving it on screen with nothing listening.
|
||||
function cancel(): string {
|
||||
root.dismiss();
|
||||
return "ok";
|
||||
}
|
||||
}
|
||||
|
||||
// Cancelling answers with an EMPTY line rather than by closing silently:
|
||||
// the helper then reads zero bytes and exits non-zero, so sudo aborts
|
||||
// instead of spending a retry on a blank password.
|
||||
function dismiss() {
|
||||
root.respond("");
|
||||
}
|
||||
|
||||
function submit(secret) {
|
||||
root.respond(secret);
|
||||
}
|
||||
|
||||
// The writer reads ONE LINE and exits; it does not wait for EOF.
|
||||
//
|
||||
// The obvious version — `cat > fifo`, write the secret, then close stdin by
|
||||
// setting stdinEnabled false — does not terminate. Measured: the secret
|
||||
// arrives intact but `cat` never sees EOF, so the fifo is never closed and
|
||||
// the helper blocks until its timeout. sudo would hang after you typed.
|
||||
//
|
||||
// A single `read` needs no EOF at all: the trailing newline ends it, the
|
||||
// shell writes what it got and exits, and THAT close is what gives the
|
||||
// helper its EOF. `IFS=` keeps leading and trailing whitespace, `-r` keeps
|
||||
// backslashes, and the secret still travels on stdin rather than in argv.
|
||||
function respond(secret) {
|
||||
if (!root.active)
|
||||
return;
|
||||
|
||||
root.pendingSecret = secret + "\n";
|
||||
writer.command = ["sh", "-c", "IFS= read -r line; printf %s \"$line\" > \"$1\"", "sh", root.fifoPath];
|
||||
writer.running = true;
|
||||
root.fifoPath = "";
|
||||
}
|
||||
|
||||
// Opening a fifo for writing BLOCKS until a reader attaches, which is why
|
||||
// this is a subprocess rather than a FileView: the helper's `cat` is that
|
||||
// reader, and blocking the QML thread on it would freeze the shell.
|
||||
Process {
|
||||
id: writer
|
||||
|
||||
stdinEnabled: true
|
||||
|
||||
// Written on `started`, not at respond() time: a Process has no stdin
|
||||
// to write to until it is actually running.
|
||||
onStarted: {
|
||||
writer.write(root.pendingSecret);
|
||||
root.pendingSecret = "";
|
||||
}
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
id: win
|
||||
|
||||
screen: root.screen
|
||||
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
|
||||
}
|
||||
|
||||
// No click-off dismissal, for the same reason the polkit dialog has
|
||||
// none: something is blocked waiting on the answer, and losing it to a
|
||||
// stray click would leave sudo hanging with no visible cause.
|
||||
|
||||
PolkitPromptContent {
|
||||
id: content
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
y: Math.max(32, Math.round(parent.height / 3 - height / 2))
|
||||
width: 520
|
||||
|
||||
// Deliberately the polkit dialog's own content component: this is a
|
||||
// password prompt with the same shape, and keeping one means a
|
||||
// restyle of PolkitPanel covers both. `identities` stays empty —
|
||||
// sudo offers no choice of who authenticates — which hides the
|
||||
// picker and the "AS" line on its own.
|
||||
message: "Authentication is required to run a command as another user"
|
||||
actionId: "sudo"
|
||||
iconName: ""
|
||||
showIcon: false
|
||||
identities: []
|
||||
|
||||
responseRequired: true
|
||||
inputPrompt: root.promptText
|
||||
responseVisible: false
|
||||
failed: root.failed
|
||||
|
||||
onSubmitted: value => root.submit(value)
|
||||
onCancelled: root.dismiss()
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (win.visible) {
|
||||
content.clearResponse();
|
||||
content.focusInput();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import Quickshell.Wayland
|
||||
import qs.HyprChrome.Widgets.Bar
|
||||
import qs.HyprChrome.Widgets
|
||||
import qs.HyprChrome.Widgets.Polkit
|
||||
import qs.HyprChrome.Widgets.Launcher
|
||||
import qs.HyprChrome.Widgets.Askpass
|
||||
|
||||
// The hyprchrome shell: owns everything the rail's surfaces have to agree on,
|
||||
// and instantiates them.
|
||||
@@ -17,10 +19,11 @@ import qs.HyprChrome.Widgets.Polkit
|
||||
// * which monitor the shell lives on — every surface has to pick the same one
|
||||
// * the density — the whole rail expands and collapses as one, so the toggle
|
||||
// and the shortcut that drives it belong to the shell, not to the bar
|
||||
// * whether an authorization prompt is up — it raises the same scrim the rail
|
||||
// uses and freezes the density while it is open, so two surfaces read it.
|
||||
// That is why the agent lives here rather than as a sibling of the
|
||||
// launchers in shell.qml
|
||||
// * whether a MODAL is open — the polkit prompt, the launcher, or the sudo
|
||||
// askpass dialog. Each raises the same scrim the rail uses, freezes the
|
||||
// density, lands on the focused monitor and takes the keyboard off the
|
||||
// rail, so several surfaces read it. That is why they live here rather than
|
||||
// as siblings of the remaining launcher variants in shell.qml
|
||||
// * the layer PAIR — the backdrop must sit exactly one layer below the bar in
|
||||
// both densities. Two surfaces on the same layer stack by creation order,
|
||||
// which is not something to rely on; one layer apart is a guarantee. Split
|
||||
@@ -96,24 +99,30 @@ Scope {
|
||||
// Frozen while a prompt is up, and dropped rather than queued: SUPER A
|
||||
// during a prompt does nothing at all, instead of arming a change that
|
||||
// springs the rail open or shut the moment the dialog goes.
|
||||
if (polkit.prompting)
|
||||
if (shell.modalOpen)
|
||||
return;
|
||||
|
||||
shell.expanded = !shell.expanded;
|
||||
}
|
||||
|
||||
// Whether the scrim is up, from EITHER cause. This is the fact the surfaces
|
||||
// actually share — the rail's density is only one of the two things that
|
||||
// can raise it — so the backdrop and the layer pair below key off this
|
||||
// rather than off `expanded`.
|
||||
// The surfaces that take over the screen: they dim EVERY output, land on the
|
||||
// focused one, and take the keyboard off the rail. Grouped because
|
||||
// everything below treats them alike, so a fourth one joins by being named
|
||||
// here and nowhere else.
|
||||
readonly property bool modalOpen: polkit.prompting || launcher.active || askpass.active
|
||||
|
||||
// Whether the scrim is up, from ANY cause. This is the fact the surfaces
|
||||
// actually share — the rail's density is only one of the things that can
|
||||
// raise it — so the backdrop and the layer pair below key off this rather
|
||||
// than off `expanded`.
|
||||
//
|
||||
// One backdrop instance serves both. A prompt arriving over an already
|
||||
// expanded rail therefore changes nothing about the scrim: it is already up,
|
||||
// already full height, and the dialog simply appears above it. A prompt over
|
||||
// a COLLAPSED rail expands that same scrim from its bar-height band to the
|
||||
// whole output, using the animation it already has, and the rail stays
|
||||
// collapsed throughout.
|
||||
readonly property bool scrimUp: shell.expanded || polkit.prompting
|
||||
// One backdrop instance serves all of them. A modal opening over an already
|
||||
// expanded rail therefore changes nothing about the scrim on that monitor:
|
||||
// it is already up, already full height, and the modal simply appears above
|
||||
// it. Over a COLLAPSED rail the same scrim expands from its bar-height band
|
||||
// to the whole output, using the animation it already has, and the rail
|
||||
// stays collapsed throughout.
|
||||
readonly property bool scrimUp: shell.expanded || shell.modalOpen
|
||||
|
||||
// Scrim up, the rail is over everything; scrim down, it drops below ordinary
|
||||
// windows. BOTTOM rather than BACKGROUND for the lowered bar: it is the
|
||||
@@ -174,7 +183,7 @@ Scope {
|
||||
required property var modelData
|
||||
|
||||
screen: modelData
|
||||
active: polkit.prompting
|
||||
active: shell.modalOpen
|
||||
wlrLayer: WlrLayer.Top
|
||||
barHeight: 0
|
||||
}
|
||||
@@ -196,7 +205,7 @@ Scope {
|
||||
// the rail. Withheld rather than left to the compositor to arbitrate
|
||||
// between two exclusive surfaces, which would decide by stacking and
|
||||
// silently swap the order the day the layers change.
|
||||
grabsKeyboard: shell.expanded && !polkit.prompting
|
||||
grabsKeyboard: shell.expanded && !shell.modalOpen
|
||||
|
||||
onDismissed: shell.expanded = false
|
||||
}
|
||||
@@ -220,4 +229,23 @@ Scope {
|
||||
|
||||
screen: shell.focusedScreen
|
||||
}
|
||||
|
||||
// Primary application launcher — SUPER_L. Migrated out of
|
||||
// widgets/launcher/; the ten remaining variants are still evaluation copies
|
||||
// and stay in shell.qml. Declared after the bar for the same reason the
|
||||
// prompt is: while it is open the bar is on Overlay too, and there is no
|
||||
// layer above Overlay to escape to.
|
||||
AppLauncher {
|
||||
id: launcher
|
||||
|
||||
screen: shell.focusedScreen
|
||||
}
|
||||
|
||||
// GUI password prompt for `sudo -A`. Not the polkit agent — sudo cannot use
|
||||
// one — but it renders the same dialog. See the file for the flow.
|
||||
AskpassPrompt {
|
||||
id: askpass
|
||||
|
||||
screen: shell.focusedScreen
|
||||
}
|
||||
}
|
||||
|
||||
+39
-15
@@ -4,18 +4,41 @@ import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Wayland
|
||||
import QtQuick
|
||||
import qs.widgets.theme
|
||||
import qs.HyprChrome.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.
|
||||
// Primary application launcher — the one on SUPER_L.
|
||||
//
|
||||
// Migrated from widgets/launcher/ApplicationLauncher.qml. Two things changed in
|
||||
// the move, both because HyprChromeShell now owns the state its surfaces share:
|
||||
//
|
||||
// * no scrim of its own. The shell raises the single ChromeBackdrop for any
|
||||
// of its causes — expanded rail, polkit prompt, this — so opening the
|
||||
// launcher over an already-expanded rail reuses the scrim that is there
|
||||
// rather than laying a second dim on top of it.
|
||||
// * `active` is read by the shell, which uses it to raise that scrim, to
|
||||
// place this on the focused monitor, and to decide who gets the keyboard.
|
||||
//
|
||||
// The full-screen layer-shell adapter owns focus, DesktopEntries and execution;
|
||||
// AppLauncherContent stays an Item so the whole visual state can be rendered
|
||||
// headlessly (tests/AppLauncherHeadless.qml).
|
||||
Scope {
|
||||
id: root
|
||||
|
||||
property bool active: false
|
||||
|
||||
function toggle() { root.active = !root.active; }
|
||||
// Which output to appear on. Driven by the shell, which puts it on the
|
||||
// focused monitor — a launcher belongs where the user is looking, which is
|
||||
// not necessarily where the rail lives.
|
||||
property var screen: null
|
||||
|
||||
function toggle() { root.active = !root.active; }
|
||||
function close() { root.active = false; }
|
||||
|
||||
// The name is legacy: this was "variant 8" of eleven, and both SUPER_L (via
|
||||
// open_launcher.sh) and SUPER CTRL 8 still dispatch quickshell:launcher8.
|
||||
// Renaming it means editing hosts/terra/home/hyprland.nix AND the script
|
||||
// together, and neither takes effect until a deploy — so the shortcut would
|
||||
// be dead in the running session in between. Kept as-is deliberately.
|
||||
GlobalShortcut {
|
||||
name: "launcher8"
|
||||
description: "Toggle dense application command index"
|
||||
@@ -25,7 +48,9 @@ Scope {
|
||||
PanelWindow {
|
||||
id: win
|
||||
|
||||
screen: root.screen
|
||||
visible: root.active
|
||||
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
@@ -71,19 +96,18 @@ Scope {
|
||||
search: content.query
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
// Click-off dismissal. The scrim itself belongs to the shell and takes
|
||||
// no input (its mask is empty), so the catcher lives here: a
|
||||
// transparent full-surface MouseArea UNDER the content, which is what
|
||||
// keeps clicks on the launcher itself from closing it.
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
color: Theme.surface
|
||||
opacity: 0.72
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.active = false
|
||||
}
|
||||
onClicked: root.close()
|
||||
}
|
||||
|
||||
ApplicationLauncherContent {
|
||||
AppLauncherContent {
|
||||
id: content
|
||||
|
||||
anchors.centerIn: parent
|
||||
width: 1080
|
||||
height: 620
|
||||
@@ -95,7 +119,7 @@ Scope {
|
||||
onSelectionRequested: index => win.selectedIndex = win.clampSelection(index)
|
||||
onMoveRequested: delta => win.move(delta)
|
||||
onLaunchRequested: index => win.launch(index)
|
||||
onDismissRequested: root.active = false
|
||||
onDismissRequested: root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-9
@@ -5,12 +5,11 @@ import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Shapes
|
||||
import qs.widgets.bar
|
||||
import qs.widgets.theme
|
||||
import qs.HyprChrome.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.
|
||||
// Runtime concerns (DesktopEntries, layer shell, focus, launching) stay in
|
||||
// AppLauncher.qml; this component only renders state and emits intent.
|
||||
Item {
|
||||
id: root
|
||||
|
||||
@@ -70,7 +69,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
StatusBarPanel {
|
||||
LauncherPanel {
|
||||
anchors.fill: parent
|
||||
panelId: "008"
|
||||
title: "APPLICATION COMMAND INDEX"
|
||||
@@ -78,7 +77,7 @@ Item {
|
||||
chamfer: 18
|
||||
|
||||
// Query module.
|
||||
StatusBarPanel {
|
||||
LauncherPanel {
|
||||
id: queryPanel
|
||||
x: 18
|
||||
y: 34
|
||||
@@ -184,7 +183,7 @@ Item {
|
||||
}
|
||||
|
||||
// Search result table.
|
||||
StatusBarPanel {
|
||||
LauncherPanel {
|
||||
id: resultPanel
|
||||
x: 18
|
||||
y: 118
|
||||
@@ -347,7 +346,7 @@ Item {
|
||||
}
|
||||
|
||||
// Selected application inspector.
|
||||
StatusBarPanel {
|
||||
LauncherPanel {
|
||||
id: inspector
|
||||
x: 700
|
||||
y: 34
|
||||
@@ -569,7 +568,7 @@ Item {
|
||||
}
|
||||
|
||||
// Dense command footer.
|
||||
StatusBarPanel {
|
||||
LauncherPanel {
|
||||
x: 18
|
||||
y: 550
|
||||
width: 1044
|
||||
@@ -0,0 +1,44 @@
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
|
||||
// Non-visual, reusable app-search model shared by every launcher variant.
|
||||
// Set `search`; read `apps` (a ranked, filtered list of DesktopEntry).
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
property string search: ""
|
||||
|
||||
// `keywords`/`categories` come through as string lists, so coerce every
|
||||
// field to a string before matching (String([]) joins with commas).
|
||||
function haystack(a) {
|
||||
return (String(a.name || "") + " " + String(a.genericName || "") + " " + String(a.comment || "") + " " + String(a.keywords || "")).toLowerCase();
|
||||
}
|
||||
|
||||
readonly property var apps: {
|
||||
const all = DesktopEntries.applications.values.filter(a => !a.noDisplay);
|
||||
const q = root.search.trim().toLowerCase();
|
||||
|
||||
if (q.length === 0)
|
||||
return all.slice().sort((x, y) => String(x.name).localeCompare(String(y.name)));
|
||||
|
||||
const matches = all.filter(a => root.haystack(a).includes(q));
|
||||
|
||||
// Prefix matches on the visible name rank first, then alphabetical.
|
||||
return matches.slice().sort((x, y) => {
|
||||
const xs = String(x.name).toLowerCase().startsWith(q) ? 0 : 1;
|
||||
const ys = String(y.name).toLowerCase().startsWith(q) ? 0 : 1;
|
||||
if (xs !== ys)
|
||||
return xs - ys;
|
||||
return String(x.name).localeCompare(String(y.name));
|
||||
});
|
||||
}
|
||||
|
||||
function launch(index) {
|
||||
const list = root.apps;
|
||||
if (index >= 0 && index < list.length) {
|
||||
list[index].execute();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import qs.HyprChrome.Theme
|
||||
|
||||
// Chamfered panel chrome for the launcher: outline, corner accent lines, and
|
||||
// the optional header strip (id chip / title / meta / tick marks). Content is
|
||||
// supplied as children by the call site.
|
||||
//
|
||||
// A sibling of BarPanel rather than a use of it: BarPanel is almost entirely
|
||||
// density machinery (summary slot, animated height, state pair, transitions)
|
||||
// for a rail that expands and collapses, and the launcher has exactly one
|
||||
// density. Same reasoning as PolkitPanel — see that file.
|
||||
//
|
||||
// Carried over from widgets/bar/StatusBarPanel.qml, which the legacy launcher
|
||||
// variants still use. Restyle this one freely; it is read only by the launcher.
|
||||
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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,11 @@ Scope {
|
||||
// Dense multi-monitor status rail; visual core is headlessly renderable.
|
||||
HyprChromeShell {}
|
||||
|
||||
// App launcher variants — 1–11; variant 8 remains the primary HUD.
|
||||
// App launcher variants still under evaluation, on SUPER CTRL 1–11.
|
||||
// Variant 8 — the primary launcher on SUPER_L — has moved into
|
||||
// HyprChrome/Widgets/Launcher and is instantiated by HyprChromeShell,
|
||||
// because the shell owns the scrim, the focused monitor and the keyboard
|
||||
// arbitration it now shares with the polkit prompt.
|
||||
LauncherStack {} // 1 — left vertical list
|
||||
LauncherGrid {} // 2 — centered icon grid
|
||||
LauncherSpotlight {} // 3 — top-center command bar
|
||||
@@ -28,7 +32,6 @@ 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
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
import qs.widgets.launcher
|
||||
import qs.HyprChrome.Widgets.Launcher
|
||||
|
||||
ApplicationLauncherContent {
|
||||
AppLauncherContent {
|
||||
width: 1080
|
||||
height: 620
|
||||
|
||||
Reference in New Issue
Block a user