theme: soften the YoRHa shapes; SUPER+D opens the dashboard

The square markers, block meters and hard-cornered fills read as boxy
next to the porthole. The desktop is now soft throughout: the porthole
is the shape everything is built around and every smaller surface echoes
it.

- Theme gains radiusS / radiusM / radiusL. radiusL is frameRadius - padS,
  so anything inset padS inside one of the frame's curves is concentric
  with it.
- P5Panel and Skew draw rounded rectangles (Geom.roundedRect) whenever
  they neither lean nor are chopped, which is always now; the polygon
  path is kept for shapes brought back on purpose.
- Markers are dots; workspace pips, toggles, transport and media chips,
  notification actions and today's date are pills or discs; the slider
  handle is round. Meters are one continuous rounded line that glides to
  its level instead of a row of segments.
- Album art is rounded with ClippingRectangle in the bar, the media
  popout and the dashboard.
- Free-standing panels (launcher, ask) drop the corner brackets and take
  the frame radius.
- Fix: a hovered notification's wash was a full-bleed square, so in the
  drawer's bottom corner it ran past the drawer's curve. It is inset by
  padS/2 and rounded by radiusL, which keeps it inside the curve.

Hyprland: the discord special workspace and its window rule are gone,
SUPER+S stays the only scratchpad, and SUPER+D now opens the dashboard
(it was on SUPER+I).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
nvrlandClaude Opus 5.5 committed 2026-10-04 20:56:43 +02:00
1 parent d6de04eaaf
commit a8c19961e2
24 files changed
+184 -106

No files matched your search

