improvements
This commit is contained in:
@@ -4,8 +4,8 @@ import Quickshell.Hyprland
|
||||
import "root:/config"
|
||||
import "root:/components"
|
||||
|
||||
// Focused window title, glitching on every change the way a Persona 5 caption
|
||||
// snaps into place.
|
||||
// Focused window title. Rapid application title churn is settled before it is
|
||||
// painted, then the stable caption lands like a misregistered print layer.
|
||||
Item {
|
||||
id: root
|
||||
|
||||
@@ -19,7 +19,8 @@ Item {
|
||||
&& (root.monitor === null || toplevel.monitor === null
|
||||
|| toplevel.monitor.id === root.monitor.id)
|
||||
|
||||
readonly property string title: mine && toplevel.title ? toplevel.title : "—"
|
||||
readonly property string incomingTitle: mine && toplevel.title ? toplevel.title : "—"
|
||||
property string displayedTitle: "—"
|
||||
|
||||
// The bar shrinks this when the three clusters would otherwise collide.
|
||||
property real maxWidth: 240
|
||||
@@ -31,18 +32,60 @@ Item {
|
||||
NumberAnimation { duration: Theme.durSlow; easing.type: Easing.OutExpo }
|
||||
}
|
||||
|
||||
Marquee {
|
||||
id: label
|
||||
Item {
|
||||
id: titleStage
|
||||
anchors.fill: parent
|
||||
text: root.title
|
||||
color: root.mine ? Theme.subtext : Theme.muted
|
||||
pixelSize: Theme.fsSmall
|
||||
family: Theme.fontDisplay
|
||||
italic: true
|
||||
weight: Font.DemiBold
|
||||
clip: true
|
||||
|
||||
Text {
|
||||
id: label
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width
|
||||
text: root.displayedTitle
|
||||
color: root.mine ? Theme.subtext : Theme.muted
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: Theme.fsSmall
|
||||
font.family: Theme.fontDisplay
|
||||
font.italic: true
|
||||
font.weight: Font.DemiBold
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
// Crimson duplicate: visible only during the landing transition. The
|
||||
// small opposing offset gives the title Persona's rough print snap
|
||||
// without leaving permanent motion in the bar.
|
||||
Text {
|
||||
id: ghost
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width
|
||||
text: root.displayedTitle
|
||||
color: Theme.accent
|
||||
opacity: 0
|
||||
elide: Text.ElideRight
|
||||
font: label.font
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
|
||||
onTitleChanged: flash.restart()
|
||||
function commitTitle(): void {
|
||||
if (root.displayedTitle === root.incomingTitle)
|
||||
return;
|
||||
root.displayedTitle = root.incomingTitle;
|
||||
titleLanding.restart();
|
||||
}
|
||||
|
||||
// Browser tabs, terminals and editors can update titles several times in a
|
||||
// frame. Keep the old caption steady until the stream settles instead of
|
||||
// restarting an animation for every intermediate value.
|
||||
Timer {
|
||||
id: titleSettle
|
||||
interval: 120
|
||||
repeat: false
|
||||
onTriggered: root.commitTitle()
|
||||
}
|
||||
|
||||
onIncomingTitleChanged: titleSettle.restart()
|
||||
Component.onCompleted: displayedTitle = incomingTitle
|
||||
|
||||
// A quick crimson wipe under the title on focus change.
|
||||
Rectangle {
|
||||
@@ -55,7 +98,7 @@ Item {
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: flash
|
||||
id: titleLanding
|
||||
ParallelAnimation {
|
||||
NumberAnimation {
|
||||
target: wipe; property: "width"
|
||||
@@ -66,6 +109,24 @@ Item {
|
||||
target: wipe; property: "opacity"
|
||||
from: 0; to: 1; duration: Theme.durFast
|
||||
}
|
||||
NumberAnimation {
|
||||
target: label; property: "x"
|
||||
from: -10; to: 0
|
||||
duration: Theme.durBase; easing.type: Easing.OutBack
|
||||
}
|
||||
NumberAnimation {
|
||||
target: label; property: "opacity"
|
||||
from: 0.35; to: 1; duration: Theme.durFast
|
||||
}
|
||||
NumberAnimation {
|
||||
target: ghost; property: "x"
|
||||
from: 9; to: -3
|
||||
duration: Theme.durBase; easing.type: Easing.OutExpo
|
||||
}
|
||||
NumberAnimation {
|
||||
target: ghost; property: "opacity"
|
||||
from: 0.8; to: 0; duration: Theme.durBase
|
||||
}
|
||||
}
|
||||
NumberAnimation {
|
||||
target: wipe; property: "opacity"
|
||||
|
||||
+3
-16
@@ -37,29 +37,16 @@ PanelWindow {
|
||||
exclusiveZone: Theme.barHeight + Theme.barMargin
|
||||
color: "transparent"
|
||||
|
||||
// The slab. One panel, full width, flat fill, black keyline.
|
||||
P5Panel {
|
||||
// Calm permanent chrome: one opaque slab with no texture or offset copy.
|
||||
ChromeSurface {
|
||||
id: slab
|
||||
anchors.fill: parent
|
||||
lean: Theme.skew
|
||||
chop: Theme.cut
|
||||
chopCorners: [1, 3]
|
||||
padding: 0
|
||||
fill: Theme.surface
|
||||
fillOpacity: 1.0
|
||||
border: Theme.alpha(Theme.outline, 0.9)
|
||||
borderWidth: 1
|
||||
slash: true
|
||||
slashColor: Theme.accent
|
||||
active: true
|
||||
slashWidth: 5
|
||||
|
||||
// No offset copy on the big slabs. The 3px black silhouette behind the
|
||||
// fill is a print-misregistration trick, and at this scale it stops
|
||||
// reading as one: on a 1900px bar you only ever see it at the chopped
|
||||
// corners, where it turns the single clean cut into a staggered double
|
||||
// edge that looks like a rendering fault. The black keyline still
|
||||
// carries the heavy outer contour, which is what the corner needs.
|
||||
drop: false
|
||||
}
|
||||
|
||||
readonly property real edgeInset: Theme.barHeight * Theme.skew + Theme.padM
|
||||
|
||||
@@ -17,7 +17,7 @@ BarPill {
|
||||
|
||||
SystemClock {
|
||||
id: clock
|
||||
precision: SystemClock.Seconds
|
||||
precision: SystemClock.Minutes
|
||||
}
|
||||
|
||||
onClicked: calendar.pinned = !calendar.pinned
|
||||
@@ -65,16 +65,6 @@ BarPill {
|
||||
split: 1.4
|
||||
}
|
||||
|
||||
// Seconds, kept quiet so they read as a ticking detail rather than noise.
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.verticalCenterOffset: 4
|
||||
text: Qt.formatDateTime(clock.date, "ss")
|
||||
color: Theme.alpha(Theme.primary, 0.8)
|
||||
font.family: Theme.fontMono
|
||||
font.pixelSize: Theme.fsMicro
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
|
||||
// Glitch the time on the hour, because it should feel like something happened.
|
||||
|
||||
@@ -17,7 +17,7 @@ BarPill {
|
||||
visible: Media.hasPlayer || width > 1
|
||||
|
||||
// Track position, drawn by the pill in its own rule band.
|
||||
progress: Media.canSeek ? Media.progress : -1
|
||||
progress: Media.hasTimeline ? Media.progress : -1
|
||||
|
||||
implicitWidth: Media.hasPlayer
|
||||
? layout.implicitWidth + contentPad * 2
|
||||
@@ -83,8 +83,6 @@ BarPill {
|
||||
text: Media.sourceIcon
|
||||
font.pixelSize: Theme.fsLarge
|
||||
color: Theme.primary
|
||||
pulsing: Media.playing
|
||||
pulseScale: 1.12
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,28 +99,33 @@ BarPill {
|
||||
|
||||
readonly property real cap: 150
|
||||
|
||||
Marquee {
|
||||
Text {
|
||||
width: Math.min(meta.cap, implicitWidth)
|
||||
height: 14
|
||||
text: Media.title || Media.identity
|
||||
color: Theme.text
|
||||
pixelSize: Theme.fsSmall
|
||||
family: Theme.fontDisplay
|
||||
weight: Font.DemiBold
|
||||
italic: true
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: Theme.fsSmall
|
||||
font.family: Theme.fontDisplay
|
||||
font.weight: Font.DemiBold
|
||||
font.italic: true
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Marquee {
|
||||
Text {
|
||||
width: Math.min(meta.cap, implicitWidth)
|
||||
height: 12
|
||||
text: Media.artist || Media.album || Media.identity
|
||||
color: Theme.muted
|
||||
pixelSize: Theme.fsMicro
|
||||
family: Theme.fontMono
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: Theme.fsMicro
|
||||
font.family: Theme.fontMono
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
|
||||
// Play state, as a two-bar equaliser that freezes when paused.
|
||||
// Compact play-state equaliser. It moves only while audio is playing
|
||||
// and freezes into three quiet bars when paused.
|
||||
Row {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
+36
-26
@@ -40,8 +40,8 @@ PanelWindow {
|
||||
exclusiveZone: Theme.railWidth + Theme.barMargin
|
||||
color: "transparent"
|
||||
|
||||
// The slab.
|
||||
P5Panel {
|
||||
// The same calm permanent-chrome preset used by the top bar.
|
||||
ChromeSurface {
|
||||
id: slab
|
||||
anchors.fill: parent
|
||||
vertical: true
|
||||
@@ -49,18 +49,8 @@ PanelWindow {
|
||||
chop: Theme.cut
|
||||
chopCorners: [1, 3]
|
||||
padding: 0
|
||||
fill: Theme.surface
|
||||
fillOpacity: 1.0
|
||||
border: Theme.alpha(Theme.outline, 0.9)
|
||||
borderWidth: 1
|
||||
slash: true
|
||||
slashColor: Theme.accent
|
||||
active: true
|
||||
slashWidth: 5
|
||||
|
||||
// Same as the bar — see the note there. The doubled corner is worse on
|
||||
// the rail, because its chopped corners sit against the wallpaper's
|
||||
// high-contrast stripes rather than against other chrome.
|
||||
drop: false
|
||||
}
|
||||
|
||||
readonly property real edgeInset: Theme.railWidth * Theme.skew + Theme.padM
|
||||
@@ -76,18 +66,21 @@ PanelWindow {
|
||||
RailIcon {
|
||||
id: netIcon
|
||||
icon: Net.icon
|
||||
label: "Network"
|
||||
statusText: Net.label
|
||||
suppressTooltip: netPopout.detailActive
|
||||
dim: !Sys.netUp
|
||||
onClicked: netPopout.pinned = !netPopout.pinned
|
||||
onRightClicked: Net.toggleWifi()
|
||||
onMiddleClicked: Actions.openNetworkSettings()
|
||||
onClicked: netPopout.togglePinned()
|
||||
}
|
||||
|
||||
RailIcon {
|
||||
id: btIcon
|
||||
icon: Bt.icon
|
||||
label: "Bluetooth"
|
||||
statusText: Bt.label
|
||||
suppressTooltip: btPopout.detailActive
|
||||
dim: !Bt.enabled
|
||||
onClicked: btPopout.pinned = !btPopout.pinned
|
||||
onRightClicked: Bt.toggle()
|
||||
onClicked: btPopout.togglePinned()
|
||||
}
|
||||
|
||||
Divider {}
|
||||
@@ -96,19 +89,24 @@ PanelWindow {
|
||||
id: volIcon
|
||||
icon: Audio.icon
|
||||
readout: Audio.muted ? "" : Audio.volumePercent + "%"
|
||||
label: "Volume"
|
||||
statusText: Audio.muted ? "Muted" : Audio.volumePercent + "% · " + Audio.sinkName
|
||||
hint: "Scroll to adjust"
|
||||
suppressTooltip: audioPopout.detailActive
|
||||
alert: Audio.muted
|
||||
onClicked: Audio.toggleMute()
|
||||
onRightClicked: audioPopout.pinned = !audioPopout.pinned
|
||||
onMiddleClicked: Actions.openAudioSettings()
|
||||
onClicked: audioPopout.togglePinned()
|
||||
onScrolled: delta => Audio.changeVolume(delta > 0 ? 0.05 : -0.05)
|
||||
}
|
||||
|
||||
RailIcon {
|
||||
id: micIcon
|
||||
icon: Audio.micIcon
|
||||
label: "Microphone"
|
||||
statusText: Audio.micMuted ? "Muted" : Audio.micPercent + "% · " + Audio.sourceName
|
||||
hint: "Scroll to adjust"
|
||||
suppressTooltip: audioPopout.detailActive
|
||||
alert: Audio.micMuted
|
||||
onClicked: Audio.toggleMicMute()
|
||||
onRightClicked: audioPopout.pinned = !audioPopout.pinned
|
||||
onClicked: audioPopout.togglePinned()
|
||||
onScrolled: delta => Audio.setMicVolume(Audio.micVolume + (delta > 0 ? 0.05 : -0.05))
|
||||
}
|
||||
|
||||
@@ -117,6 +115,9 @@ PanelWindow {
|
||||
visible: Brightness.available
|
||||
icon: Brightness.icon
|
||||
readout: Math.round(Brightness.percent) + "%"
|
||||
label: "Brightness"
|
||||
statusText: Math.round(Brightness.percent) + "%"
|
||||
hint: "Scroll to adjust"
|
||||
onScrolled: delta => Brightness.change(delta > 0 ? 5 : -5)
|
||||
}
|
||||
|
||||
@@ -127,9 +128,12 @@ PanelWindow {
|
||||
visible: Battery.available
|
||||
icon: Battery.icon
|
||||
readout: Math.round(Battery.percent) + "%"
|
||||
label: "Battery"
|
||||
statusText: Math.round(Battery.percent) + "% · " + Battery.timeLabel
|
||||
suppressTooltip: battPopout.detailActive
|
||||
alert: Battery.critical
|
||||
pulsing: Battery.critical
|
||||
onClicked: battPopout.pinned = !battPopout.pinned
|
||||
onClicked: battPopout.togglePinned()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,21 +155,27 @@ PanelWindow {
|
||||
RailIcon {
|
||||
id: bellIcon
|
||||
icon: Notifs.icon
|
||||
label: "Notifications"
|
||||
statusText: Notifs.dnd
|
||||
? "Do not disturb"
|
||||
: (Notifs.unread > 0 ? Notifs.unread + " unread" : "All caught up")
|
||||
readout: Notifs.unread > 0
|
||||
? (Notifs.unread > 9 ? "9+" : String(Notifs.unread))
|
||||
: ""
|
||||
dim: Notifs.dnd
|
||||
alert: Notifs.hasUnread && !Notifs.dnd
|
||||
suppressTooltip: notifPopout.detailActive
|
||||
onClicked: {
|
||||
notifPopout.pinned = !notifPopout.pinned;
|
||||
notifPopout.togglePinned();
|
||||
Notifs.markRead();
|
||||
}
|
||||
onRightClicked: Notifs.toggleDnd()
|
||||
}
|
||||
|
||||
RailIcon {
|
||||
id: powerIcon
|
||||
icon: ""
|
||||
label: "Power"
|
||||
statusText: "Session actions"
|
||||
alert: Actions.powerMenuOpen
|
||||
onClicked: Actions.togglePowerMenu()
|
||||
}
|
||||
|
||||
+13
-10
@@ -12,6 +12,10 @@ Item {
|
||||
|
||||
property string icon: ""
|
||||
property string readout: ""
|
||||
property string label: ""
|
||||
property string statusText: ""
|
||||
property string hint: ""
|
||||
property bool suppressTooltip: false
|
||||
property color tint: Theme.primary
|
||||
property bool alert: false
|
||||
property bool dim: false
|
||||
@@ -24,8 +28,6 @@ Item {
|
||||
readonly property real wipeInset: 5
|
||||
|
||||
signal clicked()
|
||||
signal rightClicked()
|
||||
signal middleClicked()
|
||||
signal scrolled(real delta)
|
||||
|
||||
implicitWidth: Theme.railWidth
|
||||
@@ -110,16 +112,17 @@ Item {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onTapped: root.clicked()
|
||||
}
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
onTapped: root.rightClicked()
|
||||
}
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.MiddleButton
|
||||
onTapped: root.middleClicked()
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,10 +56,8 @@ BarPill {
|
||||
color: Theme.alpha(Theme.outline, 0.8)
|
||||
}
|
||||
|
||||
// The CPU/MEM/GPU labels are gone: three characters of chrome per metric,
|
||||
// repeated three times, in the most cramped part of the shell. The number
|
||||
// over its meter is legible on its own, and dropping them buys the room to
|
||||
// set the figure in display italic instead of squeezing it into mono.
|
||||
// A tiny fixed label makes every number self-describing without turning the
|
||||
// compact meter cluster back into three separate cards.
|
||||
component Metric: Column {
|
||||
id: metric
|
||||
|
||||
@@ -70,18 +68,34 @@ BarPill {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
Text {
|
||||
text: metric.readout
|
||||
color: Theme.heat(metric.value)
|
||||
font.family: Theme.fontDisplay
|
||||
font.pixelSize: Theme.fsBody
|
||||
font.weight: Theme.weightDisplay
|
||||
font.italic: true
|
||||
font.letterSpacing: 0.5
|
||||
renderType: Text.NativeRendering
|
||||
Row {
|
||||
spacing: 4
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.durBase }
|
||||
Text {
|
||||
anchors.baseline: readoutText.baseline
|
||||
text: metric.label
|
||||
color: Theme.muted
|
||||
font.family: Theme.fontMono
|
||||
font.pixelSize: Theme.fsMicro
|
||||
font.weight: Font.Bold
|
||||
font.letterSpacing: 0.5
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Text {
|
||||
id: readoutText
|
||||
text: metric.readout
|
||||
color: Theme.heat(metric.value)
|
||||
font.family: Theme.fontDisplay
|
||||
font.pixelSize: Theme.fsBody
|
||||
font.weight: Theme.weightDisplay
|
||||
font.italic: true
|
||||
font.letterSpacing: 0.5
|
||||
renderType: Text.NativeRendering
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.durBase }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user