Replaces the Persona x Mocha rice. NieR's menu language is kept whole — plain rectangles, hairline rules, letter-spaced upright capitals, a square marker on the current row, corner ticks round panels, and selection shown by inverting the row — but the colours come from ~/.wallpapers/space-1.jpg rather than from the game's beige: the grey-green void as the ground, the suit's sage grey as text, the planet's white as the one accent, and the hull's red indicator lights as the alarm. The Persona geometry is switched off rather than ripped out: Theme.skew and Theme.cut are 0, so P5Panel, Skew and Geom collapse to rectangles. Overshoot easing, italics and black weights are gone shell-wide. New, borrowed from caelestia's screen border: bar/Frame.qml draws the bar, the rail and a thin left/bottom band as one hull on the bottom layer, with the desktop as a rounded opening cut out of it. Frame also owns all four exclusive zones, so the bar and rail are placed from the screen edge. Hyprland moves to 1px borders lit from the planet white into grey, a soft wide shadow and gaps measured from the opening. Alacritty, fish and dunst get matching YoRHa palettes. Neovim, GTK/Qt and btop are still Mocha. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
179 lines
6.4 KiB
QML
179 lines
6.4 KiB
QML
import QtQuick
|
|
import "root:/config"
|
|
import "root:/components"
|
|
|
|
// One slot on the right-hand rail: a glyph with an optional micro readout
|
|
// stacked beneath it, and an accent bar that wipes down the leading edge on
|
|
// hover. The rail is only 48px wide, so the readout sits under the glyph
|
|
// rather than beside it, and anything longer than a percentage belongs in the
|
|
// popout instead.
|
|
Item {
|
|
id: root
|
|
|
|
property string icon: ""
|
|
property string readout: ""
|
|
property string label: ""
|
|
property string statusText: ""
|
|
property string hint: ""
|
|
property bool suppressTooltip: false
|
|
property color tint: Theme.primary
|
|
|
|
// Three states, three meanings. `live` wants attention but nothing is wrong
|
|
// (unread notifications, an open power menu): accent. `alarm` is wrong or
|
|
// critical (a dying battery): alarm. `dim` is off by choice (muted, radio
|
|
// off, do-not-disturb): grey. These used to be one `alert` flag painted in
|
|
// the accent, which made a muted speaker look like the most active thing
|
|
// on the rail.
|
|
property bool live: false
|
|
property bool alarm: false
|
|
property bool dim: false
|
|
property bool pulsing: false
|
|
|
|
// Hold the readout line open even while there is nothing to put in it.
|
|
// Volume drops its percentage when muted and the bell only carries a count
|
|
// when something is unread, so without this the slot collapsed from 42px to
|
|
// 32px and every icon below it jumped a third of its own height.
|
|
property bool reserveReadout: false
|
|
|
|
readonly property bool hasReadout: root.readout !== "" || root.reserveReadout
|
|
readonly property bool hovered: hover.hovered
|
|
|
|
// Clearance between the wipe and the rail's left edge, chosen so the bar
|
|
// stays inside the slab once `scale` has expanded the icon about its centre.
|
|
readonly property real wipeInset: 5
|
|
|
|
signal clicked()
|
|
signal scrolled(real delta)
|
|
|
|
implicitWidth: Theme.railWidth
|
|
implicitHeight: root.hasReadout ? root.glyphBand + readoutLine.height + 1 : root.glyphBand
|
|
|
|
// The glyph always sits centred in a fixed band at the top of the slot and
|
|
// the readout hangs below it, rather than the pair being centred together.
|
|
// Centring the pair meant the icon slid up and down by a few px whenever its
|
|
// number appeared or vanished — the icons stopped lining up with each other
|
|
// exactly when something was changing and you were looking at them.
|
|
readonly property real glyphBand: 32
|
|
|
|
// Nerd Font's status glyphs are not all centred inside their own advance —
|
|
// the muted-speaker and muted-mic marks carry their cross out to the right,
|
|
// so centring the advance box left them visibly off-axis next to the wifi
|
|
// and bluetooth glyphs. Centre the ink instead, capped so a glyph with an
|
|
// odd bounding box cannot slide far off the rail's axis.
|
|
readonly property real opticalShift: {
|
|
const ink = glyphMetrics.tightBoundingRect;
|
|
if (ink.width <= 0) return 0;
|
|
const off = glyphMetrics.advanceWidth / 2 - (ink.x + ink.width / 2);
|
|
return Math.max(-4, Math.min(4, off));
|
|
}
|
|
|
|
TextMetrics {
|
|
id: glyphMetrics
|
|
text: root.icon
|
|
font.family: Theme.fontIcon
|
|
font.pixelSize: Theme.fsRailIcon
|
|
}
|
|
|
|
Item {
|
|
id: layout
|
|
anchors.fill: parent
|
|
|
|
Icon {
|
|
id: glyph
|
|
x: (root.width - width) / 2 + root.opticalShift
|
|
y: (root.glyphBand - height) / 2
|
|
text: root.icon
|
|
pulsing: root.pulsing
|
|
font.pixelSize: Theme.fsRailIcon
|
|
color: {
|
|
if (root.alarm) return Theme.alarm;
|
|
if (root.live) return Theme.accent;
|
|
if (root.dim) return Theme.muted;
|
|
if (root.hovered) return Theme.glow;
|
|
return root.tint;
|
|
}
|
|
|
|
Behavior on color {
|
|
ColorAnimation { duration: Theme.durBase }
|
|
}
|
|
}
|
|
|
|
Text {
|
|
id: readoutLine
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.top: parent.top
|
|
anchors.topMargin: root.glyphBand + 1
|
|
visible: root.readout !== ""
|
|
text: root.readout
|
|
color: {
|
|
if (root.alarm) return Theme.alarm;
|
|
if (root.dim) return Theme.muted;
|
|
return Theme.subtext;
|
|
}
|
|
font.family: Theme.fontMono
|
|
font.pixelSize: Theme.fsMicro
|
|
font.weight: Font.DemiBold
|
|
renderType: Text.NativeRendering
|
|
|
|
Behavior on color {
|
|
ColorAnimation { duration: Theme.durBase }
|
|
}
|
|
}
|
|
}
|
|
|
|
// Leading-edge wipe. On the rail this runs vertically down the left side,
|
|
// mirroring the slash P5Panel puts on horizontal modules.
|
|
//
|
|
// Held off the edge by `wipeInset`: the icon spans the rail's full width, so
|
|
// at x = 0 this landed exactly on the slab's own border, and the 1.08 hover
|
|
// scale then pushed it out past the silhouette onto the desktop. The inset
|
|
// has to survive that scale, which is why it is not simply 1 or 2.
|
|
//
|
|
// It is the accent rather than `tint` because it only ever appears on
|
|
// hover, and hover is the accent everywhere else in the shell (Theme.glow).
|
|
// Painting it white put a white bar on the rail's edge for no stated reason.
|
|
Rectangle {
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: root.wipeInset
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
width: 2
|
|
height: root.hovered ? root.height - 4 : 0
|
|
color: Theme.glow
|
|
|
|
Behavior on height {
|
|
NumberAnimation { duration: Theme.durBase; easing.type: Easing.OutExpo }
|
|
}
|
|
}
|
|
|
|
scale: root.hovered ? 1.08 : 1.0
|
|
Behavior on scale {
|
|
NumberAnimation { duration: Theme.durBase; easing.type: Easing.OutCubic }
|
|
}
|
|
|
|
onAlarmChanged: if (alarm) glyph.bump()
|
|
onLiveChanged: if (live) glyph.bump()
|
|
|
|
HoverHandler {
|
|
id: hover
|
|
cursorShape: Qt.PointingHandCursor
|
|
}
|
|
|
|
TapHandler {
|
|
acceptedButtons: Qt.LeftButton
|
|
onTapped: root.clicked()
|
|
}
|
|
WheelHandler {
|
|
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
|
onWheel: event => root.scrolled(event.angleDelta.y)
|
|
}
|
|
|
|
P5Tooltip {
|
|
anchorItem: root
|
|
triggerHovered: root.hovered
|
|
title: root.label
|
|
status: root.statusText
|
|
hint: root.hint
|
|
suppressed: root.suppressTooltip
|
|
}
|
|
}
|