+1 -1
View File
@@ -9,7 +9,7 @@ end
shell("lock", { key = mod .. " + CTRL + L", description = "lock screen" }) shell("lock", { key = mod .. " + CTRL + L", description = "lock screen" })
shell("power", { key = mod .. " + P", description = "power menu" }) shell("power", { key = mod .. " + P", description = "power menu" })
shell("ask", { key = mod .. " + A", description = "ask claude" }) shell("ask", { key = mod .. " + A", description = "ask claude" })
shell("dashboard", { key = mod .. " + I", description = "dashboard" }) shell("dashboard", { key = mod .. " + D", description = "dashboard" })
shell("toggleDnd", { key = mod .. " + SHIFT + N", description = "toggle do not disturb" }) shell("toggleDnd", { key = mod .. " + SHIFT + N", description = "toggle do not disturb" })
shell("clearNotifications", { key = mod .. " + CTRL + SHIFT + N", description = "clear notifications" }) shell("clearNotifications", { key = mod .. " + CTRL + SHIFT + N", description = "clear notifications" })
shell("reloadConfig", { key = mod .. " + SHIFT + R", description = "reload shell config" }) shell("reloadConfig", { key = mod .. " + SHIFT + R", description = "reload shell config" })
+4 -8
View File
@@ -1,4 +1,4 @@
-- Workspaces, including the two scratchpads. -- Workspaces, including the scratchpad.
local mod = "SUPER" local mod = "SUPER"
@@ -13,10 +13,6 @@ end
hl.bind(mod .. " + mouse_down", hl.dsp.focus({ workspace = "e+1" })) hl.bind(mod .. " + mouse_down", hl.dsp.focus({ workspace = "e+1" }))
hl.bind(mod .. " + mouse_up", hl.dsp.focus({ workspace = "e-1" })) hl.bind(mod .. " + mouse_up", hl.dsp.focus({ workspace = "e-1" }))
-- Scratchpads: S holds anything virtual, D holds chat. -- Scratchpad: S holds anything virtual. Chat opens wherever it is launched.
local special = { S = "virtual", D = "discord" } hl.bind(mod .. " + S", hl.dsp.workspace.toggle_special("virtual"))
hl.bind(mod .. " + SHIFT + S", hl.dsp.window.move({ workspace = "special:virtual" }))
for key, name in pairs(special) do
hl.bind(mod .. " + " .. key, hl.dsp.workspace.toggle_special(name))
hl.bind(mod .. " + SHIFT + " .. key, hl.dsp.window.move({ workspace = "special:" .. name }))
end
-1
View File
@@ -54,7 +54,6 @@ hl.window_rule({ match = { class = GAMESCOPE }, workspace = "1" })
-- Special workspaces -- Special workspaces
hl.window_rule({ match = { class = "^(Spotify|spotify)$" }, workspace = "special:virtual" }) hl.window_rule({ match = { class = "^(Spotify|spotify)$" }, workspace = "special:virtual" })
hl.window_rule({ match = { class = "^(discord|vesktop)$" }, workspace = "special:discord" })
-- ========================================== -- ==========================================
-- STEAM & GAMING -- STEAM & GAMING
+18 -13
View File
@@ -3,7 +3,7 @@
A NieR:Automata flavoured Quickshell desktop for Hyprland, kept dark and lit A NieR:Automata flavoured Quickshell desktop for Hyprland, kept dark and lit
like its wallpaper: a lone figure at a porthole, the void outside, one planet's like its wallpaper: a lone figure at a porthole, the void outside, one planet's
light. The planet white for anything live, the hull red for anything wrong, light. The planet white for anything live, the hull red for anything wrong,
grey-green for everything else. Hairlines, square markers, corner ticks, grey-green for everything else. Hairlines, small dots, soft rounded corners,
letter-spaced capitals, and selection shown by inverting the row. The bar, the letter-spaced capitals, and selection shown by inverting the row. The bar, the
rail and a thin border are one hull around the desktop, and the desktop is the rail and a thin border are one hull around the desktop, and the desktop is the
porthole cut out of it. porthole cut out of it.
@@ -91,7 +91,7 @@ left a thin strip of opening between itself and the rail.
## Dashboard ## Dashboard
`dashboard/Dashboard.qml` is a drawer that slides down out of the underside of `dashboard/Dashboard.qml` is a drawer that slides down out of the underside of
the bar — clock click, `SUPER + I`, or `qs ipc call shell dashboard`. It is the bar — clock click, `SUPER + D`, or `qs ipc call shell dashboard`. It is
painted in the hull colour and joins the bar through two concave fillets, so it painted in the hull colour and joins the bar through two concave fillets, so it
reads as more of the hull rather than as a floating card (caelestia's idea). reads as more of the hull rather than as a floating card (caelestia's idea).
Four columns divided by hairlines: operator and time, the month, the system Four columns divided by hairlines: operator and time, the month, the system
@@ -262,10 +262,9 @@ brings the marker up to full light, but never inverts. Destructive-active is
the same slab in red. The `treatment*` tokens in `Theme.qml` encode all of the same slab in red. The `treatment*` tokens in `Theme.qml` encode all of
this; use them rather than picking colours per widget. this; use them rather than picking colours per widget.
**Markers, not slashes.** The current row, a button under the pointer and the **Dots, not slashes.** The current row, a button under the pointer, the end of
end of a header rule carry a small square (`Theme.marker`). Popouts are framed a header rule and a notification's urgency carry a small dot (`Theme.marker`).
by corner ticks (`Theme.tick`) in their tone colour; the border itself is Borders are structure and stay neutral.
structure and stays neutral.
**Flicker, not print.** Display text (`SplitText`) is upright, tracked by **Flicker, not print.** Display text (`SplitText`) is upright, tracked by
`Theme.tracking`, on a one-pixel soft drop. The two ghosts either side of it `Theme.tracking`, on a one-pixel soft drop. The two ghosts either side of it
@@ -332,16 +331,22 @@ inside the ship and the windows sit in the porthole. The bar and the rail draw
no surface of their own and ignore exclusive zones; Frame reserves all four no surface of their own and ignore exclusive zones; Frame reserves all four
zones itself, so every piece is placed from the screen edge. zones itself, so every piece is placed from the screen edge.
**Rectangles, everywhere else.** NieR panels are plain boxes. `Theme.skew` and **Soft, everywhere else.** The porthole is the shape the desktop is built
`Theme.cut` are both `0`, so `P5Panel`, `Skew` and `Geom` all collapse to around, and every smaller surface is a gentler echo of it. `Theme.skew` and
rectangles; the knobs are kept so a shape can be brought back on purpose. The `Theme.cut` are `0`; `P5Panel` and `Skew` draw `Geom.roundedRect` with
porthole's curve is the only one on screen, which is what lets it read. `Theme.radiusS` (pills, chips, meter tracks), `radiusM` (rows, buttons) or
`radiusL`. `radiusL` is `frameRadius - padS`: anything inset by `padS` inside
one of the frame's curves — a hovered notification in the drawer's corner —
gets a corner concentric with it, so the gap stays even and nothing pokes past
the curve. Free-standing panels (launcher, ask) take `frameRadius` itself.
Toggles, transport buttons, workspace pips and today's date are pills or
discs; album art is rounded via `ClippingRectangle`.
**Motion glides.** No overshoot anywhere: things ease in on `OutCubic` or **Motion glides.** No overshoot anywhere: things ease in on `OutCubic` or
`OutExpo` and settle. Nothing scales up or nudges sideways on hover either; `OutExpo` and settle. Nothing scales up or nudges sideways on hover either;
hover brightens and marks, the way NieR's menus do. Meters are thin segment hover brightens and marks, the way NieR's menus do. Meters are one continuous
rows, album art sits square in a hairline frame, and the bar's player shows a rounded line that glides to its level, and the bar's player shows a
five-hairline signal meter. five-line signal meter.
Each surface is **one continuous slab**, with modules divided by hairline rules Each surface is **one continuous slab**, with modules divided by hairline rules
rather than by gaps. A row of separate floating cards reads as clutter; that is rather than by gaps. A row of separate floating cards reads as clutter; that is
+8 -5
View File
@@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Shapes import QtQuick.Shapes
import QtQuick.Window import QtQuick.Window
import Quickshell import Quickshell
import Quickshell.Widgets
import "root:/config" import "root:/config"
import "root:/components" import "root:/components"
import "root:/services" import "root:/services"
@@ -41,12 +42,13 @@ BarPill {
anchors.left: parent.left anchors.left: parent.left
spacing: Theme.padS spacing: Theme.padS
// Album art in a hairline frame. // Album art, rounded, in a hairline frame.
Rectangle { ClippingRectangle {
id: artFrame id: artFrame
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: 24 width: 24
height: 24 height: 24
radius: Theme.radiusS
color: Theme.surface color: Theme.surface
border.width: 1 border.width: 1
border.color: Theme.overlay border.color: Theme.overlay
@@ -111,9 +113,9 @@ BarPill {
} }
} }
// Signal meter: five hairline bars, NieR's audio readout rather than a // Signal meter: five thin rounded bars, NieR's audio readout rather than a
// chunky equaliser. It moves only while audio is playing and settles // chunky equaliser. It moves only while audio is playing and settles
// into a row of square dots when paused — the channel is open, there // into a row of dots when paused — the channel is open, there
// is just nothing on it. // is just nothing on it.
// //
// Sampled from a timer rather than tweened by an infinite // Sampled from a timer rather than tweened by an infinite
@@ -181,7 +183,8 @@ BarPill {
Rectangle { Rectangle {
required property int index required property int index
width: 1 width: 2
radius: 1
height: Media.playing ? eq.level(index) : 2 height: Media.playing ? eq.level(index) : 2
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: Media.playing ? Theme.primary : Theme.muted color: Media.playing ? Theme.primary : Theme.muted
+1
View File
@@ -135,6 +135,7 @@ Item {
anchors.leftMargin: root.wipeInset anchors.leftMargin: root.wipeInset
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: 2 width: 2
radius: 1
height: root.hovered ? Theme.marker * 2 : 0 height: root.hovered ? Theme.marker * 2 : 0
color: Theme.glow color: Theme.glow
+3 -1
View File
@@ -4,7 +4,7 @@ import Quickshell.Hyprland
import "root:/config" import "root:/config"
import "root:/components" import "root:/components"
// Workspace indicator: leaning ticks that stretch open when focused. Occupied // Workspace indicator: soft pills that stretch open when focused. Occupied
// workspaces fill in, urgent ones snap to the alarm colour. // workspaces fill in, urgent ones snap to the alarm colour.
Item { Item {
id: root id: root
@@ -82,6 +82,7 @@ Item {
Skew { Skew {
id: tick id: tick
anchors.fill: parent anchors.fill: parent
radius: height / 2
// Focused is a solid accent block. Urgent is a solid alarm // Focused is a solid accent block. Urgent is a solid alarm
// block — it used to be the accent too, which made an // block — it used to be the accent too, which made an
@@ -132,6 +133,7 @@ Item {
Rectangle { Rectangle {
visible: pip.info !== undefined && pip.info.fullscreen && !pip.focused visible: pip.info !== undefined && pip.info.fullscreen && !pip.focused
width: 4; height: 4 width: 4; height: 4
radius: 2
color: Theme.accent color: Theme.accent
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.bottom anchors.top: parent.bottom
+1
View File
@@ -18,4 +18,5 @@ P5Panel {
slashColor: tone slashColor: tone
slashWidth: 2 slashWidth: 2
padding: Theme.padS padding: Theme.padS
radius: Theme.radiusM
} }
+23 -3
View File
@@ -3,9 +3,10 @@ pragma Singleton
import QtQuick import QtQuick
import Quickshell import Quickshell
// Polygon maths for panel shapes. Under the YoRHa theme nothing leans and // Shape maths for panels. Under the YoRHa theme nothing leans and nothing is
// nothing is chopped (Theme.skew and Theme.cut are 0), so these collapse to // chopped (Theme.skew and Theme.cut are 0); surfaces are rounded rectangles
// plain rectangles; they are kept so a shape can be brought back on purpose. // (roundedRect). The polygon helpers are kept so a shape can be brought back
// on purpose.
// Originally the Persona 5 panel language: everything leans, and corners // Originally the Persona 5 panel language: everything leans, and corners
// get chopped rather than rounded. // get chopped rather than rounded.
Singleton { Singleton {
@@ -89,6 +90,25 @@ Singleton {
return root.closed(out); return root.closed(out);
} }
// A polygon as an SVG path string, for PathSvg.
function svg(pts: var): string {
if (pts.length === 0) return "";
let d = `M ${pts[0].x} ${pts[0].y}`;
for (let i = 1; i < pts.length; i++) d += ` L ${pts[i].x} ${pts[i].y}`;
return d + " Z";
}
// A w x h rectangle with every corner rounded by `r`, clamped so a short
// piece becomes a pill rather than folding over itself.
function roundedRect(w: real, h: real, r: real): string {
const k = Math.max(0, Math.min(r, w / 2, h / 2));
if (k <= 0) return root.svg([Qt.point(0, 0), Qt.point(w, 0), Qt.point(w, h), Qt.point(0, h)]);
return `M ${k} 0 H ${w - k} A ${k} ${k} 0 0 1 ${w} ${k}`
+ ` V ${h - k} A ${k} ${k} 0 0 1 ${w - k} ${h}`
+ ` H ${k} A ${k} ${k} 0 0 1 0 ${h - k}`
+ ` V ${k} A ${k} ${k} 0 0 1 ${k} 0 Z`;
}
function closed(pts: var): var { function closed(pts: var): var {
if (pts.length === 0) return pts; if (pts.length === 0) return pts;
const out = pts.slice(); const out = pts.slice();
+3 -1
View File
@@ -7,7 +7,7 @@ import "root:/config"
// came out as a faint milky film. This is the one row they share now. // came out as a faint milky film. This is the one row they share now.
// //
// Rest is bare. Hover lifts the row onto the surface tone. The current item — // Rest is bare. Hover lifts the row onto the surface tone. The current item —
// connected, in use — carries the NieR square marker at its leading edge and // connected, in use — carries a dot at its leading edge and
// an accent glyph, and its label goes to full weight. // an accent glyph, and its label goes to full weight.
Item { Item {
id: root id: root
@@ -30,6 +30,7 @@ Item {
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
radius: Theme.radiusM
color: root.hovered ? Theme.treatmentHoverFill : "transparent" color: root.hovered ? Theme.treatmentHoverFill : "transparent"
Behavior on color { Behavior on color {
@@ -43,6 +44,7 @@ Item {
anchors.leftMargin: 2 anchors.leftMargin: 2
width: root.current ? Theme.marker : 0 width: root.current ? Theme.marker : 0
height: width height: width
radius: width / 2
color: Theme.accent color: Theme.accent
Behavior on width { Behavior on width {
+24 -29
View File
@@ -2,8 +2,12 @@ import QtQuick
import "root:/config" import "root:/config"
import "root:/components" import "root:/components"
// A segmented load meter — upright ticks that light up left to right. Reads as // A load meter: a soft rounded track with the level filling it from the left.
// a gauge at a glance and keeps the panel language consistent. // It used to be a row of separate segments; one continuous line reads calmer,
// and a level that glides reads as a quantity rather than as a count.
//
// `segments`, `segmentWidth` and `spacing` are accepted and ignored, so the
// call sites that still size a segmented meter do not have to be chased down.
Item { Item {
id: root id: root
@@ -12,42 +16,33 @@ Item {
property real segmentWidth: 4 property real segmentWidth: 4
property real spacing: 3 property real spacing: 3
property color activeColor: Theme.heat(value) property color activeColor: Theme.heat(value)
property color idleColor: Theme.alpha(Theme.outline, 0.55) property color idleColor: Theme.alpha(Theme.outline, 0.35)
property real lean: Theme.skew property real lean: Theme.skew
readonly property int litCount: Math.round(Math.max(0, Math.min(100, value)) / 100 * segments)
implicitWidth: segments * segmentWidth + (segments - 1) * spacing implicitWidth: segments * segmentWidth + (segments - 1) * spacing
implicitHeight: 14 implicitHeight: 4
Row { readonly property real fraction: Math.max(0, Math.min(100, root.value)) / 100
Rectangle {
anchors.fill: parent anchors.fill: parent
spacing: root.spacing radius: height / 2
color: root.idleColor
}
Repeater { Rectangle {
model: root.segments height: parent.height
// Never narrower than its own height, so a near-zero level is a dot
Item { // rather than a sliver.
required property int index width: root.fraction > 0 ? Math.max(height, parent.width * root.fraction) : 0
width: root.segmentWidth radius: height / 2
height: root.height color: root.activeColor
Skew {
width: root.segmentWidth
height: root.height
lean: root.lean
color: parent.index < root.litCount ? root.activeColor : root.idleColor
// Peak segments glow, so a pegged CPU is visible peripherally.
opacity: parent.index < root.litCount
? (parent.index >= root.segments - 2 ? 1.0 : 0.9)
: 1.0
Behavior on width {
NumberAnimation { duration: Theme.durSlow; easing.type: Easing.OutCubic }
}
Behavior on color { Behavior on color {
ColorAnimation { duration: Theme.durBase } ColorAnimation { duration: Theme.durBase }
} }
} }
} }
}
}
}
+21 -11
View File
@@ -1,5 +1,6 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Widgets
import Quickshell.Services.Notifications import Quickshell.Services.Notifications
import "root:/config" import "root:/config"
import "root:/components" import "root:/components"
@@ -7,14 +8,14 @@ import "root:/services"
// One notification, used both in the history list and in the toast drawer. // One notification, used both in the history list and in the toast drawer.
// //
// Laid out like an entry in NieR's message log rather than as a card: a square // Laid out like an entry in NieR's message log rather than as a card: a dot
// marker and the sender's name running into a hairline, the summary, the body, // and the sender's name running into a hairline, the summary, the body, and
// and hairline buttons for any actions. There is no panel of its own — both // pill buttons for any actions. There is no panel of its own — both places it
// places it appears are already hull surfaces — so rest is bare, hover lifts // appears are already hull surfaces — so rest is bare, hover lifts the entry
// the row onto the surface tone, and entries are divided by the hairline in // onto a soft rounded wash, and entries are divided by the hairline in their
// their own header. // own header.
// //
// Urgency is the marker: the light for normal, grey for low, red for critical. // Urgency is the dot: the light for normal, grey for low, red for critical.
// A critical entry also takes the red into its sender line, and nothing else. // A critical entry also takes the red into its sender line, and nothing else.
Item { Item {
id: root id: root
@@ -38,8 +39,14 @@ Item {
implicitHeight: layout.implicitHeight + Theme.padM * 2 implicitHeight: layout.implicitHeight + Theme.padM * 2
// Hover wash. Inset by padS and rounded by radiusL, which is the frame's
// radius less that inset: an entry in the bottom corner of the drawer gets
// a corner concentric with the drawer's own, instead of a square one that
// ran out past the drawer's curve.
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.padS / 2
radius: Theme.radiusL
color: root.hovered ? Theme.treatmentHoverFill : "transparent" color: root.hovered ? Theme.treatmentHoverFill : "transparent"
Behavior on color { Behavior on color {
@@ -66,6 +73,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: Theme.marker width: Theme.marker
height: Theme.marker height: Theme.marker
radius: width / 2
color: root.tone color: root.tone
} }
@@ -162,12 +170,13 @@ Item {
renderType: Text.NativeRendering renderType: Text.NativeRendering
} }
// Inline image, when the sender supplied one, in a hairline frame. // Inline image, when the sender supplied one, rounded in a hairline frame.
Rectangle { ClippingRectangle {
x: Theme.marker + Theme.padS x: Theme.marker + Theme.padS
visible: inline.source != "" && inline.status === Image.Ready visible: inline.source != "" && inline.status === Image.Ready
width: inline.width + 2 width: inline.width + 2
height: inline.height + 2 height: inline.height + 2
radius: Theme.radiusM
color: "transparent" color: "transparent"
border.width: 1 border.width: 1
border.color: Theme.overlay border.color: Theme.overlay
@@ -192,8 +201,7 @@ Item {
Repeater { Repeater {
model: root.notif?.actions ?? [] model: root.notif?.actions ?? []
// A hairline button with a marker, the P5Button pattern at a // A hairline pill. Hover lifts and lights it; it never inverts.
// smaller size. Hover lifts and lights it; it never inverts.
Item { Item {
id: actionChip id: actionChip
required property var modelData required property var modelData
@@ -203,6 +211,7 @@ Item {
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
radius: height / 2
color: actionHover.hovered ? Theme.treatmentHoverFill : "transparent" color: actionHover.hovered ? Theme.treatmentHoverFill : "transparent"
border.width: 1 border.width: 1
border.color: actionHover.hovered ? Theme.treatmentHoverMark : Theme.overlay border.color: actionHover.hovered ? Theme.treatmentHoverMark : Theme.overlay
@@ -221,6 +230,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: 4 width: 4
height: 4 height: 4
radius: 2
color: actionLabel.color color: actionLabel.color
opacity: actionHover.hovered ? 1 : 0.4 opacity: actionHover.hovered ? 1 : 0.4
} }
+1
View File
@@ -90,6 +90,7 @@ FocusScope {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: Theme.marker width: Theme.marker
height: Theme.marker height: Theme.marker
radius: width / 2
color: label.color color: label.color
opacity: root.highlighted ? 1.0 : 0.35 opacity: root.highlighted ? 1.0 : 0.35
+19 -11
View File
@@ -5,9 +5,8 @@ import "root:/config"
import "root:/components" import "root:/components"
// The base surface for everything in this shell. Under the YoRHa theme it is a // The base surface for everything in this shell. Under the YoRHa theme it is a
// plain rectangle with a hairline border, an optional dot wash, an optional // rounded rectangle with a hairline border, an optional dot wash, an optional
// accent bar down its leading edge, and optional corner ticks — the brackets // accent bar down its leading edge, and optional corner ticks.
// NieR frames its panels with.
// //
// The lean and chop knobs still exist and still work, so a caller can bring a // The lean and chop knobs still exist and still work, so a caller can bring a
// shape back on purpose, but Theme.skew and Theme.cut are both zero. // shape back on purpose, but Theme.skew and Theme.cut are both zero.
@@ -34,6 +33,10 @@ Item {
property real chop: Theme.cut property real chop: Theme.cut
property var chopCorners: [1, 3] property var chopCorners: [1, 3]
// Corner radius. Used whenever the panel neither leans nor is chopped,
// which under the YoRHa theme is always.
property real radius: Theme.radiusM
// Accent bar down the leading edge. // Accent bar down the leading edge.
property bool slash: false property bool slash: false
property color slashColor: Theme.accent property color slashColor: Theme.accent
@@ -67,6 +70,11 @@ Item {
readonly property real leanInset: readonly property real leanInset:
(vertical ? width : height) * Math.abs(lean) (vertical ? width : height) * Math.abs(lean)
// The outline every layer below is drawn from.
readonly property string outline: root.lean === 0 && root.chop <= 0
? Geom.roundedRect(root.width, root.height, root.radius)
: Geom.svg(Geom.chamfer(root.polygon, root.chop, root.chopCorners).slice(0, -1))
readonly property var polygon: vertical readonly property var polygon: vertical
? (leanLeft ? Geom.parallelogramVL(width, height, lean) ? (leanLeft ? Geom.parallelogramVL(width, height, lean)
: Geom.parallelogramV(width, height, lean)) : Geom.parallelogramV(width, height, lean))
@@ -99,8 +107,8 @@ Item {
strokeColor: "transparent" strokeColor: "transparent"
joinStyle: ShapePath.MiterJoin joinStyle: ShapePath.MiterJoin
PathPolyline { PathSvg {
path: Geom.chamfer(root.polygon, root.chop, root.chopCorners) path: root.outline
} }
} }
} }
@@ -123,8 +131,8 @@ Item {
strokeColor: "transparent" strokeColor: "transparent"
joinStyle: ShapePath.MiterJoin joinStyle: ShapePath.MiterJoin
PathPolyline { PathSvg {
path: Geom.chamfer(root.polygon, root.chop, root.chopCorners) path: root.outline
} }
} }
} }
@@ -148,8 +156,8 @@ Item {
joinStyle: ShapePath.MiterJoin joinStyle: ShapePath.MiterJoin
capStyle: ShapePath.FlatCap capStyle: ShapePath.FlatCap
PathPolyline { PathSvg {
path: Geom.chamfer(root.polygon, root.chop, root.chopCorners) path: root.outline
} }
} }
@@ -161,8 +169,8 @@ Item {
joinStyle: ShapePath.MiterJoin joinStyle: ShapePath.MiterJoin
capStyle: ShapePath.FlatCap capStyle: ShapePath.FlatCap
PathPolyline { PathSvg {
path: Geom.chamfer(root.polygon, root.chop, root.chopCorners) path: root.outline
} }
} }
} }
+2 -2
View File
@@ -2,8 +2,7 @@ import QtQuick
import "root:/config" import "root:/config"
import "root:/components" import "root:/components"
// Hairline slider with a square handle, the NieR settings-menu slider. Drag or // Hairline slider with a round handle. Drag or click anywhere on the rail.
// click anywhere on the rail.
Item { Item {
id: root id: root
@@ -68,6 +67,7 @@ Item {
Skew { Skew {
id: handle id: handle
width: height width: height
radius: height / 2
height: root.activeFocus || hover.hovered || drag.active ? 12 : 10 height: root.activeFocus || hover.hovered || drag.active ? 12 : 10
color: root.enabled ? Theme.text : Theme.treatmentDisabledText color: root.enabled ? Theme.text : Theme.treatmentDisabledText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
+12 -4
View File
@@ -3,7 +3,8 @@ import QtQuick.Shapes
import "root:/config" import "root:/config"
import "root:/components" import "root:/components"
// A leaning block — the small-scale counterpart to P5Panel. // A small block — the small-scale counterpart to P5Panel. Upright and rounded
// under the YoRHa theme; it still leans if given a lean.
// //
// Every tick, rule, meter segment and slider rail in this shell leans at // Every tick, rule, meter segment and slider rail in this shell leans at
// Theme.skew. Shearing a plain Rectangle with a Matrix4x4 does that, but Qt // Theme.skew. Shearing a plain Rectangle with a Matrix4x4 does that, but Qt
@@ -31,6 +32,11 @@ Shape {
// rail slab. // rail slab.
property bool vertical: false property bool vertical: false
// Corner radius for an upright piece, clamped to a pill. Small by default,
// so a meter segment or a hover wash reads soft rather than as a block;
// a hairline stays a hairline.
property real radius: Theme.radiusS
// NOTE ON SIZING. The lean shifts one edge by `lean * height` (or by // NOTE ON SIZING. The lean shifts one edge by `lean * height` (or by
// `lean * width` when vertical), and the piece that actually gets painted is // `lean * width` when vertical), and the piece that actually gets painted is
// what is left: a Skew declared 1px wide and 20px tall does not draw a 1px // what is left: a Skew declared 1px wide and 20px tall does not draw a 1px
@@ -55,13 +61,15 @@ Shape {
joinStyle: ShapePath.MiterJoin joinStyle: ShapePath.MiterJoin
capStyle: ShapePath.FlatCap capStyle: ShapePath.FlatCap
PathPolyline { PathSvg {
path: { path: {
const w = root.width, h = root.height, l = root.lean; const w = root.width, h = root.height, l = root.lean;
if (l === 0)
return Geom.roundedRect(w, h, root.radius);
if (root.vertical) if (root.vertical)
return Geom.closed(root.leanLeft ? Geom.parallelogramVL(w, h, l) return Geom.svg(root.leanLeft ? Geom.parallelogramVL(w, h, l)
: Geom.parallelogramV(w, h, l)); : Geom.parallelogramV(w, h, l));
return Geom.closed(root.leanLeft ? Geom.parallelogramL(w, h, l) return Geom.svg(root.leanLeft ? Geom.parallelogramL(w, h, l)
: Geom.parallelogram(w, h, l)); : Geom.parallelogram(w, h, l));
} }
} }
+17 -5
View File
@@ -190,9 +190,8 @@ Singleton {
readonly property int frame: 8 readonly property int frame: 8
readonly property int frameRadius: 18 readonly property int frameRadius: 18
// Nothing leans and nothing is chopped. NieR panels are plain rectangles; // Nothing leans and nothing is chopped. Both knobs are kept so the shape
// their character comes from hairlines, square markers and corner ticks. // code still has one place to ask.
// Both knobs are kept so the shape code still has one place to ask.
readonly property real skew: 0 readonly property real skew: 0
readonly property int cut: 0 readonly property int cut: 0
readonly property int stroke: 1 readonly property int stroke: 1
@@ -202,8 +201,21 @@ Singleton {
readonly property real dropOffset: 0 readonly property real dropOffset: 0
readonly property real keylineWidth: 0 readonly property real keylineWidth: 0
// The square bullet that marks the current row, and the length of the // Corners. Everything is soft — the porthole is the shape the whole
// corner ticks that frame a panel. // desktop is built around, and every smaller surface is a gentler echo of
// it rather than a hard block.
//
// radiusS pills and small chips: buttons, tags, meter tracks
// radiusM rows and panels inside a surface
// radiusL anything that sits a padS inside the frame's own curve:
// frameRadius - padS, so its corner is concentric with the
// corner it sits in and the gap between them stays even
readonly property int radiusS: 6
readonly property int radiusM: 10
readonly property int radiusL: frameRadius - padS
// The dot that marks the current row, and the length of the corner
// ticks a panel can still ask for.
readonly property int marker: 6 readonly property int marker: 6
readonly property int tick: 8 readonly property int tick: 8
+9 -4
View File
@@ -1,6 +1,7 @@
import QtQuick import QtQuick
import QtQuick.Shapes import QtQuick.Shapes
import Quickshell import Quickshell
import Quickshell.Widgets
import Quickshell.Io import Quickshell.Io
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Hyprland import Quickshell.Hyprland
@@ -241,7 +242,7 @@ PanelWindow {
Rule { height: root.columnHeight } Rule { height: root.columnHeight }
// The month, Monday first. Today is the NieR cursor: an inverted square. // The month, Monday first. Today is the NieR cursor: an inverted disc.
Item { Item {
id: month id: month
implicitWidth: 238 implicitWidth: 238
@@ -311,7 +312,8 @@ PanelWindow {
Rectangle { Rectangle {
anchors.centerIn: parent anchors.centerIn: parent
width: 22 width: 22
height: 20 height: 22
radius: 11
visible: parent.modelData.today visible: parent.modelData.today
color: Theme.treatmentSelectedFill color: Theme.treatmentSelectedFill
} }
@@ -418,10 +420,11 @@ PanelWindow {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
visible: Media.hasPlayer visible: Media.hasPlayer
Rectangle { ClippingRectangle {
id: artFrame id: artFrame
width: 96 width: 96
height: 96 height: 96
radius: Theme.radiusL
color: Theme.surface color: Theme.surface
border.width: 1 border.width: 1
border.color: Theme.overlay border.color: Theme.overlay
@@ -506,6 +509,7 @@ PanelWindow {
x: parent.width * Media.progress - width / 2 x: parent.width * Media.progress - width / 2
width: Theme.marker width: Theme.marker
height: Theme.marker height: Theme.marker
radius: width / 2
color: Theme.accent color: Theme.accent
} }
} }
@@ -596,7 +600,7 @@ PanelWindow {
} }
} }
// A square hairline button. Hover lifts it; nothing here inverts, since // A round hairline button. Hover lifts it; nothing here inverts, since
// inversion is reserved for the keyboard's cursor. // inversion is reserved for the keyboard's cursor.
component Transport: Item { component Transport: Item {
id: tb id: tb
@@ -609,6 +613,7 @@ PanelWindow {
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
radius: width / 2
color: tbHover.hovered ? Theme.treatmentHoverFill : "transparent" color: tbHover.hovered ? Theme.treatmentHoverFill : "transparent"
border.width: 1 border.width: 1
border.color: tbHover.hovered ? Theme.treatmentHoverMark : Theme.overlay border.color: tbHover.hovered ? Theme.treatmentHoverMark : Theme.overlay
+1
View File
@@ -28,6 +28,7 @@ Item {
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: -Theme.padS anchors.leftMargin: -Theme.padS
anchors.rightMargin: -Theme.padS anchors.rightMargin: -Theme.padS
radius: Theme.radiusM
color: root.selected ? Theme.treatmentSelectedFill : "transparent" color: root.selected ? Theme.treatmentSelectedFill : "transparent"
opacity: root.selected ? 1 : 0 opacity: root.selected ? 1 : 0
+2 -1
View File
@@ -130,7 +130,8 @@ Popout {
Skew { Skew {
anchors.centerIn: parent anchors.centerIn: parent
width: 26 width: 26
height: 24 height: 26
radius: 13
visible: cell.modelData.today visible: cell.modelData.today
color: Theme.accent color: Theme.accent
} }
+6 -2
View File
@@ -1,6 +1,7 @@
import QtQuick import QtQuick
import QtQuick.Shapes import QtQuick.Shapes
import Quickshell import Quickshell
import Quickshell.Widgets
import "root:/config" import "root:/config"
import "root:/components" import "root:/components"
import "root:/services" import "root:/services"
@@ -32,14 +33,15 @@ Popout {
subtitle: Media.identity subtitle: Media.identity
} }
// Cover art in a hairline frame. // Cover art, rounded, in a hairline frame.
Rectangle { ClippingRectangle {
id: artFrame id: artFrame
anchors.top: header.bottom anchors.top: header.bottom
anchors.topMargin: Theme.padM anchors.topMargin: Theme.padM
anchors.left: parent.left anchors.left: parent.left
width: 108 width: 108
height: 108 height: 108
radius: Theme.radiusL
color: Theme.surface color: Theme.surface
border.width: 1 border.width: 1
border.color: Theme.overlay border.color: Theme.overlay
@@ -216,6 +218,7 @@ Popout {
Skew { Skew {
anchors.fill: parent anchors.fill: parent
radius: height / 2
color: !chip.enabled color: !chip.enabled
? Theme.treatmentDisabledFill ? Theme.treatmentDisabledFill
: (chip.activeFocus : (chip.activeFocus
@@ -287,6 +290,7 @@ Popout {
Skew { Skew {
anchors.fill: parent anchors.fill: parent
radius: width / 2
color: { color: {
if (!tb.enabled) return "transparent"; if (!tb.enabled) return "transparent";
if (tb.activeFocus) return Theme.treatmentFocusFill; if (tb.activeFocus) return Theme.treatmentFocusFill;
+2 -1
View File
@@ -3,7 +3,7 @@ import "root:/config"
import "root:/components" import "root:/components"
// Title bar for a popout, in the NieR header pattern: glyph, tracked heading, // Title bar for a popout, in the NieR header pattern: glyph, tracked heading,
// right-aligned status text, and a hairline rule that ends in a square. // right-aligned status text, and a hairline rule that ends in a dot.
Item { Item {
id: root id: root
@@ -62,6 +62,7 @@ Item {
anchors.right: parent.right anchors.right: parent.right
width: Theme.marker width: Theme.marker
height: Theme.marker height: Theme.marker
radius: width / 2
color: Theme.accent color: Theme.accent
} }
} }
+5 -3
View File
@@ -17,14 +17,16 @@ P5Panel {
// Zero, not a transparent fill: P5Panel re-applies this opacity to the // Zero, not a transparent fill: P5Panel re-applies this opacity to the
// fill colour, which turned "transparent" into 97% black. // fill colour, which turned "transparent" into 97% black.
fillOpacity: attached ? 0 : 0.97 fillOpacity: attached ? 0 : 0.97
// The frame is structure: a neutral hairline. The tone goes into the // The frame is structure: a neutral hairline. A free-standing panel takes
// corner ticks, which is where NieR puts the one bright mark on a panel. // the frame's own corner radius, so it reads as a piece of the hull set
// down somewhere else rather than as a hard card.
border: Theme.surfaceAlt border: Theme.surfaceAlt
borderWidth: attached ? 0 : Theme.stroke borderWidth: attached ? 0 : Theme.stroke
halftone: textured halftone: textured
halftoneColor: tone halftoneColor: tone
halftoneOpacity: 0.06 halftoneOpacity: 0.06
ticks: !attached ticks: false
radius: Theme.frameRadius
tickColor: tone tickColor: tone
padding: Theme.padM padding: Theme.padM
} }
+1
View File
@@ -106,6 +106,7 @@ Column {
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: Theme.padS anchors.leftMargin: Theme.padS
anchors.rightMargin: Theme.padS anchors.rightMargin: Theme.padS
radius: Theme.radiusM
color: row.hovered ? Theme.accent : "transparent" color: row.hovered ? Theme.accent : "transparent"
Behavior on color { Behavior on color {