diff --git a/hypr/lua/binds/shell.lua b/hypr/lua/binds/shell.lua index 717aff2..899e5bb 100644 --- a/hypr/lua/binds/shell.lua +++ b/hypr/lua/binds/shell.lua @@ -9,7 +9,7 @@ end shell("lock", { key = mod .. " + CTRL + L", description = "lock screen" }) shell("power", { key = mod .. " + P", description = "power menu" }) 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("clearNotifications", { key = mod .. " + CTRL + SHIFT + N", description = "clear notifications" }) shell("reloadConfig", { key = mod .. " + SHIFT + R", description = "reload shell config" }) diff --git a/hypr/lua/binds/workspaces.lua b/hypr/lua/binds/workspaces.lua index aa15e3e..ca513ab 100644 --- a/hypr/lua/binds/workspaces.lua +++ b/hypr/lua/binds/workspaces.lua @@ -1,4 +1,4 @@ --- Workspaces, including the two scratchpads. +-- Workspaces, including the scratchpad. local mod = "SUPER" @@ -13,10 +13,6 @@ end hl.bind(mod .. " + mouse_down", 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. -local special = { S = "virtual", D = "discord" } - -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 +-- Scratchpad: S holds anything virtual. Chat opens wherever it is launched. +hl.bind(mod .. " + S", hl.dsp.workspace.toggle_special("virtual")) +hl.bind(mod .. " + SHIFT + S", hl.dsp.window.move({ workspace = "special:virtual" })) diff --git a/hypr/lua/rules/windows.lua b/hypr/lua/rules/windows.lua index 5064ff6..2671ab6 100644 --- a/hypr/lua/rules/windows.lua +++ b/hypr/lua/rules/windows.lua @@ -54,7 +54,6 @@ hl.window_rule({ match = { class = GAMESCOPE }, workspace = "1" }) -- Special workspaces hl.window_rule({ match = { class = "^(Spotify|spotify)$" }, workspace = "special:virtual" }) -hl.window_rule({ match = { class = "^(discord|vesktop)$" }, workspace = "special:discord" }) -- ========================================== -- STEAM & GAMING diff --git a/quickshell/README.md b/quickshell/README.md index 8e388ce..10a6b38 100644 --- a/quickshell/README.md +++ b/quickshell/README.md @@ -3,7 +3,7 @@ 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 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 rail and a thin border are one hull around the desktop, and the desktop is the porthole cut out of it. @@ -91,7 +91,7 @@ left a thin strip of opening between itself and the rail. ## Dashboard `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 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 @@ -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 this; use them rather than picking colours per widget. -**Markers, not slashes.** The current row, a button under the pointer and the -end of a header rule carry a small square (`Theme.marker`). Popouts are framed -by corner ticks (`Theme.tick`) in their tone colour; the border itself is -structure and stays neutral. +**Dots, not slashes.** The current row, a button under the pointer, the end of +a header rule and a notification's urgency carry a small dot (`Theme.marker`). +Borders are structure and stay neutral. **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 @@ -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 zones itself, so every piece is placed from the screen edge. -**Rectangles, everywhere else.** NieR panels are plain boxes. `Theme.skew` and -`Theme.cut` are both `0`, so `P5Panel`, `Skew` and `Geom` all collapse to -rectangles; the knobs are kept so a shape can be brought back on purpose. The -porthole's curve is the only one on screen, which is what lets it read. +**Soft, everywhere else.** The porthole is the shape the desktop is built +around, and every smaller surface is a gentler echo of it. `Theme.skew` and +`Theme.cut` are `0`; `P5Panel` and `Skew` draw `Geom.roundedRect` with +`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 `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 -rows, album art sits square in a hairline frame, and the bar's player shows a -five-hairline signal meter. +hover brightens and marks, the way NieR's menus do. Meters are one continuous +rounded line that glides to its level, and the bar's player shows a +five-line signal meter. 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 diff --git a/quickshell/bar/MediaPill.qml b/quickshell/bar/MediaPill.qml index bf9e627..1e76e33 100644 --- a/quickshell/bar/MediaPill.qml +++ b/quickshell/bar/MediaPill.qml @@ -2,6 +2,7 @@ import QtQuick import QtQuick.Shapes import QtQuick.Window import Quickshell +import Quickshell.Widgets import "root:/config" import "root:/components" import "root:/services" @@ -41,12 +42,13 @@ BarPill { anchors.left: parent.left spacing: Theme.padS - // Album art in a hairline frame. - Rectangle { + // Album art, rounded, in a hairline frame. + ClippingRectangle { id: artFrame anchors.verticalCenter: parent.verticalCenter width: 24 height: 24 + radius: Theme.radiusS color: Theme.surface border.width: 1 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 - // 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. // // Sampled from a timer rather than tweened by an infinite @@ -181,7 +183,8 @@ BarPill { Rectangle { required property int index - width: 1 + width: 2 + radius: 1 height: Media.playing ? eq.level(index) : 2 anchors.verticalCenter: parent.verticalCenter color: Media.playing ? Theme.primary : Theme.muted diff --git a/quickshell/bar/RailIcon.qml b/quickshell/bar/RailIcon.qml index 0244d51..0dc90da 100644 --- a/quickshell/bar/RailIcon.qml +++ b/quickshell/bar/RailIcon.qml @@ -135,6 +135,7 @@ Item { anchors.leftMargin: root.wipeInset anchors.verticalCenter: parent.verticalCenter width: 2 + radius: 1 height: root.hovered ? Theme.marker * 2 : 0 color: Theme.glow diff --git a/quickshell/bar/Workspaces.qml b/quickshell/bar/Workspaces.qml index 638dceb..15dbe13 100644 --- a/quickshell/bar/Workspaces.qml +++ b/quickshell/bar/Workspaces.qml @@ -4,7 +4,7 @@ import Quickshell.Hyprland import "root:/config" 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. Item { id: root @@ -82,6 +82,7 @@ Item { Skew { id: tick anchors.fill: parent + radius: height / 2 // Focused is a solid accent block. Urgent is a solid alarm // block — it used to be the accent too, which made an @@ -132,6 +133,7 @@ Item { Rectangle { visible: pip.info !== undefined && pip.info.fullscreen && !pip.focused width: 4; height: 4 + radius: 2 color: Theme.accent anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.bottom diff --git a/quickshell/components/ChromeSurface.qml b/quickshell/components/ChromeSurface.qml index 788ad83..c2f535e 100644 --- a/quickshell/components/ChromeSurface.qml +++ b/quickshell/components/ChromeSurface.qml @@ -18,4 +18,5 @@ P5Panel { slashColor: tone slashWidth: 2 padding: Theme.padS + radius: Theme.radiusM } diff --git a/quickshell/components/Geom.qml b/quickshell/components/Geom.qml index 266f309..f46a7b1 100644 --- a/quickshell/components/Geom.qml +++ b/quickshell/components/Geom.qml @@ -3,9 +3,10 @@ pragma Singleton import QtQuick import Quickshell -// Polygon maths for panel shapes. Under the YoRHa theme nothing leans and -// nothing is chopped (Theme.skew and Theme.cut are 0), so these collapse to -// plain rectangles; they are kept so a shape can be brought back on purpose. +// Shape maths for panels. Under the YoRHa theme nothing leans and nothing is +// chopped (Theme.skew and Theme.cut are 0); surfaces are rounded rectangles +// (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 // get chopped rather than rounded. Singleton { @@ -89,6 +90,25 @@ Singleton { 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 { if (pts.length === 0) return pts; const out = pts.slice(); diff --git a/quickshell/components/ListRow.qml b/quickshell/components/ListRow.qml index dd02216..ec9759b 100644 --- a/quickshell/components/ListRow.qml +++ b/quickshell/components/ListRow.qml @@ -7,7 +7,7 @@ import "root:/config" // 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 — -// 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. Item { id: root @@ -30,6 +30,7 @@ Item { Rectangle { anchors.fill: parent + radius: Theme.radiusM color: root.hovered ? Theme.treatmentHoverFill : "transparent" Behavior on color { @@ -43,6 +44,7 @@ Item { anchors.leftMargin: 2 width: root.current ? Theme.marker : 0 height: width + radius: width / 2 color: Theme.accent Behavior on width { diff --git a/quickshell/components/MeterBar.qml b/quickshell/components/MeterBar.qml index fb979aa..a512b6d 100644 --- a/quickshell/components/MeterBar.qml +++ b/quickshell/components/MeterBar.qml @@ -2,8 +2,12 @@ import QtQuick import "root:/config" import "root:/components" -// A segmented load meter — upright ticks that light up left to right. Reads as -// a gauge at a glance and keeps the panel language consistent. +// A load meter: a soft rounded track with the level filling it from the left. +// 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 { id: root @@ -12,42 +16,33 @@ Item { property real segmentWidth: 4 property real spacing: 3 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 - readonly property int litCount: Math.round(Math.max(0, Math.min(100, value)) / 100 * segments) - 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 - spacing: root.spacing + radius: height / 2 + color: root.idleColor + } - Repeater { - model: root.segments + Rectangle { + height: parent.height + // Never narrower than its own height, so a near-zero level is a dot + // rather than a sliver. + width: root.fraction > 0 ? Math.max(height, parent.width * root.fraction) : 0 + radius: height / 2 + color: root.activeColor - Item { - required property int index - width: root.segmentWidth - height: root.height - - 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 color { - ColorAnimation { duration: Theme.durBase } - } - } - } + Behavior on width { + NumberAnimation { duration: Theme.durSlow; easing.type: Easing.OutCubic } + } + Behavior on color { + ColorAnimation { duration: Theme.durBase } } } } diff --git a/quickshell/components/NotifCard.qml b/quickshell/components/NotifCard.qml index e45b410..4bf06a7 100644 --- a/quickshell/components/NotifCard.qml +++ b/quickshell/components/NotifCard.qml @@ -1,5 +1,6 @@ import QtQuick import Quickshell +import Quickshell.Widgets import Quickshell.Services.Notifications import "root:/config" import "root:/components" @@ -7,14 +8,14 @@ import "root:/services" // 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 -// marker and the sender's name running into a hairline, the summary, the body, -// and hairline buttons for any actions. There is no panel of its own — both -// places it appears are already hull surfaces — so rest is bare, hover lifts -// the row onto the surface tone, and entries are divided by the hairline in -// their own header. +// Laid out like an entry in NieR's message log rather than as a card: a dot +// and the sender's name running into a hairline, the summary, the body, and +// pill buttons for any actions. There is no panel of its own — both places it +// appears are already hull surfaces — so rest is bare, hover lifts the entry +// onto a soft rounded wash, and entries are divided by the hairline in their +// 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. Item { id: root @@ -38,8 +39,14 @@ Item { 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 { anchors.fill: parent + anchors.margins: Theme.padS / 2 + radius: Theme.radiusL color: root.hovered ? Theme.treatmentHoverFill : "transparent" Behavior on color { @@ -66,6 +73,7 @@ Item { anchors.verticalCenter: parent.verticalCenter width: Theme.marker height: Theme.marker + radius: width / 2 color: root.tone } @@ -162,12 +170,13 @@ Item { renderType: Text.NativeRendering } - // Inline image, when the sender supplied one, in a hairline frame. - Rectangle { + // Inline image, when the sender supplied one, rounded in a hairline frame. + ClippingRectangle { x: Theme.marker + Theme.padS visible: inline.source != "" && inline.status === Image.Ready width: inline.width + 2 height: inline.height + 2 + radius: Theme.radiusM color: "transparent" border.width: 1 border.color: Theme.overlay @@ -192,8 +201,7 @@ Item { Repeater { model: root.notif?.actions ?? [] - // A hairline button with a marker, the P5Button pattern at a - // smaller size. Hover lifts and lights it; it never inverts. + // A hairline pill. Hover lifts and lights it; it never inverts. Item { id: actionChip required property var modelData @@ -203,6 +211,7 @@ Item { Rectangle { anchors.fill: parent + radius: height / 2 color: actionHover.hovered ? Theme.treatmentHoverFill : "transparent" border.width: 1 border.color: actionHover.hovered ? Theme.treatmentHoverMark : Theme.overlay @@ -221,6 +230,7 @@ Item { anchors.verticalCenter: parent.verticalCenter width: 4 height: 4 + radius: 2 color: actionLabel.color opacity: actionHover.hovered ? 1 : 0.4 } diff --git a/quickshell/components/P5Button.qml b/quickshell/components/P5Button.qml index 9eef41c..fd3a071 100644 --- a/quickshell/components/P5Button.qml +++ b/quickshell/components/P5Button.qml @@ -90,6 +90,7 @@ FocusScope { anchors.verticalCenter: parent.verticalCenter width: Theme.marker height: Theme.marker + radius: width / 2 color: label.color opacity: root.highlighted ? 1.0 : 0.35 diff --git a/quickshell/components/P5Panel.qml b/quickshell/components/P5Panel.qml index 6d355a8..728c844 100644 --- a/quickshell/components/P5Panel.qml +++ b/quickshell/components/P5Panel.qml @@ -5,9 +5,8 @@ import "root:/config" import "root:/components" // 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 -// accent bar down its leading edge, and optional corner ticks — the brackets -// NieR frames its panels with. +// rounded rectangle with a hairline border, an optional dot wash, an optional +// accent bar down its leading edge, and optional corner ticks. // // 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. @@ -34,6 +33,10 @@ Item { property real chop: Theme.cut 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. property bool slash: false property color slashColor: Theme.accent @@ -67,6 +70,11 @@ Item { readonly property real leanInset: (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 ? (leanLeft ? Geom.parallelogramVL(width, height, lean) : Geom.parallelogramV(width, height, lean)) @@ -99,8 +107,8 @@ Item { strokeColor: "transparent" joinStyle: ShapePath.MiterJoin - PathPolyline { - path: Geom.chamfer(root.polygon, root.chop, root.chopCorners) + PathSvg { + path: root.outline } } } @@ -123,8 +131,8 @@ Item { strokeColor: "transparent" joinStyle: ShapePath.MiterJoin - PathPolyline { - path: Geom.chamfer(root.polygon, root.chop, root.chopCorners) + PathSvg { + path: root.outline } } } @@ -148,8 +156,8 @@ Item { joinStyle: ShapePath.MiterJoin capStyle: ShapePath.FlatCap - PathPolyline { - path: Geom.chamfer(root.polygon, root.chop, root.chopCorners) + PathSvg { + path: root.outline } } @@ -161,8 +169,8 @@ Item { joinStyle: ShapePath.MiterJoin capStyle: ShapePath.FlatCap - PathPolyline { - path: Geom.chamfer(root.polygon, root.chop, root.chopCorners) + PathSvg { + path: root.outline } } } diff --git a/quickshell/components/P5Slider.qml b/quickshell/components/P5Slider.qml index 8aa69a2..c0a0ac6 100644 --- a/quickshell/components/P5Slider.qml +++ b/quickshell/components/P5Slider.qml @@ -2,8 +2,7 @@ import QtQuick import "root:/config" import "root:/components" -// Hairline slider with a square handle, the NieR settings-menu slider. Drag or -// click anywhere on the rail. +// Hairline slider with a round handle. Drag or click anywhere on the rail. Item { id: root @@ -68,6 +67,7 @@ Item { Skew { id: handle width: height + radius: height / 2 height: root.activeFocus || hover.hovered || drag.active ? 12 : 10 color: root.enabled ? Theme.text : Theme.treatmentDisabledText anchors.verticalCenter: parent.verticalCenter diff --git a/quickshell/components/Skew.qml b/quickshell/components/Skew.qml index 7e61547..e691925 100644 --- a/quickshell/components/Skew.qml +++ b/quickshell/components/Skew.qml @@ -3,7 +3,8 @@ import QtQuick.Shapes import "root:/config" 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 // Theme.skew. Shearing a plain Rectangle with a Matrix4x4 does that, but Qt @@ -31,6 +32,11 @@ Shape { // rail slab. 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 // `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 @@ -55,14 +61,16 @@ Shape { joinStyle: ShapePath.MiterJoin capStyle: ShapePath.FlatCap - PathPolyline { + PathSvg { path: { const w = root.width, h = root.height, l = root.lean; + if (l === 0) + return Geom.roundedRect(w, h, root.radius); if (root.vertical) - return Geom.closed(root.leanLeft ? Geom.parallelogramVL(w, h, l) - : Geom.parallelogramV(w, h, l)); - return Geom.closed(root.leanLeft ? Geom.parallelogramL(w, h, l) - : Geom.parallelogram(w, h, l)); + return Geom.svg(root.leanLeft ? Geom.parallelogramVL(w, h, l) + : Geom.parallelogramV(w, h, l)); + return Geom.svg(root.leanLeft ? Geom.parallelogramL(w, h, l) + : Geom.parallelogram(w, h, l)); } } } diff --git a/quickshell/config/Theme.qml b/quickshell/config/Theme.qml index de92057..4127685 100644 --- a/quickshell/config/Theme.qml +++ b/quickshell/config/Theme.qml @@ -190,9 +190,8 @@ Singleton { readonly property int frame: 8 readonly property int frameRadius: 18 - // Nothing leans and nothing is chopped. NieR panels are plain rectangles; - // their character comes from hairlines, square markers and corner ticks. - // Both knobs are kept so the shape code still has one place to ask. + // Nothing leans and nothing is chopped. Both knobs are kept so the shape + // code still has one place to ask. readonly property real skew: 0 readonly property int cut: 0 readonly property int stroke: 1 @@ -202,8 +201,21 @@ Singleton { readonly property real dropOffset: 0 readonly property real keylineWidth: 0 - // The square bullet that marks the current row, and the length of the - // corner ticks that frame a panel. + // Corners. Everything is soft — the porthole is the shape the whole + // 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 tick: 8 diff --git a/quickshell/dashboard/Dashboard.qml b/quickshell/dashboard/Dashboard.qml index 5006bdf..6a7ef1e 100644 --- a/quickshell/dashboard/Dashboard.qml +++ b/quickshell/dashboard/Dashboard.qml @@ -1,6 +1,7 @@ import QtQuick import QtQuick.Shapes import Quickshell +import Quickshell.Widgets import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland @@ -241,7 +242,7 @@ PanelWindow { 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 { id: month implicitWidth: 238 @@ -311,7 +312,8 @@ PanelWindow { Rectangle { anchors.centerIn: parent width: 22 - height: 20 + height: 22 + radius: 11 visible: parent.modelData.today color: Theme.treatmentSelectedFill } @@ -418,10 +420,11 @@ PanelWindow { anchors.bottom: parent.bottom visible: Media.hasPlayer - Rectangle { + ClippingRectangle { id: artFrame width: 96 height: 96 + radius: Theme.radiusL color: Theme.surface border.width: 1 border.color: Theme.overlay @@ -506,6 +509,7 @@ PanelWindow { x: parent.width * Media.progress - width / 2 width: Theme.marker height: Theme.marker + radius: width / 2 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. component Transport: Item { id: tb @@ -609,6 +613,7 @@ PanelWindow { Rectangle { anchors.fill: parent + radius: width / 2 color: tbHover.hovered ? Theme.treatmentHoverFill : "transparent" border.width: 1 border.color: tbHover.hovered ? Theme.treatmentHoverMark : Theme.overlay diff --git a/quickshell/launcher/ResultRow.qml b/quickshell/launcher/ResultRow.qml index 0ad94c1..ddfd672 100644 --- a/quickshell/launcher/ResultRow.qml +++ b/quickshell/launcher/ResultRow.qml @@ -28,6 +28,7 @@ Item { anchors.fill: parent anchors.leftMargin: -Theme.padS anchors.rightMargin: -Theme.padS + radius: Theme.radiusM color: root.selected ? Theme.treatmentSelectedFill : "transparent" opacity: root.selected ? 1 : 0 diff --git a/quickshell/popouts/CalendarPopout.qml b/quickshell/popouts/CalendarPopout.qml index 441aa1a..94f8203 100644 --- a/quickshell/popouts/CalendarPopout.qml +++ b/quickshell/popouts/CalendarPopout.qml @@ -130,7 +130,8 @@ Popout { Skew { anchors.centerIn: parent width: 26 - height: 24 + height: 26 + radius: 13 visible: cell.modelData.today color: Theme.accent } diff --git a/quickshell/popouts/MediaPopout.qml b/quickshell/popouts/MediaPopout.qml index 39bd8fc..64f2cbb 100644 --- a/quickshell/popouts/MediaPopout.qml +++ b/quickshell/popouts/MediaPopout.qml @@ -1,6 +1,7 @@ import QtQuick import QtQuick.Shapes import Quickshell +import Quickshell.Widgets import "root:/config" import "root:/components" import "root:/services" @@ -32,14 +33,15 @@ Popout { subtitle: Media.identity } - // Cover art in a hairline frame. - Rectangle { + // Cover art, rounded, in a hairline frame. + ClippingRectangle { id: artFrame anchors.top: header.bottom anchors.topMargin: Theme.padM anchors.left: parent.left width: 108 height: 108 + radius: Theme.radiusL color: Theme.surface border.width: 1 border.color: Theme.overlay @@ -216,6 +218,7 @@ Popout { Skew { anchors.fill: parent + radius: height / 2 color: !chip.enabled ? Theme.treatmentDisabledFill : (chip.activeFocus @@ -287,6 +290,7 @@ Popout { Skew { anchors.fill: parent + radius: width / 2 color: { if (!tb.enabled) return "transparent"; if (tb.activeFocus) return Theme.treatmentFocusFill; diff --git a/quickshell/popouts/PopoutHeader.qml b/quickshell/popouts/PopoutHeader.qml index ee4b516..8ba2786 100644 --- a/quickshell/popouts/PopoutHeader.qml +++ b/quickshell/popouts/PopoutHeader.qml @@ -3,7 +3,7 @@ import "root:/config" import "root:/components" // 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 { id: root @@ -62,6 +62,7 @@ Item { anchors.right: parent.right width: Theme.marker height: Theme.marker + radius: width / 2 color: Theme.accent } } diff --git a/quickshell/popouts/PopoutSurface.qml b/quickshell/popouts/PopoutSurface.qml index 26734f2..0821894 100644 --- a/quickshell/popouts/PopoutSurface.qml +++ b/quickshell/popouts/PopoutSurface.qml @@ -17,14 +17,16 @@ P5Panel { // Zero, not a transparent fill: P5Panel re-applies this opacity to the // fill colour, which turned "transparent" into 97% black. fillOpacity: attached ? 0 : 0.97 - // The frame is structure: a neutral hairline. The tone goes into the - // corner ticks, which is where NieR puts the one bright mark on a panel. + // The frame is structure: a neutral hairline. A free-standing panel takes + // 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 borderWidth: attached ? 0 : Theme.stroke halftone: textured halftoneColor: tone halftoneOpacity: 0.06 - ticks: !attached + ticks: false + radius: Theme.frameRadius tickColor: tone padding: Theme.padM } diff --git a/quickshell/popouts/TrayMenuList.qml b/quickshell/popouts/TrayMenuList.qml index 2f3cbc7..307d252 100644 --- a/quickshell/popouts/TrayMenuList.qml +++ b/quickshell/popouts/TrayMenuList.qml @@ -106,6 +106,7 @@ Column { anchors.fill: parent anchors.leftMargin: Theme.padS anchors.rightMargin: Theme.padS + radius: Theme.radiusM color: row.hovered ? Theme.accent : "transparent" Behavior on color {