theme: calm YoRHa lock screen and power menu; nvim and btop follow
The lock screen and the power menu lose the last Persona motion: no split display type, no grid sweeping in behind a dark column, no tilted slabs, no skewed password blocks or hard shake. Both are now one small group of type in the dark — the porthole mark, a line of light display type, a status line, and either a rounded password field that fills with dots or a NieR menu whose selected row is a pill of light (rust for the session-ending three). The lock keeps the wallpaper almost whole; a wrong password nudges the field once and turns it to rust. P5Backdrop and the grid texture had no users left and go. Neovim moves to a local yorha colorscheme (nvim submodule) and btop gets a yorha theme with hairline boxes and grey-to-light gradients. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a8c19961e2
commit
63a910bf14
11 files changed
+581
-565
No files matched your search
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#* Name of a btop++/bpytop/bashtop formatted ".theme" file, "Default" and "TTY" for builtin themes.
|
#* Name of a btop++/bpytop/bashtop formatted ".theme" file, "Default" and "TTY" for builtin themes.
|
||||||
#* Themes should be placed in "../share/btop/themes" relative to binary or "$HOME/.config/btop/themes"
|
#* Themes should be placed in "../share/btop/themes" relative to binary or "$HOME/.config/btop/themes"
|
||||||
color_theme = "/home/narl/.config/btop/themes/catppuccin_mocha.theme"
|
color_theme = "/home/narl/.config/btop/themes/yorha.theme"
|
||||||
|
|
||||||
#* If the theme set background should be shown, set to False if you want terminal background transparency.
|
#* If the theme set background should be shown, set to False if you want terminal background transparency.
|
||||||
theme_background = false
|
theme_background = false
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# YoRHa dark — matched to ~/.config/quickshell/config/Theme.qml and
|
||||||
|
# ~/.config/alacritty/yorha.toml.
|
||||||
|
#
|
||||||
|
# The void ground and sage text of the space-1 wallpaper. Boxes are hairline
|
||||||
|
# grey, not four competing hues; the light marks what is selected. Every
|
||||||
|
# gradient climbs from the suit's grey up to the light, and only temperature
|
||||||
|
# and load run on into the hull's rust, the shell's one colour for "wrong".
|
||||||
|
|
||||||
|
theme[main_bg]="#0f110e"
|
||||||
|
theme[main_fg]="#ccd2cb"
|
||||||
|
theme[title]="#eef1ea"
|
||||||
|
theme[hi_fg]="#eef1ea"
|
||||||
|
theme[selected_bg]="#20241f"
|
||||||
|
theme[selected_fg]="#eef1ea"
|
||||||
|
theme[inactive_fg]="#545c52"
|
||||||
|
theme[graph_text]="#9fa7a2"
|
||||||
|
theme[meter_bg]="#20241f"
|
||||||
|
theme[proc_misc]="#9fa7a2"
|
||||||
|
|
||||||
|
theme[cpu_box]="#383e35"
|
||||||
|
theme[mem_box]="#383e35"
|
||||||
|
theme[net_box]="#383e35"
|
||||||
|
theme[proc_box]="#383e35"
|
||||||
|
theme[div_line]="#2a2f28"
|
||||||
|
|
||||||
|
theme[temp_start]="#8e978d"
|
||||||
|
theme[temp_mid]="#c9b27c"
|
||||||
|
theme[temp_end]="#c8684f"
|
||||||
|
|
||||||
|
theme[cpu_start]="#79847a"
|
||||||
|
theme[cpu_mid]="#bcc1bf"
|
||||||
|
theme[cpu_end]="#c8684f"
|
||||||
|
|
||||||
|
theme[free_start]="#545c52"
|
||||||
|
theme[free_mid]="#79847a"
|
||||||
|
theme[free_end]="#9fa7a2"
|
||||||
|
|
||||||
|
theme[cached_start]="#545c52"
|
||||||
|
theme[cached_mid]="#7f9aa8"
|
||||||
|
theme[cached_end]="#96afbc"
|
||||||
|
|
||||||
|
theme[available_start]="#545c52"
|
||||||
|
theme[available_mid]="#86a8a0"
|
||||||
|
theme[available_end]="#9cbdb4"
|
||||||
|
|
||||||
|
theme[used_start]="#79847a"
|
||||||
|
theme[used_mid]="#bcc1bf"
|
||||||
|
theme[used_end]="#eef1ea"
|
||||||
|
|
||||||
|
theme[download_start]="#545c52"
|
||||||
|
theme[download_mid]="#8fa37e"
|
||||||
|
theme[download_end]="#a5b893"
|
||||||
|
|
||||||
|
theme[upload_start]="#545c52"
|
||||||
|
theme[upload_mid]="#c9b27c"
|
||||||
|
theme[upload_end]="#d8c491"
|
||||||
|
|
||||||
|
theme[process_start]="#79847a"
|
||||||
|
theme[process_mid]="#9fa7a2"
|
||||||
|
theme[process_end]="#eef1ea"
|
||||||
+1
-1
Submodule nvim updated: 4736228253...19e0db60a1.
+21
-9
@@ -52,9 +52,10 @@ its own panel.
|
|||||||
The shell uses three deliberate surface tiers. **Chrome** (the hull and
|
The shell uses three deliberate surface tiers. **Chrome** (the hull and
|
||||||
`ChromeSurface`) is the calm, texture-free bar, rail, and tooltip layer. **Transient**
|
`ChromeSurface`) is the calm, texture-free bar, rail, and tooltip layer. **Transient**
|
||||||
(`PopoutSurface`) is reserved for expressive menus and detail panels, with one
|
(`PopoutSurface`) is reserved for expressive menus and detail panels, with one
|
||||||
clear composition and stronger separation from the desktop. **Cinematic**
|
clear composition and stronger separation from the desktop. **Scene**
|
||||||
(`P5Backdrop`) belongs only to scene-scale experiences such as power and lock;
|
surfaces — the lock screen and the power menu — have no panel at all: the
|
||||||
it is never used as decoration inside ordinary chrome or popouts.
|
desktop or the wallpaper dims, and one small group of type (the mark, a line of
|
||||||
|
thin display type, a short log, a menu or a field) stands in the dark.
|
||||||
|
|
||||||
```
|
```
|
||||||
shell.qml entrypoint — hull, bar, rail, dashboard, power menu, toasts, OSD per screen
|
shell.qml entrypoint — hull, bar, rail, dashboard, power menu, toasts, OSD per screen
|
||||||
@@ -217,6 +218,12 @@ Authenticates through `/etc/pam.d/hyprlock`, which on this box is just
|
|||||||
styled in an ordinary window (`qs -p lockpreview.qml`) without locking the
|
styled in an ordinary window (`qs -p lockpreview.qml`) without locking the
|
||||||
session.
|
session.
|
||||||
|
|
||||||
|
It is the wallpaper, barely softened, with the face set in the dark of the
|
||||||
|
porthole: the mark, the time in light display type, the date, a status log
|
||||||
|
(battery, network, what is playing, unread notices), and a rounded field that
|
||||||
|
fills with dots as you type. The dots breathe while PAM checks; a wrong
|
||||||
|
password turns them and the field's rim to rust and nudges the field once.
|
||||||
|
|
||||||
If the shell ever dies while the session is locked, the compositor keeps the
|
If the shell ever dies while the session is locked, the compositor keeps the
|
||||||
session secured by protocol and there is no unlocker left to talk to — recovery
|
session secured by protocol and there is no unlocker left to talk to — recovery
|
||||||
means restarting Hyprland from a TTY.
|
means restarting Hyprland from a TTY.
|
||||||
@@ -270,9 +277,9 @@ Borders are structure and stay neutral.
|
|||||||
`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
|
||||||
appear only while `glitch()` runs — NieR's brief data-corruption flicker.
|
appear only while `glitch()` runs — NieR's brief data-corruption flicker.
|
||||||
|
|
||||||
**Texture is tiered.** Chrome (bar, rail, tooltips) and transient panels
|
**Texture is rare.** Chrome (bar, rail, tooltips), transient panels (popouts,
|
||||||
(popouts, OSD, toasts) are flat. The hairline grid and the dot wash live only in
|
OSD, toasts) and the two scene surfaces are flat. Depth comes from the hull's
|
||||||
`P5Backdrop`, behind the lock screen and the power menu.
|
dark, the rim hairline and the wallpaper, not from patterns.
|
||||||
|
|
||||||
**Type has a floor.** Nothing renders below `Theme.fsMicro` (10px).
|
**Type has a floor.** Nothing renders below `Theme.fsMicro` (10px).
|
||||||
|
|
||||||
@@ -284,6 +291,8 @@ appear only while `glitch()` runs — NieR's brief data-corruption flicker.
|
|||||||
| `hypr/lua/palette.lua` | window borders, groups |
|
| `hypr/lua/palette.lua` | window borders, groups |
|
||||||
| `alacritty/yorha.toml` | the terminal |
|
| `alacritty/yorha.toml` | the terminal |
|
||||||
| `fish/conf.d/yorha_theme.fish` | the prompt and pager |
|
| `fish/conf.d/yorha_theme.fish` | the prompt and pager |
|
||||||
|
| `nvim/lua/yorha.lua` | the editor and its statusline |
|
||||||
|
| `btop/themes/yorha.theme` | the resource monitor |
|
||||||
|
|
||||||
Everything else derives. `glow` *is* `accent`, and `primary` is plain `text`.
|
Everything else derives. `glow` *is* `accent`, and `primary` is plain `text`.
|
||||||
`Theme.heat()` is a continuous text → light → red ramp, so a CPU figure that
|
`Theme.heat()` is a continuous text → light → red ramp, so a CPU figure that
|
||||||
@@ -308,9 +317,12 @@ network and bluetooth, but keep running as the secret and pairing agents.
|
|||||||
Alacritty only live-reloads when `alacritty.toml` itself changes, so touch
|
Alacritty only live-reloads when `alacritty.toml` itself changes, so touch
|
||||||
it after editing the palette.
|
it after editing the palette.
|
||||||
The Catppuccin and `takemi.toml` files are left in place, unreferenced.
|
The Catppuccin and `takemi.toml` files are left in place, unreferenced.
|
||||||
- **Neovim** — still the `catppuccin` plugin, flavour mocha, configured in
|
- **Neovim** — `nvim/lua/yorha.lua` (loaded by `colors/yorha.lua`, with a
|
||||||
`nvim/lua/plugins/ui.lua`. Not yet moved to the YoRHa palette.
|
matching lualine theme). Syntax in the terminal's earth hues, floats as hull
|
||||||
- **GTK and Qt** — still `catppuccin-mocha-blue`. Not yet moved either.
|
surfaces with rounded rims, menus selecting with the light slab.
|
||||||
|
- **btop** — `btop/themes/yorha.theme`. Hairline boxes; gradients climb from
|
||||||
|
grey to the light, and only temperature and CPU load run on into rust.
|
||||||
|
- **GTK and Qt** — still `catppuccin-mocha-blue`. Not yet moved.
|
||||||
- **Fish** — `fish/conf.d/yorha_theme.fish`.
|
- **Fish** — `fish/conf.d/yorha_theme.fish`.
|
||||||
- **The launcher** — no external theming to keep in step, because it is part of
|
- **The launcher** — no external theming to keep in step, because it is part of
|
||||||
this shell (`launcher/`). Fuzzel is gone; `takemi-dmenu` stands in for
|
this shell (`launcher/`). Fuzzel is gone; `takemi-dmenu` stands in for
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 337 B |
@@ -1,91 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import "root:/config"
|
|
||||||
import "root:/components"
|
|
||||||
|
|
||||||
// The shared ground for the two full-screen surfaces, in the NieR menu
|
|
||||||
// pattern: a hairline grid that sweeps in from the left, a darker column down
|
|
||||||
// the left-hand side for the menu or the clock to stand in, and a dot wash
|
|
||||||
// over the top.
|
|
||||||
//
|
|
||||||
// The lock screen and the power menu both stand on this one component, which
|
|
||||||
// is what makes them read as the same object seen twice rather than as two
|
|
||||||
// guesses at the same brief.
|
|
||||||
Item {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
// 0 = withdrawn, 1 = fully present. Drives the grid slide, the grid
|
|
||||||
// opacity and the column together, so a caller animates one number instead
|
|
||||||
// of keeping three in sync. The power menu runs it both ways on open and
|
|
||||||
// close; the lock runs it once on entry.
|
|
||||||
property real reveal: 1
|
|
||||||
|
|
||||||
property color tone: Theme.accent
|
|
||||||
|
|
||||||
// The column: a band of the void, edged by a hairline of the light.
|
|
||||||
// `wedge*` names are kept from the Persona layout so callers did not have
|
|
||||||
// to move; there is nothing wedge-shaped left.
|
|
||||||
property bool wedge: true
|
|
||||||
property real wedgeOpacity: 0.35
|
|
||||||
property real wedgeLeft: 0
|
|
||||||
property real wedgeSpan: 0.34
|
|
||||||
|
|
||||||
property bool stripes: true
|
|
||||||
property real stripeOpacity: 0.05
|
|
||||||
|
|
||||||
property bool halftone: true
|
|
||||||
property real halftoneOpacity: 0.05
|
|
||||||
property color halftoneColor: Theme.primary
|
|
||||||
|
|
||||||
// Hairline grid, sliding in from off-screen left.
|
|
||||||
Image {
|
|
||||||
anchors.fill: parent
|
|
||||||
visible: root.stripes && root.reveal > 0
|
|
||||||
source: Theme.texGrid
|
|
||||||
fillMode: Image.Tile
|
|
||||||
opacity: root.stripeOpacity * root.reveal
|
|
||||||
smooth: false
|
|
||||||
|
|
||||||
layer.enabled: true
|
|
||||||
layer.effect: TintEffect {
|
|
||||||
tintColor: root.tone
|
|
||||||
}
|
|
||||||
|
|
||||||
transform: Translate {
|
|
||||||
x: -root.width * (1 - root.reveal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The column. Drawn after the grid so it reads as a panel laid over it.
|
|
||||||
Item {
|
|
||||||
x: root.wedgeLeft * root.width
|
|
||||||
width: root.wedgeSpan * root.width
|
|
||||||
height: root.height
|
|
||||||
visible: root.wedge && root.reveal > 0
|
|
||||||
opacity: root.reveal
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: Theme.alpha(Theme.ink, root.wedgeOpacity)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Its edge, drawn top-down as the column arrives.
|
|
||||||
Rectangle {
|
|
||||||
anchors.right: parent.right
|
|
||||||
width: 1
|
|
||||||
height: parent.height * root.reveal
|
|
||||||
color: Theme.alpha(root.tone, 0.35)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dot wash over the whole thing.
|
|
||||||
Image {
|
|
||||||
anchors.fill: parent
|
|
||||||
visible: root.halftone
|
|
||||||
source: Theme.texDots
|
|
||||||
fillMode: Image.Tile
|
|
||||||
opacity: root.halftoneOpacity
|
|
||||||
smooth: false
|
|
||||||
layer.enabled: true
|
|
||||||
layer.effect: TintEffect { tintColor: root.halftoneColor }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -240,10 +240,8 @@ Singleton {
|
|||||||
readonly property var easeIn: [0.55, 0.0, 0.9, 0.35, 1, 1]
|
readonly property var easeIn: [0.55, 0.0, 0.9, 0.35, 1, 1]
|
||||||
|
|
||||||
// -------------------------------------------------------------- textures
|
// -------------------------------------------------------------- textures
|
||||||
// A 1px dot every 8px, and a 32px hairline grid — the two backgrounds the
|
// A 1px dot every 8px, the ground NieR's menus are printed on.
|
||||||
// NieR menus are printed on.
|
|
||||||
readonly property url texDots: "root:/assets/dots.png"
|
readonly property url texDots: "root:/assets/dots.png"
|
||||||
readonly property url texGrid: "root:/assets/grid.png"
|
|
||||||
readonly property url texScanline: "root:/assets/scanline.png"
|
readonly property url texScanline: "root:/assets/scanline.png"
|
||||||
|
|
||||||
// Last resort only. The live wallpaper comes from the Wallpaper singleton,
|
// Last resort only. The live wallpaper comes from the Wallpaper singleton,
|
||||||
|
|||||||
+286
-259
@@ -10,6 +10,13 @@ import "root:/lock"
|
|||||||
// The lock screen's whole face, kept separate from WlSessionLockSurface so it
|
// The lock screen's whole face, kept separate from WlSessionLockSurface so it
|
||||||
// can be instantiated in an ordinary window for development without actually
|
// can be instantiated in an ordinary window for development without actually
|
||||||
// locking the session.
|
// locking the session.
|
||||||
|
//
|
||||||
|
// Quiet on purpose. The wallpaper stays almost whole — the porthole, the
|
||||||
|
// planet, the girl looking out — and the face is one small group of type set
|
||||||
|
// in the dark of the porthole: the mark, the time, the date, a short status
|
||||||
|
// log, and a rounded field to type into. Nothing sweeps, splits or shakes;
|
||||||
|
// the group resolves in, and a wrong password nudges it once and turns its
|
||||||
|
// dots to rust.
|
||||||
Item {
|
Item {
|
||||||
id: surface
|
id: surface
|
||||||
|
|
||||||
@@ -20,12 +27,12 @@ Item {
|
|||||||
property string status: ""
|
property string status: ""
|
||||||
property bool failed: false
|
property bool failed: false
|
||||||
property bool busy: pam.active
|
property bool busy: pam.active
|
||||||
readonly property real uiScale: Math.max(0.72, Math.min(1.15,
|
readonly property real uiScale: Math.max(0.72, Math.min(1.3,
|
||||||
Math.min(width / 1920, height / 1080)))
|
Math.min(width / 1920, height / 1080)))
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: Theme.base
|
color: Theme.ink
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------- background
|
// ------------------------------------------------------- background
|
||||||
@@ -43,290 +50,312 @@ Item {
|
|||||||
fillMode: Image.PreserveAspectCrop
|
fillMode: Image.PreserveAspectCrop
|
||||||
asynchronous: false
|
asynchronous: false
|
||||||
cache: true
|
cache: true
|
||||||
|
opacity: 0
|
||||||
|
|
||||||
|
// Barely softened: enough that the scene steps back behind the type,
|
||||||
|
// not so much that it stops being a place.
|
||||||
layer.enabled: true
|
layer.enabled: true
|
||||||
layer.effect: MultiEffect {
|
layer.effect: MultiEffect {
|
||||||
blurEnabled: true
|
blurEnabled: true
|
||||||
blur: 0.55
|
blur: 0.2
|
||||||
blurMax: 48
|
blurMax: 32
|
||||||
saturation: -0.15
|
saturation: -0.1
|
||||||
brightness: -0.35
|
brightness: -0.18
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wash pulling the photo down toward the shell's ground. Was described as
|
// Shade deepening toward the left, where the type sits in the porthole's
|
||||||
// a teal wash back when the UI was teal; the colours it actually uses are
|
// dark, and a breath of it along the foot.
|
||||||
// the neutral surfaces, which is what it should have been all along.
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
gradient: Gradient {
|
gradient: Gradient {
|
||||||
GradientStop { position: 0.0; color: Theme.alpha(Theme.deep, 0.55) }
|
orientation: Gradient.Horizontal
|
||||||
GradientStop { position: 0.6; color: Theme.alpha(Theme.base, 0.75) }
|
GradientStop { position: 0.0; color: Theme.alpha(Theme.ink, 0.72) }
|
||||||
GradientStop { position: 1.0; color: Theme.alpha(Theme.base, 0.92) }
|
GradientStop { position: 0.45; color: Theme.alpha(Theme.ink, 0.28) }
|
||||||
|
GradientStop { position: 1.0; color: Theme.alpha(Theme.ink, 0.18) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The same wedge-and-stripes ground the power menu stands on. The wedge
|
Rectangle {
|
||||||
// sits under the clock, which is where the power menu puts it under the
|
|
||||||
// title — so the two screens land the accent mass in the same place.
|
|
||||||
P5Backdrop {
|
|
||||||
id: backdrop
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
reveal: 0
|
gradient: Gradient {
|
||||||
wedgeSpan: 0.30
|
GradientStop { position: 0.6; color: "transparent" }
|
||||||
wedgeOpacity: 0.30
|
GradientStop { position: 1.0; color: Theme.alpha(Theme.ink, 0.5) }
|
||||||
stripeOpacity: 0.04
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------ clock
|
// ------------------------------------------------------------- face
|
||||||
SystemClock {
|
SystemClock {
|
||||||
id: clock
|
id: clock
|
||||||
precision: SystemClock.Minutes
|
precision: SystemClock.Minutes
|
||||||
}
|
}
|
||||||
|
|
||||||
// The house mark, the same porthole the bar carries far left. Small, above
|
|
||||||
// the clock — the lock's hero is the time, not the branding.
|
|
||||||
Porthole {
|
|
||||||
id: mark
|
|
||||||
anchors.left: timeBlock.left
|
|
||||||
anchors.bottom: timeBlock.top
|
|
||||||
anchors.bottomMargin: 10
|
|
||||||
size: 28
|
|
||||||
opacity: timeBlock.opacity
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: timeBlock
|
id: face
|
||||||
anchors.left: parent.left
|
x: parent.width * 0.14
|
||||||
anchors.leftMargin: parent.width * 0.1
|
y: parent.height * 0.2
|
||||||
anchors.top: parent.top
|
width: 380
|
||||||
anchors.topMargin: parent.height * 0.18
|
scale: surface.uiScale
|
||||||
spacing: -18
|
transformOrigin: Item.TopLeft
|
||||||
|
opacity: 0
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
SplitText {
|
transform: Translate { id: drift }
|
||||||
text: Qt.formatDateTime(clock.date, "HH")
|
|
||||||
pixelSize: Theme.fsMega * 1.7
|
|
||||||
split: 5
|
|
||||||
}
|
|
||||||
|
|
||||||
SplitText {
|
// The house mark, the same porthole the bar carries far left.
|
||||||
text: Qt.formatDateTime(clock.date, "mm")
|
Porthole {
|
||||||
pixelSize: Theme.fsMega * 1.7
|
size: 26
|
||||||
color: Theme.primary
|
|
||||||
split: 5
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: Qt.formatDateTime(clock.date, "dddd, dd MMMM").toUpperCase()
|
topPadding: Theme.padM
|
||||||
|
text: Qt.formatDateTime(clock.date, "HH:mm")
|
||||||
|
color: Theme.accent
|
||||||
|
font.family: Theme.fontDisplay
|
||||||
|
font.pixelSize: Theme.fsMega * 1.35
|
||||||
|
font.weight: Font.Light
|
||||||
|
font.letterSpacing: 2
|
||||||
|
// Scaled with the face, so distance-field glyphs: native hinting
|
||||||
|
// at this size under a scale transform comes out stepped.
|
||||||
|
renderType: Text.QtRendering
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
leftPadding: 4
|
||||||
|
text: Qt.formatDateTime(clock.date, "dddd · dd MMMM").toUpperCase()
|
||||||
color: Theme.subtext
|
color: Theme.subtext
|
||||||
font.family: Theme.fontMono
|
font.family: Theme.fontDisplay
|
||||||
font.pixelSize: Theme.fsBody
|
font.pixelSize: Theme.fsBody
|
||||||
font.letterSpacing: 6
|
font.weight: Theme.weightDisplay
|
||||||
topPadding: 34
|
font.letterSpacing: 5
|
||||||
renderType: Text.NativeRendering
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------ status rail
|
Item { width: 1; height: Theme.padXL }
|
||||||
Column {
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: parent.width * 0.1
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.topMargin: parent.height * 0.2
|
|
||||||
spacing: Theme.padM
|
|
||||||
width: Math.min(300, parent.width * 0.28)
|
|
||||||
scale: surface.uiScale
|
|
||||||
transformOrigin: Item.TopRight
|
|
||||||
|
|
||||||
RailRow {
|
// ------------------------------------------------------ status log
|
||||||
|
Column {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
visible: Battery.available
|
leftPadding: 4
|
||||||
glyph: Battery.icon
|
|
||||||
label: Math.round(Battery.percent) + "%"
|
StatusRow {
|
||||||
detail: Battery.timeLabel
|
width: parent.width - 4
|
||||||
tone: Battery.tint
|
visible: Battery.available
|
||||||
|
label: "Battery"
|
||||||
|
detail: Math.round(Battery.percent) + "%"
|
||||||
|
+ (Battery.timeLabel ? " " + Battery.timeLabel : "")
|
||||||
|
tone: Battery.tint
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusRow {
|
||||||
|
width: parent.width - 4
|
||||||
|
label: "Network"
|
||||||
|
detail: Sys.netUp ? Net.label : "no link"
|
||||||
|
tone: Sys.netUp ? Theme.accentSoft : Theme.overlay
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusRow {
|
||||||
|
width: parent.width - 4
|
||||||
|
visible: Media.hasPlayer
|
||||||
|
label: Media.playing ? "Playing" : "Paused"
|
||||||
|
detail: Media.artist ? Media.title + " — " + Media.artist : Media.title
|
||||||
|
tone: Media.playing ? Theme.accent : Theme.primaryDim
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusRow {
|
||||||
|
width: parent.width - 4
|
||||||
|
visible: Notifs.count > 0
|
||||||
|
label: "Notices"
|
||||||
|
detail: Notifs.count + (Notifs.count === 1 ? " message" : " messages")
|
||||||
|
tone: Theme.accentSoft
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RailRow {
|
Item { width: 1; height: Theme.padXL }
|
||||||
|
|
||||||
|
// ------------------------------------------------------ the field
|
||||||
|
Item {
|
||||||
|
id: authBlock
|
||||||
width: parent.width
|
width: parent.width
|
||||||
glyph: Net.icon
|
height: field.height + hint.height + Theme.padS
|
||||||
label: Net.label
|
|
||||||
detail: Sys.netUp ? Sys.netIp : "no link"
|
|
||||||
tone: Sys.netUp ? Theme.primary : Theme.muted
|
|
||||||
}
|
|
||||||
|
|
||||||
RailRow {
|
transform: Translate { id: nudge }
|
||||||
width: parent.width
|
|
||||||
visible: Media.hasPlayer
|
|
||||||
glyph: Media.playing ? "" : ""
|
|
||||||
label: Media.title
|
|
||||||
detail: Media.artist
|
|
||||||
tone: Theme.primary
|
|
||||||
}
|
|
||||||
|
|
||||||
RailRow {
|
Rectangle {
|
||||||
width: parent.width
|
id: field
|
||||||
visible: Notifs.count > 0
|
width: parent.width
|
||||||
glyph: ""
|
height: 48
|
||||||
label: Notifs.count + " notification" + (Notifs.count === 1 ? "" : "s")
|
radius: height / 2
|
||||||
detail: Notifs.history.length > 0 ? Notifs.history[0].summary : ""
|
color: Theme.alpha(Theme.hull, 0.86)
|
||||||
tone: Theme.accent
|
border.width: 1
|
||||||
}
|
border.color: surface.failed
|
||||||
}
|
? Theme.alarm
|
||||||
|
: (input.activeFocus && surface.entry.length > 0 ? Theme.outline : Theme.rim)
|
||||||
|
|
||||||
// ------------------------------------------------------- auth panel
|
Behavior on border.color {
|
||||||
Item {
|
ColorAnimation { duration: Theme.durBase }
|
||||||
id: authBlock
|
}
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.bottomMargin: parent.height * 0.14
|
|
||||||
width: Math.min(460, parent.width - Theme.padXL * 2)
|
|
||||||
height: 150
|
|
||||||
scale: surface.uiScale
|
|
||||||
transformOrigin: Item.Bottom
|
|
||||||
|
|
||||||
transform: Translate { id: shakeShift }
|
Icon {
|
||||||
|
id: lockGlyph
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.padL
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: ""
|
||||||
|
color: surface.failed ? Theme.alarm : Theme.muted
|
||||||
|
font.pixelSize: Theme.fsLarge
|
||||||
|
}
|
||||||
|
|
||||||
SplitText {
|
Text {
|
||||||
id: prompt
|
anchors.left: lockGlyph.right
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.leftMargin: Theme.padM
|
||||||
anchors.top: parent.top
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: surface.failed
|
visible: surface.entry.length === 0
|
||||||
? (surface.status || "WRONG").toUpperCase()
|
text: "PASSWORD"
|
||||||
: (surface.busy ? "CHECKING…" : "WHO GOES THERE")
|
color: Theme.muted
|
||||||
color: surface.failed ? Theme.alarm : Theme.text
|
font.family: Theme.fontDisplay
|
||||||
pixelSize: Theme.fsTitle
|
font.pixelSize: Theme.fsSmall
|
||||||
letterSpacing: 4
|
font.letterSpacing: Theme.tracking * 2
|
||||||
horizontalAlignment: Text.AlignHCenter
|
renderType: Text.NativeRendering
|
||||||
split: surface.failed ? 4 : 1.6
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Password field: one chopped block per character.
|
// One dot per character, each resolving in as it is typed.
|
||||||
P5Panel {
|
// They breathe while PAM is checking.
|
||||||
id: field
|
Row {
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
id: dots
|
||||||
anchors.top: prompt.bottom
|
anchors.left: lockGlyph.right
|
||||||
anchors.topMargin: Theme.padL
|
anchors.leftMargin: Theme.padM
|
||||||
width: Math.min(380, authBlock.width - Theme.padL * 2)
|
anchors.right: enter.left
|
||||||
height: 56
|
anchors.rightMargin: Theme.padM
|
||||||
fill: Theme.mantle
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
fillOpacity: 0.94
|
spacing: 7
|
||||||
border: surface.failed
|
clip: true
|
||||||
? Theme.accent
|
|
||||||
: (input.activeFocus ? Theme.primary : Theme.outline)
|
|
||||||
borderWidth: 2
|
|
||||||
slash: true
|
|
||||||
slashColor: surface.failed ? Theme.alarm : Theme.primary
|
|
||||||
slashWidth: 6
|
|
||||||
halftone: true
|
|
||||||
halftoneColor: surface.failed ? Theme.alarm : Theme.primary
|
|
||||||
halftoneOpacity: 0.08
|
|
||||||
|
|
||||||
Row {
|
Repeater {
|
||||||
anchors.centerIn: parent
|
model: Math.min(surface.entry.length, 28)
|
||||||
spacing: 6
|
|
||||||
visible: surface.entry.length > 0
|
|
||||||
|
|
||||||
Repeater {
|
Rectangle {
|
||||||
model: Math.min(surface.entry.length, 22)
|
id: pip
|
||||||
|
required property int index
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: 7
|
||||||
|
height: 7
|
||||||
|
radius: width / 2
|
||||||
|
color: surface.failed ? Theme.alarm : Theme.accent
|
||||||
|
opacity: 0
|
||||||
|
scale: 0.4
|
||||||
|
|
||||||
Skew {
|
Component.onCompleted: arrive.start()
|
||||||
required property int index
|
|
||||||
width: 10
|
|
||||||
height: 20
|
|
||||||
color: surface.failed ? Theme.alarm : Theme.primary
|
|
||||||
|
|
||||||
// Each new block snaps in.
|
ParallelAnimation {
|
||||||
Component.onCompleted: pop.start()
|
id: arrive
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
id: pop
|
target: pip; property: "opacity"
|
||||||
target: parent
|
to: 1; duration: Theme.durBase; easing.type: Easing.OutCubic
|
||||||
property: "scale"
|
}
|
||||||
from: 0.2; to: 1
|
NumberAnimation {
|
||||||
duration: Theme.durBase
|
target: pip; property: "scale"
|
||||||
easing.type: Easing.OutCubic
|
to: 1; duration: Theme.durBase; easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SequentialAnimation on opacity {
|
||||||
|
running: surface.busy
|
||||||
|
loops: Animation.Infinite
|
||||||
|
alwaysRunToEnd: true
|
||||||
|
NumberAnimation { to: 0.35; duration: 520; easing.type: Easing.InOutSine }
|
||||||
|
NumberAnimation { to: 1; duration: 520; easing.type: Easing.InOutSine }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit: a disc at the field's end that lights once there is
|
||||||
|
// something to send.
|
||||||
|
Rectangle {
|
||||||
|
id: enter
|
||||||
|
readonly property bool ready: surface.entry.length > 0 && !surface.busy
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 8
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: 32
|
||||||
|
height: 32
|
||||||
|
radius: width / 2
|
||||||
|
color: enter.ready ? Theme.accent : "transparent"
|
||||||
|
border.width: enter.ready ? 0 : 1
|
||||||
|
border.color: Theme.rim
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation { duration: Theme.durBase }
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: ""
|
||||||
|
color: enter.ready ? Theme.ink : Theme.overlay
|
||||||
|
font.pixelSize: Theme.fsLarge
|
||||||
|
}
|
||||||
|
|
||||||
|
TapHandler {
|
||||||
|
onTapped: surface.submit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The real input, invisible but focused.
|
||||||
|
TextInput {
|
||||||
|
id: input
|
||||||
|
anchors.fill: parent
|
||||||
|
opacity: 0
|
||||||
|
focus: true
|
||||||
|
echoMode: TextInput.Password
|
||||||
|
enabled: !surface.busy
|
||||||
|
activeFocusOnTab: true
|
||||||
|
|
||||||
|
onTextChanged: {
|
||||||
|
surface.entry = text;
|
||||||
|
if (surface.failed && text.length > 0) surface.failed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onAccepted: surface.submit()
|
||||||
|
|
||||||
|
Keys.onEscapePressed: {
|
||||||
|
input.text = "";
|
||||||
|
surface.failed = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
// Under the field: who is locked, what is happening, or what went
|
||||||
anchors.centerIn: parent
|
// wrong — one line, never more.
|
||||||
visible: surface.entry.length === 0 && !surface.busy
|
Row {
|
||||||
text: "PASSWORD"
|
id: hint
|
||||||
color: Theme.muted
|
anchors.top: field.bottom
|
||||||
font.family: Theme.fontMono
|
anchors.topMargin: Theme.padS
|
||||||
font.pixelSize: Theme.fsSmall
|
anchors.left: parent.left
|
||||||
font.letterSpacing: 5
|
anchors.leftMargin: Theme.padL
|
||||||
renderType: Text.NativeRendering
|
spacing: Theme.padS
|
||||||
}
|
|
||||||
|
|
||||||
// Busy sweep.
|
Rectangle {
|
||||||
Rectangle {
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.bottom: parent.bottom
|
width: 4
|
||||||
anchors.bottomMargin: 6
|
height: 4
|
||||||
width: 60
|
radius: 2
|
||||||
height: 3
|
color: surface.failed ? Theme.alarm : Theme.overlay
|
||||||
color: Theme.primary
|
|
||||||
visible: surface.busy
|
|
||||||
|
|
||||||
XAnimator on x {
|
|
||||||
running: surface.busy
|
|
||||||
loops: Animation.Infinite
|
|
||||||
from: 20
|
|
||||||
to: field.width - 80
|
|
||||||
duration: 700
|
|
||||||
easing.type: Easing.InOutQuad
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The real input, invisible but focused.
|
|
||||||
TextInput {
|
|
||||||
id: input
|
|
||||||
anchors.fill: parent
|
|
||||||
opacity: 0
|
|
||||||
focus: true
|
|
||||||
echoMode: TextInput.Password
|
|
||||||
enabled: !surface.busy
|
|
||||||
activeFocusOnTab: true
|
|
||||||
|
|
||||||
onTextChanged: {
|
|
||||||
surface.entry = text;
|
|
||||||
if (surface.failed && text.length === 0) surface.failed = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onAccepted: surface.submit()
|
Text {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
Keys.onEscapePressed: {
|
text: surface.failed
|
||||||
input.text = "";
|
? (surface.status || "Access denied").toUpperCase()
|
||||||
surface.failed = false;
|
: (surface.busy ? "VERIFYING" : (Quickshell.env("USER") || "user"))
|
||||||
|
color: surface.failed ? Theme.alarm : Theme.muted
|
||||||
|
font.family: Theme.fontMono
|
||||||
|
font.pixelSize: Theme.fsMicro
|
||||||
|
font.letterSpacing: 2
|
||||||
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.top: field.bottom
|
|
||||||
anchors.topMargin: Theme.padM
|
|
||||||
spacing: Theme.padS
|
|
||||||
|
|
||||||
Icon {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: ""
|
|
||||||
color: Theme.muted
|
|
||||||
font.pixelSize: Theme.fsBody
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: Quickshell.env("USER") || "USER"
|
|
||||||
color: Theme.muted
|
|
||||||
font.family: Theme.fontMono
|
|
||||||
font.pixelSize: Theme.fsSmall
|
|
||||||
font.letterSpacing: 3
|
|
||||||
renderType: Text.NativeRendering
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep focus on the field no matter what.
|
// Keep focus on the field no matter what.
|
||||||
@@ -362,62 +391,60 @@ Item {
|
|||||||
surface.entry = "";
|
surface.entry = "";
|
||||||
surface.failed = false;
|
surface.failed = false;
|
||||||
} else {
|
} else {
|
||||||
surface.failed = true;
|
|
||||||
if (surface.status === "") surface.status = "Access denied";
|
|
||||||
input.text = "";
|
input.text = "";
|
||||||
surface.entry = "";
|
surface.entry = "";
|
||||||
shake.restart();
|
surface.failed = true;
|
||||||
|
if (surface.status === "") surface.status = "Access denied";
|
||||||
|
refuse.restart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onError: err => {
|
onError: err => {
|
||||||
surface.failed = true;
|
|
||||||
surface.status = "PAM error";
|
|
||||||
input.text = "";
|
input.text = "";
|
||||||
surface.entry = "";
|
surface.entry = "";
|
||||||
shake.restart();
|
surface.failed = true;
|
||||||
|
surface.status = "PAM error";
|
||||||
|
refuse.restart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A wrong password: the field leans away once and comes back, a head
|
||||||
|
// shaken slowly rather than a buzzer.
|
||||||
SequentialAnimation {
|
SequentialAnimation {
|
||||||
id: shake
|
id: refuse
|
||||||
loops: 2
|
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
target: shakeShift; property: "x"
|
target: nudge; property: "x"
|
||||||
to: -16; duration: 55; easing.type: Easing.OutQuad
|
to: -7; duration: 90; easing.type: Easing.OutSine
|
||||||
}
|
}
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
target: shakeShift; property: "x"
|
target: nudge; property: "x"
|
||||||
to: 16; duration: 55; easing.type: Easing.OutQuad
|
to: 5; duration: 140; easing.type: Easing.InOutSine
|
||||||
}
|
}
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
target: shakeShift; property: "x"
|
target: nudge; property: "x"
|
||||||
to: 0; duration: 90; easing.type: Easing.OutCubic
|
to: 0; duration: 220; easing.type: Easing.OutCubic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Entry flourish when the lock comes up.
|
// The lock comes up the way the scene would if you opened your eyes: the
|
||||||
|
// picture first, then the type settling a few pixels into place.
|
||||||
Component.onCompleted: intro.start()
|
Component.onCompleted: intro.start()
|
||||||
|
|
||||||
ParallelAnimation {
|
SequentialAnimation {
|
||||||
id: intro
|
id: intro
|
||||||
// The stripes sweep in under everything, the same move the power menu
|
|
||||||
// opens with.
|
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
target: backdrop; property: "reveal"
|
target: wall; property: "opacity"
|
||||||
from: 0; to: 1; duration: Theme.durLazy; easing.type: Easing.OutExpo
|
from: 0; to: 1; duration: Theme.durLazy; easing.type: Easing.OutCubic
|
||||||
}
|
}
|
||||||
NumberAnimation {
|
ParallelAnimation {
|
||||||
target: timeBlock; property: "opacity"
|
NumberAnimation {
|
||||||
from: 0; to: 1; duration: Theme.durLazy; easing.type: Easing.OutExpo
|
target: face; property: "opacity"
|
||||||
}
|
from: 0; to: 1; duration: Theme.durLazy; easing.type: Easing.OutCubic
|
||||||
NumberAnimation {
|
}
|
||||||
target: authBlock; property: "opacity"
|
NumberAnimation {
|
||||||
from: 0; to: 1; duration: Theme.durLazy; easing.type: Easing.OutExpo
|
target: drift; property: "y"
|
||||||
}
|
from: 14; to: 0; duration: Theme.durLazy; easing.type: Easing.OutQuint
|
||||||
NumberAnimation {
|
}
|
||||||
target: shakeShift; property: "y"
|
|
||||||
from: 40; to: 0; duration: Theme.durLazy; easing.type: Easing.OutCubic
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import "root:/config"
|
|
||||||
import "root:/components"
|
|
||||||
|
|
||||||
// One line of the lock screen's status rail.
|
|
||||||
Item {
|
|
||||||
id: rail
|
|
||||||
|
|
||||||
property string glyph: ""
|
|
||||||
property string label: ""
|
|
||||||
property string detail: ""
|
|
||||||
property color tone: Theme.primary
|
|
||||||
|
|
||||||
implicitHeight: visible ? 38 : 0
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: 3
|
|
||||||
height: 26
|
|
||||||
color: rail.tone
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.padM
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.padS
|
|
||||||
|
|
||||||
Icon {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: rail.glyph
|
|
||||||
color: rail.tone
|
|
||||||
font.pixelSize: Theme.fsLarge
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: -2
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: rail.label
|
|
||||||
color: Theme.text
|
|
||||||
elide: Text.ElideRight
|
|
||||||
font.family: Theme.fontDisplay
|
|
||||||
font.pixelSize: Theme.fsSmall
|
|
||||||
font.weight: Font.DemiBold
|
|
||||||
renderType: Text.NativeRendering
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
visible: rail.detail !== ""
|
|
||||||
text: rail.detail
|
|
||||||
color: Theme.muted
|
|
||||||
elide: Text.ElideRight
|
|
||||||
font.family: Theme.fontMono
|
|
||||||
font.pixelSize: Theme.fsMicro
|
|
||||||
renderType: Text.NativeRendering
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import QtQuick
|
||||||
|
import "root:/config"
|
||||||
|
import "root:/components"
|
||||||
|
|
||||||
|
// One line of the lock screen's status log, laid out like the dashboard's
|
||||||
|
// facts: a dot in the line's tone, the label tracked in caps, a hairline
|
||||||
|
// running out to the detail at the far end.
|
||||||
|
Item {
|
||||||
|
id: row
|
||||||
|
|
||||||
|
property string label: ""
|
||||||
|
property string detail: ""
|
||||||
|
property color tone: Theme.primaryDim
|
||||||
|
|
||||||
|
implicitHeight: visible ? 26 : 0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: dot
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: Theme.marker
|
||||||
|
height: Theme.marker
|
||||||
|
radius: width / 2
|
||||||
|
color: row.tone
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: name
|
||||||
|
anchors.left: dot.right
|
||||||
|
anchors.leftMargin: Theme.padS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: Math.min(implicitWidth, row.width * 0.55)
|
||||||
|
text: row.label.toUpperCase()
|
||||||
|
color: Theme.text
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.family: Theme.fontDisplay
|
||||||
|
font.pixelSize: Theme.fsSmall
|
||||||
|
font.weight: Theme.weightDisplay
|
||||||
|
font.letterSpacing: Theme.tracking
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.left: name.right
|
||||||
|
anchors.leftMargin: Theme.padS
|
||||||
|
anchors.right: value.left
|
||||||
|
anchors.rightMargin: Theme.padS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
height: 1
|
||||||
|
color: Theme.alpha(Theme.outline, 0.4)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: value
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: Math.min(implicitWidth, row.width * 0.4)
|
||||||
|
text: row.detail
|
||||||
|
color: Theme.muted
|
||||||
|
elide: Text.ElideRight
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
font.family: Theme.fontMono
|
||||||
|
font.pixelSize: Theme.fsMicro
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
}
|
||||||
|
}
|
||||||
+145
-138
@@ -1,12 +1,16 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
|
import Quickshell.Io
|
||||||
import "root:/config"
|
import "root:/config"
|
||||||
import "root:/components"
|
import "root:/components"
|
||||||
import "root:/services"
|
import "root:/services"
|
||||||
|
|
||||||
// Full-screen power menu. An accent wedge and stripes sweep in behind a stack of leaning
|
// Full-screen power menu, built like the lock screen: the desktop dims to the
|
||||||
// buttons; Escape or a click on the backdrop dismisses it.
|
// void, and one small group of type stands in it — the mark, the motto, a
|
||||||
|
// line about the session, and a NieR menu of six rows. The selected row is a
|
||||||
|
// pill of light with the text punched out of it; the three that end the
|
||||||
|
// session light in rust instead. Escape or a click on the dark dismisses it.
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
@@ -32,16 +36,9 @@ PanelWindow {
|
|||||||
|
|
||||||
property int selectedAction: 0
|
property int selectedAction: 0
|
||||||
|
|
||||||
function focusSelectedAction(): void {
|
|
||||||
const entry = buttonRepeater.itemAt(root.selectedAction);
|
|
||||||
if (entry && entry.children.length > 0)
|
|
||||||
entry.children[0].forceActiveFocus(Qt.TabFocusReason);
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveSelection(delta: int): void {
|
function moveSelection(delta: int): void {
|
||||||
root.selectedAction = (root.selectedAction + delta + root.actionModel.length)
|
root.selectedAction = (root.selectedAction + delta + root.actionModel.length)
|
||||||
% root.actionModel.length;
|
% root.actionModel.length;
|
||||||
root.focusSelectedAction();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property var actionModel: [
|
readonly property var actionModel: [
|
||||||
@@ -53,6 +50,11 @@ PanelWindow {
|
|||||||
{ label: "Shut Down", icon: "", danger: true, act: "shutdown" }
|
{ label: "Shut Down", icon: "", danger: true, act: "shutdown" }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: hostFile
|
||||||
|
path: "/etc/hostname"
|
||||||
|
}
|
||||||
|
|
||||||
onOpenChanged: {
|
onOpenChanged: {
|
||||||
if (open) {
|
if (open) {
|
||||||
PopupCoordinator.dismissAll();
|
PopupCoordinator.dismissAll();
|
||||||
@@ -64,128 +66,155 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------- backdrop
|
// ------------------------------------------------------------- backdrop
|
||||||
|
// The desktop stays faintly there behind the dark, deepest on the left
|
||||||
|
// where the menu stands.
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: backdrop
|
id: backdrop
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: Theme.alpha(Theme.base, 0.9)
|
|
||||||
opacity: 0
|
opacity: 0
|
||||||
|
gradient: Gradient {
|
||||||
|
orientation: Gradient.Horizontal
|
||||||
|
GradientStop { position: 0.0; color: Theme.alpha(Theme.ink, 0.97) }
|
||||||
|
GradientStop { position: 0.5; color: Theme.alpha(Theme.ink, 0.93) }
|
||||||
|
GradientStop { position: 1.0; color: Theme.alpha(Theme.ink, 0.86) }
|
||||||
|
}
|
||||||
|
|
||||||
TapHandler {
|
TapHandler {
|
||||||
onTapped: Actions.closePowerMenu()
|
onTapped: Actions.closePowerMenu()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grid and column, shared with the lock screen.
|
// ---------------------------------------------------------------- group
|
||||||
P5Backdrop {
|
|
||||||
id: ground
|
|
||||||
anchors.fill: parent
|
|
||||||
reveal: 0
|
|
||||||
stripeOpacity: 0.05
|
|
||||||
wedgeOpacity: 0.35
|
|
||||||
wedgeSpan: 0.34
|
|
||||||
// The live desktop is still visible behind this, so a dot wash would be
|
|
||||||
// laid over other people's windows rather than over a ground of our
|
|
||||||
// own. The lock owns the halftone; here it just muddies things.
|
|
||||||
halftone: false
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------- title
|
|
||||||
Column {
|
Column {
|
||||||
id: title
|
id: group
|
||||||
anchors.left: parent.left
|
x: root.width * 0.14
|
||||||
anchors.leftMargin: root.width * 0.12
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.verticalCenterOffset: -60
|
width: 340
|
||||||
spacing: -14
|
|
||||||
opacity: 0
|
opacity: 0
|
||||||
|
|
||||||
SplitText {
|
transform: Translate { id: drift }
|
||||||
text: "GLORY"
|
|
||||||
pixelSize: Theme.fsMega
|
|
||||||
split: 4
|
|
||||||
}
|
|
||||||
|
|
||||||
SplitText {
|
Porthole {
|
||||||
text: "TO"
|
size: 26
|
||||||
pixelSize: Theme.fsMega
|
|
||||||
color: Theme.accent
|
|
||||||
split: 4
|
|
||||||
}
|
|
||||||
|
|
||||||
SplitText {
|
|
||||||
text: "MANKIND."
|
|
||||||
pixelSize: Theme.fsMega
|
|
||||||
split: 4
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "— YORHA"
|
topPadding: Theme.padM
|
||||||
color: Theme.primary
|
text: "GLORY TO MANKIND."
|
||||||
font.family: Theme.fontMono
|
color: Theme.accent
|
||||||
font.pixelSize: Theme.fsSmall
|
font.family: Theme.fontDisplay
|
||||||
font.letterSpacing: 5
|
font.pixelSize: Theme.fsHuge
|
||||||
topPadding: 24
|
font.weight: Font.Light
|
||||||
|
font.letterSpacing: 6
|
||||||
renderType: Text.NativeRendering
|
renderType: Text.NativeRendering
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------- actions
|
Text {
|
||||||
Column {
|
topPadding: Theme.padS
|
||||||
id: actions
|
leftPadding: 2
|
||||||
anchors.right: parent.right
|
text: ["YORHA", (Quickshell.env("USER") || "user") + "@" + hostFile.text().trim(),
|
||||||
anchors.rightMargin: root.width * 0.12
|
Sys.uptime ? "UP " + Sys.uptime : ""].filter(t => t !== "").join(" · ")
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
color: Theme.muted
|
||||||
spacing: Theme.padM
|
font.family: Theme.fontMono
|
||||||
|
font.pixelSize: Theme.fsMicro
|
||||||
|
font.letterSpacing: 2
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
}
|
||||||
|
|
||||||
// The widest slab in the stack. Fixed rather than derived so the ragged
|
Item { width: 1; height: Theme.padXL }
|
||||||
// x offsets below cannot feed back into the Column's own width.
|
|
||||||
readonly property int stackWidth: 312
|
|
||||||
width: stackWidth
|
|
||||||
|
|
||||||
|
// ------------------------------------------------------------ menu
|
||||||
Repeater {
|
Repeater {
|
||||||
id: buttonRepeater
|
id: buttonRepeater
|
||||||
|
|
||||||
model: root.actionModel
|
model: root.actionModel
|
||||||
|
|
||||||
// A carrier item owns the staggered slide so the button keeps its
|
|
||||||
// own hover transform.
|
|
||||||
Item {
|
Item {
|
||||||
id: entry
|
id: entry
|
||||||
required property var modelData
|
required property var modelData
|
||||||
required property int index
|
required property int index
|
||||||
|
|
||||||
readonly property int slabWidth: actions.stackWidth
|
readonly property bool selected: root.selectedAction === entry.index
|
||||||
|
readonly property color tone: entry.modelData.danger ? Theme.alarm : Theme.accent
|
||||||
width: entry.slabWidth
|
|
||||||
height: 52
|
|
||||||
|
|
||||||
|
width: group.width
|
||||||
|
height: 44
|
||||||
opacity: 0
|
opacity: 0
|
||||||
transform: [
|
|
||||||
Translate { id: slide; x: 90 },
|
|
||||||
Rotation {
|
|
||||||
id: tilt
|
|
||||||
angle: -3
|
|
||||||
origin.x: entry.slabWidth / 2
|
|
||||||
origin.y: 26
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
P5Button {
|
transform: Translate { id: rise; y: 6 }
|
||||||
id: actionButton
|
|
||||||
anchors.fill: parent
|
// The divider after the third row: what pauses the session
|
||||||
text: entry.modelData.label
|
// above, what ends it below.
|
||||||
icon: entry.modelData.icon
|
Rectangle {
|
||||||
destructive: entry.modelData.danger
|
visible: entry.index === 3
|
||||||
selected: root.selectedAction === entry.index
|
anchors.top: parent.top
|
||||||
accent: entry.modelData.danger ? Theme.alarm : Theme.primary
|
x: Theme.padL
|
||||||
lean: Theme.skew
|
width: parent.width - Theme.padL * 2
|
||||||
onClicked: root.invoke(entry.modelData.act)
|
height: 1
|
||||||
onHoveredChanged: if (hovered) root.selectedAction = entry.index
|
color: Theme.alpha(Theme.outline, 0.35)
|
||||||
onActiveFocusChanged: if (actionButton.activeFocus) root.selectedAction = entry.index
|
|
||||||
Keys.forwardTo: [keyController]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Staggered entry, one button after another.
|
Rectangle {
|
||||||
|
id: slab
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.topMargin: 3
|
||||||
|
anchors.bottomMargin: 3
|
||||||
|
radius: height / 2
|
||||||
|
color: entry.selected ? entry.tone : "transparent"
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation { duration: Theme.durBase; easing.type: Easing.OutCubic }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: marker
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.padL
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: Theme.marker
|
||||||
|
height: Theme.marker
|
||||||
|
radius: width / 2
|
||||||
|
color: entry.selected ? Theme.ink
|
||||||
|
: (entry.modelData.danger ? Theme.alpha(Theme.alarm, 0.6) : Theme.overlay)
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon {
|
||||||
|
id: glyph
|
||||||
|
anchors.left: marker.right
|
||||||
|
anchors.leftMargin: Theme.padM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
// Fixed width so the labels line up whatever the glyph.
|
||||||
|
width: 20
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
text: entry.modelData.icon
|
||||||
|
color: entry.selected ? Theme.ink : Theme.muted
|
||||||
|
font.pixelSize: Theme.fsLarge
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.left: glyph.right
|
||||||
|
anchors.leftMargin: Theme.padM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: entry.modelData.label.toUpperCase()
|
||||||
|
color: entry.selected ? Theme.ink : Theme.text
|
||||||
|
font.family: Theme.fontDisplay
|
||||||
|
font.pixelSize: Theme.fsBody
|
||||||
|
font.weight: Theme.weightDisplay
|
||||||
|
font.letterSpacing: Theme.tracking * 1.5
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
}
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onHoveredChanged: if (hovered) root.selectedAction = entry.index
|
||||||
|
}
|
||||||
|
|
||||||
|
TapHandler {
|
||||||
|
onTapped: root.invoke(entry.modelData.act)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Staggered entry: each row resolves a beat after the last.
|
||||||
Connections {
|
Connections {
|
||||||
target: root
|
target: root
|
||||||
function onOpenChanged() {
|
function onOpenChanged() {
|
||||||
@@ -196,48 +225,31 @@ PanelWindow {
|
|||||||
|
|
||||||
SequentialAnimation {
|
SequentialAnimation {
|
||||||
id: entryAnim
|
id: entryAnim
|
||||||
PauseAnimation { duration: 40 + entry.index * 55 }
|
PauseAnimation { duration: 120 + entry.index * 40 }
|
||||||
ParallelAnimation {
|
ParallelAnimation {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
target: entry; property: "opacity"
|
target: entry; property: "opacity"
|
||||||
to: 1; duration: Theme.durBase
|
to: 1; duration: Theme.durSlow; easing.type: Easing.OutCubic
|
||||||
}
|
}
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
target: slide; property: "x"
|
target: rise; property: "y"
|
||||||
from: 90; to: 0
|
from: 6; to: 0; duration: Theme.durSlow; easing.type: Easing.OutCubic
|
||||||
duration: Theme.durSlow
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
// Slabs land out of true and snap square. Sliding alone
|
|
||||||
// reads like a slide deck; the tilt is what makes it
|
|
||||||
// land like a card being thrown down. Same direction
|
|
||||||
// every time — the motion is the flourish, the geometry
|
|
||||||
// stays disciplined.
|
|
||||||
NumberAnimation {
|
|
||||||
target: tilt; property: "angle"
|
|
||||||
from: -3; to: 0
|
|
||||||
duration: Theme.durSlow
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
easing.overshoot: 2.2
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Hint line.
|
Text {
|
||||||
Text {
|
topPadding: Theme.padXL
|
||||||
anchors.bottom: parent.bottom
|
leftPadding: Theme.padL
|
||||||
anchors.bottomMargin: 40
|
text: "↑ ↓ SELECT ENTER CONFIRM ESC CANCEL"
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
color: Theme.overlay
|
||||||
text: "↑ ↓ SELECT ENTER CONFIRM ESC CANCEL"
|
font.family: Theme.fontMono
|
||||||
color: Theme.muted
|
font.pixelSize: Theme.fsMicro
|
||||||
font.family: Theme.fontMono
|
font.letterSpacing: 2
|
||||||
font.pixelSize: Theme.fsMicro
|
renderType: Text.NativeRendering
|
||||||
font.letterSpacing: 4
|
}
|
||||||
opacity: backdrop.opacity
|
|
||||||
renderType: Text.NativeRendering
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function invoke(what: string) {
|
function invoke(what: string) {
|
||||||
@@ -280,16 +292,15 @@ PanelWindow {
|
|||||||
id: openAnim
|
id: openAnim
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
target: backdrop; property: "opacity"
|
target: backdrop; property: "opacity"
|
||||||
to: 1; duration: Theme.durBase; easing.type: Easing.OutQuad
|
to: 1; duration: Theme.durSlow; easing.type: Easing.OutCubic
|
||||||
}
|
|
||||||
// One number now drives the slide, the stripe opacity and the wedge.
|
|
||||||
NumberAnimation {
|
|
||||||
target: ground; property: "reveal"
|
|
||||||
from: 0; to: 1; duration: Theme.durLazy; easing.type: Easing.OutExpo
|
|
||||||
}
|
}
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
target: title; property: "opacity"
|
target: group; property: "opacity"
|
||||||
to: 1; duration: Theme.durSlow
|
to: 1; duration: Theme.durLazy; easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
NumberAnimation {
|
||||||
|
target: drift; property: "y"
|
||||||
|
from: 14; to: 0; duration: Theme.durLazy; easing.type: Easing.OutQuint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,14 +308,10 @@ PanelWindow {
|
|||||||
id: closeAnim
|
id: closeAnim
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
target: backdrop; property: "opacity"
|
target: backdrop; property: "opacity"
|
||||||
to: 0; duration: Theme.durFast
|
to: 0; duration: Theme.durBase; easing.type: Easing.InCubic
|
||||||
}
|
}
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
target: ground; property: "reveal"
|
target: group; property: "opacity"
|
||||||
to: 0; duration: Theme.durFast
|
|
||||||
}
|
|
||||||
NumberAnimation {
|
|
||||||
target: title; property: "opacity"
|
|
||||||
to: 0; duration: Theme.durFast
|
to: 0; duration: Theme.durFast
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user