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:
1 parent
d6de04eaaf
commit
a8c19961e2
24 files changed
+188
-110
No files matched your search
@@ -18,4 +18,5 @@ P5Panel {
|
||||
slashColor: tone
|
||||
slashWidth: 2
|
||||
padding: Theme.padS
|
||||
radius: Theme.radiusM
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user