16 Commits
Author SHA1 Message Date
nvrl e9735d1129 feat(quickshell): "Take your time." glows and flickers
A new GlowText component lights a line from inside: a soft two-layer
halo that breathes slowly, and every 4-14 seconds a short, uneven
stutter like a tube catching. Only opacity and the halo move, so the
glyphs stay on the pixel grid. The lock screen and the power menu use
it; the power menu's stops while it is closed.
2026-10-04 23:12:50 +02:00
nvrl c36a74e861 feat(quickshell): idle chain, drifting wallpaper, and a few fixes
- Idle is handled in the shell instead of hypridle: the screen dims
  slowly at 4 minutes, locks at 5, powers down at 6, and suspends at 15
  on battery. Idle inhibitors hold every stage off; Super+Shift+I does
  the same by hand and puts a cup on the rail.
- The shell draws the wallpaper (backdrop/), so hyprpaper is gone. The
  picture is drawn a little larger than the screen and drifts with the
  workspace a beat after the windows, and dims a shade while windows are
  open. The path lives in quickshell/wallpaper.
- The lock screen and power menu say "Take your time." instead of the
  NieR slogan.
- hyprpaper is no longer launched twice, the media module keeps the
  player that played last instead of an empty browser tab, and grouped
  windows show thin untitled tabs with their place in the group (2/3)
  in the bar title.
- qt6ct uses Darkly; the package list gains adw-gtk-theme and darkly-bin.
2026-10-04 23:10:19 +02:00
nvrl 864420281b chore: drop waybar, hyprlock and the catppuccin leftovers
None of these are referenced any more: the shell replaced waybar and
hyprlock, and btop, zathura and the rest now use the YoRHa themes. The
hyprpaper unit is disabled because the shell draws the wallpaper.
2026-10-04 23:10:08 +02:00
nvrlandClaude Opus 5.5 b86e584b02 fix(quickshell): keep text sharp under zoom and on the lock screen
Native-rendered glyphs are rasterised at rest size, so anything moved
with `scale` stretched a bitmap and came out blurred and stepped.

- Icons render as curves, so their pulse and bump stay smooth.
- The lock face is laid out at uiScale instead of being drawn at
  1080p size and scaled by up to 1.3, and it sits on whole pixels. Its
  native labels now land on the pixel grid.
- The OSD rises into place instead of scaling in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 21:58:00 +02:00
nvrlandClaude Opus 5.5 f1d16e5175 theme: zathura takes the YoRHa palette
A local zathura/yorha replaces the catppuccin include. Pages are
recoloured onto the hull with bone text while images keep their
colours, the index and completion select with the light slab, and
search hits use a bone wash with the current hit in sand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 21:48:28 +02:00
nvrlandClaude Opus 5.5 485ee5c0ca theme: GTK and Qt take the YoRHa palette
GTK moves from Catppuccin Mocha to adw-gtk3-dark, recoloured through the
libadwaita named colours in gtk-3.0/gtk.css and as CSS variables in
gtk-4.0/gtk.css, so Nautilus and other libadwaita apps follow too. The
gtk-4.0 symlinks into the Catppuccin theme are gone, since libadwaita
would load its gtk-dark.css over the overrides.

Qt gets qt6ct/colors/yorha.conf: the same palette in QPalette roles,
with the light slab as the highlight and ink as its text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 21:17:52 +02:00
nvrlandClaude Opus 5.5 9af24ccf87 theme: workspaces drift behind the hull instead of over the bar
The hull moves to the top layer, with its transparent opening and empty input
region, so windows sliding out of the opening during a workspace switch pass
behind the frame like things behind a porthole rim rather than drawing over
the bar. A layer rule keeps the bar and the rail above it on that layer.

The animation tree is redone around that: workspaces drift 18% vertically
and fade on a slow, jolt-free "drift" curve; the SUPER+S scratchpad slides in
whole from under the bar, the same gesture as the dashboard and popouts;
windows and layers keep their calm resolve and fade.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 21:13:11 +02:00
nvrlandClaude Opus 5.5 63a910bf14 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>
2026-10-04 21:09:07 +02:00
nvrlandClaude Opus 5.5 a8c19961e2 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>
2026-10-04 20:56:43 +02:00
nvrlandClaude Opus 5.5 d6de04eaaf feat(quickshell): notifications as a NieR message log in a corner drawer
Notifications were still Persona: separate floating cards with an urgency
slash down the leading edge, action chips that filled with the accent on
hover, and a 60px shove in from the right.

NotifCard is now laid out like an entry in NieR's message log: a square
urgency marker and the sender's name running into a hairline, the summary
and body set in from the marker, and hairline action buttons with their
own small marker that lift on hover instead of inverting. It draws no
panel; rest is bare and hover lifts the row onto the surface tone.
Critical entries take the red into the marker and the sender line only.

The toast stack is one hull drawer docked into the opening's top-right
corner — off the bar through one fillet, onto the rail through the other,
the same construction as a corner-docked popout. It grows with its
entries and slides back behind the frame when the last one goes. Each
entry's countdown is a hairline along its foot, and new entries fade in
place. The history popout lists the same entries flush.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 20:48:30 +02:00
nvrlandClaude Opus 5.5 ae6539218e theme: strip the remaining Persona punch from Hyprland and the popouts
Hyprland:
- Windows round at 8px, concentric with the porthole's 18px corners at
  the 10px gap, instead of square corners inside a rounded opening.
- Group tabs are solid hull slabs; the titles used to float over the
  wallpaper. The current tab is lifted a step with its title in the light.
- Motion rewritten to glide and settle: no overshoot curve, gentler
  popin, workspaces slide-fade a short way instead of sweeping the whole
  screen, and layers only fade so they never stack on quickshell's own
  slides.

Quickshell:
- Album art in the bar and the media popout is square in a hairline frame,
  not masked to a leaning chamfered shape; the accent slab under the
  popout art is gone, and a track change dips the title instead of
  tilting the art.
- The bar's equaliser is a five-hairline signal meter that rests as dots.
- Segment meters (system, battery, OSD) are 4px rows instead of 10-12px
  blocks; the toast fuse is a hairline; the clock's accent slab is a
  neutral divider.
- No hover pops: rail icons, tray icons, media transport buttons and
  P5Button no longer scale up or lunge sideways; the icon bump is reduced.

Terminal: ANSI bright black drops to #464d44. TUIs use it as a panel fill,
and at the outline grey it read as a flat light slab.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 20:45:24 +02:00
nvrlandClaude Opus 5.5 a27dd6f93b fix(quickshell): rail popouts no longer land short of the rail
Popouts anchored to their trigger item, and Quickshell maps an anchor
rect given in item coordinates through the item's transform. Rail icons
scale to 1.08 on hover, so a panel placed ~360px left of the icon landed
up to ~29px further left still, leaving a strip of desktop between it and
the rail — by however far the hover scale had got when the dwell timer
fired, which is why it only happened sometimes.

Anchor to the trigger window's content item instead, which never
transforms, and place in window coordinates from the trigger's centre,
which a scale about the centre leaves where it is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 20:38:06 +02:00
nvrlandClaude Opus 5.5 196abc2311 feat(quickshell): popouts grow out of the frame
Hover panels used to float a gap away from their trigger as free-standing
cards. They are now part of the hull: flush against the frame edge they
open from, painted in the hull colour, joined to it through concave
fillets, with the porthole rim carried round their open side, and they
slide out from behind the frame instead of fading and scaling in.

Popout places itself rather than relying on the compositor's slide
adjustment. A panel is centred on its trigger and, if that lands it near
one of the opening's corners, docked into the corner — attached to the
bar and the rail at once — so it never leaves a thin strip of opening
between itself and the rail. The calendar, network and audio panels dock
top-right; notifications dock bottom-right.

PopoutSurface leaves fill, border and corner ticks to the hull when it is
inside a Popout, and keeps them in the launcher and the ask panel. Its
attached fill opacity is 0 rather than a transparent colour, because
P5Panel re-applies fillOpacity to the colour and turned "transparent"
into 97% black.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 20:34:17 +02:00
nvrlandClaude Opus 5.5 ecc7d2884c feat(quickshell): dashboard drawer that drops from the bar
A drawer lowered out of the underside of the bar, painted in the hull
colour and joined to it by two concave fillets so it reads as more of the
hull rather than as a floating card — caelestia's dashboard idea, in the
YoRHa language. Four columns divided by hairlines: operator and clock, the
month with today as an inverted square, the system meters, and the active
player with a hairline timeline and square transport buttons.

Opened by clicking the bar's clock (which used to pin the calendar), by
SUPER+I, or by `qs ipc call shell dashboard`. Escape or moving focus away
closes it through a HyprlandFocusGrab; Actions.dashboardClosedAt keeps the
clock click that cleared the grab from reopening it. The clock's calendar
hover is suppressed while the drawer is down, since it shows the month.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 20:19:30 +02:00
nvrlandClaude Opus 5.5 18091a7109 theme: YoRHa dark — NieR:Automata interface on the space-1 wallpaper
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>
2026-10-04 20:10:53 +02:00
nvrl 5be22d86a9 updated autostart 2026-10-04 19:31:19 +02:00
114 changed files with 3657 additions and 3687 deletions

No files matched your search

+3
View File
@@ -44,3 +44,6 @@ gtk-4.0/*
!qt6ct/
qt6ct/*
!qt6ct/qt6ct.conf
!qt6ct/colors/
qt6ct/colors/*
!qt6ct/colors/yorha.conf
-9
View File
@@ -1,24 +1,15 @@
[submodule "waybar/catppuccin-waybar"]
path = waybar/catppuccin-waybar
url = https://github.com/catppuccin/waybar.git
[submodule "hypr/catppuccin-hyprland"]
path = hypr/catppuccin-hyprland
url = https://github.com/catppuccin/hyprland.git
[submodule "hypr/catppuccin-hyprlock"]
path = hypr/catppuccin-hyprlock
url = https://github.com/catppuccin/hyprlock.git
[submodule "zathura/catppuccin-zathura"]
path = zathura/catppuccin-zathura
url = https://github.com/catppuccin/zathura.git
[submodule "nvim/pack/nvim/start/mini.nvim"]
path = nvim/pack/nvim/start/mini.nvim
url = https://github.com/echasnovski/mini.nvim
[submodule "nvim/pack/nvim/start/nvim-lspconfig"]
path = nvim/pack/nvim/start/nvim-lspconfig
url = https://github.com/neovim/nvim-lspconfig
[submodule "btop/catppuccin-btop"]
path = btop/catppuccin-btop
url = https://github.com/catppuccin/btop.git
[submodule "nvim"]
path = nvim
url = https://git.narl.io/nvrl/nvim-config.git
+3 -3
View File
@@ -13,7 +13,7 @@ position = "None"
dynamic_padding = true
decorations = "None"
## Fully opaque, no blur. Persona surfaces are flat ink on flat ground —
## Fully opaque, no blur. YoRHa surfaces are flat and opaque —
## glass and translucency read as a different design language entirely.
opacity = 1.0
blur = false
@@ -28,7 +28,7 @@ columns = 82
lines = 24
## Blank space added around the window in pixels. Roomier than the old 3/0 —
## the text needs to sit inside the crimson border, not against it.
## the text needs to sit inside the border, not against it.
[window.padding]
x = 12
y = 8
@@ -107,7 +107,7 @@ ipc_socket = true
## GENERAL --------------------------------------------------------
## Import additional configuration files.
import = ["~/.config/alacritty/catppuccin-mocha.toml", "~/.config/alacritty/fonts.toml"]
import = ["~/.config/alacritty/yorha.toml", "~/.config/alacritty/fonts.toml"]
[[keyboard.bindings]]
key = "Return"
+67
View File
@@ -0,0 +1,67 @@
# YoRHa dark — matched to ~/.config/quickshell/config/Theme.qml.
#
# The ground and the text are the space-1 wallpaper's void and sage grey. The
# eight hues are the same few steps of earth NieR's world is painted in —
# rust, moss, sand, dusk — pulled down to sit in that void without glowing.
# Selection and search invert, the way NieR's cursor does.
[colors.primary]
background = "#0f110e"
foreground = "#ccd2cb"
dim_foreground = "#79847a"
bright_foreground = "#eef1ea"
[colors.cursor]
text = "#0f110e"
cursor = "#eef1ea"
[colors.vi_mode_cursor]
text = "#0f110e"
cursor = "#bcc1bf"
[colors.search.matches]
foreground = "#0f110e"
background = "#9fa7a2"
[colors.search.focused_match]
foreground = "#0f110e"
background = "#eef1ea"
[colors.footer_bar]
foreground = "#0f110e"
background = "#9fa7a2"
[colors.hints.start]
foreground = "#0f110e"
background = "#c9b27c"
[colors.hints.end]
foreground = "#0f110e"
background = "#9fa7a2"
[colors.selection]
text = "#0f110e"
background = "#bcc1bf"
[colors.normal]
black = "#20241f"
red = "#c8684f"
green = "#8fa37e"
yellow = "#c9b27c"
blue = "#7f9aa8"
magenta = "#a48ba0"
cyan = "#86a8a0"
white = "#bcc1bf"
# Bright black doubles as a panel fill in TUIs (Claude Code's side pane, fzf
# previews), so it sits one step under the outline grey rather than at it:
# light enough for comments, dark enough not to read as a slab.
[colors.bright]
black = "#464d44"
red = "#d98068"
green = "#a5b893"
yellow = "#d8c491"
blue = "#96afbc"
magenta = "#b8a0b4"
cyan = "#9cbdb4"
white = "#eef1ea"
+1 -1
View File
@@ -2,7 +2,7 @@
#* 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"
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.
theme_background = false
-83
View File
@@ -1,83 +0,0 @@
# Main background, empty for terminal default, need to be empty if you want transparent background
theme[main_bg]="#1e1e2e"
# Main text color
theme[main_fg]="#cdd6f4"
# Title color for boxes
theme[title]="#cdd6f4"
# Highlight color for keyboard shortcuts
theme[hi_fg]="#89b4fa"
# Background color of selected item in processes box
theme[selected_bg]="#45475a"
# Foreground color of selected item in processes box
theme[selected_fg]="#89b4fa"
# Color of inactive/disabled text
theme[inactive_fg]="#7f849c"
# Color of text appearing on top of graphs, i.e uptime and current network graph scaling
theme[graph_text]="#f5e0dc"
# Background color of the percentage meters
theme[meter_bg]="#45475a"
# Misc colors for processes box including mini cpu graphs, details memory graph and details status text
theme[proc_misc]="#f5e0dc"
# CPU, Memory, Network, Proc box outline colors
theme[cpu_box]="#cba6f7" #Mauve
theme[mem_box]="#a6e3a1" #Green
theme[net_box]="#eba0ac" #Maroon
theme[proc_box]="#89b4fa" #Blue
# Box divider line and small boxes line color
theme[div_line]="#6c7086"
# Temperature graph color (Green -> Yellow -> Red)
theme[temp_start]="#a6e3a1"
theme[temp_mid]="#f9e2af"
theme[temp_end]="#f38ba8"
# CPU graph colors (Teal -> Lavender)
theme[cpu_start]="#94e2d5"
theme[cpu_mid]="#74c7ec"
theme[cpu_end]="#b4befe"
# Mem/Disk free meter (Mauve -> Lavender -> Blue)
theme[free_start]="#cba6f7"
theme[free_mid]="#b4befe"
theme[free_end]="#89b4fa"
# Mem/Disk cached meter (Sapphire -> Lavender)
theme[cached_start]="#74c7ec"
theme[cached_mid]="#89b4fa"
theme[cached_end]="#b4befe"
# Mem/Disk available meter (Peach -> Red)
theme[available_start]="#fab387"
theme[available_mid]="#eba0ac"
theme[available_end]="#f38ba8"
# Mem/Disk used meter (Green -> Sky)
theme[used_start]="#a6e3a1"
theme[used_mid]="#94e2d5"
theme[used_end]="#89dceb"
# Download graph colors (Peach -> Red)
theme[download_start]="#fab387"
theme[download_mid]="#eba0ac"
theme[download_end]="#f38ba8"
# Upload graph colors (Green -> Sky)
theme[upload_start]="#a6e3a1"
theme[upload_mid]="#94e2d5"
theme[upload_end]="#89dceb"
# Process box color gradient for threads, mem and cpu usage (Sapphire -> Mauve)
theme[process_start]="#74c7ec"
theme[process_mid]="#b4befe"
theme[process_end]="#cba6f7"
+60
View File
@@ -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"
+10 -10
View File
@@ -1,22 +1,22 @@
[global]
corner_radius = 4
corner_radius = 0
transparency = 90
frame_color = "#89b4fa"
frame_color = "#545c52"
frame_width = 1
separator_color= frame
highlight = "#cba6f7"
highlight = "#eef1ea"
[urgency_low]
background = "#1e1e2e"
foreground = "#cdd6f4"
background = "#0f110e"
foreground = "#ccd2cb"
timeout = 4
[urgency_normal]
background = "#1e1e2e"
foreground = "#cdd6f4"
background = "#0f110e"
foreground = "#ccd2cb"
timeout = 6
[urgency_critical]
background = "#1e1e2e"
foreground = "#cdd6f4"
frame_color = "#f38ba8"
background = "#0f110e"
foreground = "#ccd2cb"
frame_color = "#c8684f"
-52
View File
@@ -1,52 +0,0 @@
# Mocha — matched to ~/.config/quickshell/config/Theme.qml,
# ~/.config/alacritty/catppuccin-mocha.toml and ~/.config/nvim/lua/takemi.lua.
#
# Catppuccin Mocha's own fish theme (themes/Catppuccin Mocha.theme), with the
# selection and pager rows carried over from the Takemi layout: a solid accent
# block with crust punched out of it, rather than a tinted surface. The shell
# selects with a blue slab and marks with mauve; the prompt should not
# disagree.
#
# Note that `fish_config` writing a theme from the web UI will recreate
# fish_frozen_theme.fish, which sorts before this file and so would lose.
set --global fish_color_normal cdd6f4
set --global fish_color_command 89b4fa
set --global fish_color_keyword f38ba8
set --global fish_color_param f2cdcd
set --global fish_color_option a6e3a1
set --global fish_color_quote a6e3a1
set --global fish_color_escape eba0ac
set --global fish_color_operator f5c2e7
set --global fish_color_redirection f5c2e7
set --global fish_color_end fab387
set --global fish_color_comment 7f849c --italics
set --global fish_color_error f38ba8 --bold
set --global fish_color_cancel f38ba8
set --global fish_color_gray 6c7086
set --global fish_color_autosuggestion 6c7086
set --global fish_color_selection 11111b --background=89b4fa
set --global fish_color_search_match 11111b --background=cba6f7
set --global fish_color_match 11111b --background=89b4fa
set --global fish_color_history_current --bold
set --global fish_color_valid_path --underline
set --global fish_color_cwd f9e2af --bold
set --global fish_color_cwd_root f38ba8 --bold
set --global fish_color_user 94e2d5
set --global fish_color_host 89b4fa --bold
set --global fish_color_host_remote a6e3a1
set --global fish_color_status f38ba8
set --global fish_pager_color_progress 6c7086 --background=313244
set --global fish_pager_color_background
set --global fish_pager_color_prefix cba6f7 --bold
set --global fish_pager_color_completion cdd6f4
set --global fish_pager_color_description 6c7086
set --global fish_pager_color_secondary_background --background=181825
set --global fish_pager_color_secondary_completion cdd6f4
set --global fish_pager_color_secondary_description 6c7086
set --global fish_pager_color_secondary_prefix cba6f7 --bold
set --global fish_pager_color_selected_background --background=89b4fa
set --global fish_pager_color_selected_completion 11111b
set --global fish_pager_color_selected_description 11111b
set --global fish_pager_color_selected_prefix 11111b --bold
+51
View File
@@ -0,0 +1,51 @@
# YoRHa dark — matched to ~/.config/quickshell/config/Theme.qml and
# ~/.config/alacritty/yorha.toml.
#
# Commands are the planet light, arguments the sage text, strings moss,
# anything wrong the hull red. Selection and the pager's current row invert —
# a light slab with the void punched out of it, the same cursor the shell
# draws.
#
# Note that `fish_config` writing a theme from the web UI will recreate
# fish_frozen_theme.fish, which sorts before this file and so would lose.
set --global fish_color_normal ccd2cb
set --global fish_color_command eef1ea --bold
set --global fish_color_keyword c9b27c
set --global fish_color_param bcc1bf
set --global fish_color_option 9cbdb4
set --global fish_color_quote 8fa37e
set --global fish_color_escape c9b27c
set --global fish_color_operator 9fa7a2
set --global fish_color_redirection 9fa7a2
set --global fish_color_end 79847a
set --global fish_color_comment 545c52
set --global fish_color_error c8684f --bold
set --global fish_color_cancel c8684f
set --global fish_color_gray 545c52
set --global fish_color_autosuggestion 545c52
set --global fish_color_selection 0f110e --background=bcc1bf
set --global fish_color_search_match 0f110e --background=eef1ea
set --global fish_color_match 0f110e --background=bcc1bf
set --global fish_color_history_current --bold
set --global fish_color_valid_path --underline
set --global fish_color_cwd c9b27c --bold
set --global fish_color_cwd_root c8684f --bold
set --global fish_color_user 9cbdb4
set --global fish_color_host eef1ea --bold
set --global fish_color_host_remote 8fa37e
set --global fish_color_status c8684f
set --global fish_pager_color_progress 79847a --background=161915
set --global fish_pager_color_background
set --global fish_pager_color_prefix eef1ea --bold
set --global fish_pager_color_completion ccd2cb
set --global fish_pager_color_description 79847a
set --global fish_pager_color_secondary_background --background=0c0d0b
set --global fish_pager_color_secondary_completion ccd2cb
set --global fish_pager_color_secondary_description 79847a
set --global fish_pager_color_secondary_prefix eef1ea --bold
set --global fish_pager_color_selected_background --background=eef1ea
set --global fish_pager_color_selected_completion 0f110e
set --global fish_pager_color_selected_description 0f110e
set --global fish_pager_color_selected_prefix 0f110e --bold
+57 -4
View File
@@ -1,4 +1,57 @@
/* Catppuccin Mocha Blue carries the whole GTK3 palette itself, so there is
* nothing to override here — the Takemi branch only needed this file because
* it was painting Fluent's greys black. Kept as a hook for the next time the
* rice disagrees with stock Catppuccin. */
/* YoRHa colours for adw-gtk3 — kept in step with
* ~/.config/quickshell/config/Theme.qml and gtk-4.0/gtk.css.
*
* adw-gtk3 draws the libadwaita shapes (soft corners, flat cards) and takes
* every surface from these named colours, so the whole palette lives here.
* Selection is the shell's light slab with ink text; red only for destructive
* and failed. */
@define-color accent_color #eef1ea;
@define-color accent_bg_color #bcc1bf;
@define-color accent_fg_color #090a08;
@define-color destructive_color #c8684f;
@define-color destructive_bg_color #c8684f;
@define-color destructive_fg_color #090a08;
@define-color error_color #c8684f;
@define-color error_bg_color #c8684f;
@define-color error_fg_color #090a08;
@define-color warning_color #c9b27c;
@define-color warning_bg_color #c9b27c;
@define-color warning_fg_color #090a08;
@define-color success_color #8fa37e;
@define-color success_bg_color #8fa37e;
@define-color success_fg_color #090a08;
@define-color window_bg_color #0f110e;
@define-color window_fg_color #ccd2cb;
@define-color view_bg_color #0c0d0b;
@define-color view_fg_color #ccd2cb;
@define-color headerbar_bg_color #161915;
@define-color headerbar_fg_color #ccd2cb;
@define-color headerbar_border_color #2a2f28;
@define-color headerbar_backdrop_color #0f110e;
@define-color headerbar_shade_color rgba(0, 0, 0, 0.36);
@define-color sidebar_bg_color #161915;
@define-color sidebar_fg_color #ccd2cb;
@define-color sidebar_backdrop_color #0f110e;
@define-color sidebar_shade_color rgba(0, 0, 0, 0.36);
@define-color secondary_sidebar_bg_color #0f110e;
@define-color secondary_sidebar_fg_color #ccd2cb;
@define-color secondary_sidebar_backdrop_color #0c0d0b;
@define-color card_bg_color #161915;
@define-color card_fg_color #ccd2cb;
@define-color card_shade_color rgba(0, 0, 0, 0.36);
@define-color dialog_bg_color #161915;
@define-color dialog_fg_color #ccd2cb;
@define-color popover_bg_color #20241f;
@define-color popover_fg_color #ccd2cb;
@define-color popover_shade_color rgba(0, 0, 0, 0.36);
@define-color thumbnail_bg_color #20241f;
@define-color thumbnail_fg_color #ccd2cb;
@define-color shade_color rgba(0, 0, 0, 0.36);
@define-color scrollbar_outline_color rgba(0, 0, 0, 0.5);
+1 -1
View File
@@ -1,5 +1,5 @@
[Settings]
gtk-theme-name=catppuccin-mocha-blue-standard+default
gtk-theme-name=adw-gtk3-dark
gtk-icon-theme-name=Papirus-Dark
gtk-font-name=Adwaita Sans 11
gtk-cursor-theme-name=Bibata-Modern-Classic
+95 -4
View File
@@ -1,4 +1,95 @@
/* Catppuccin Mocha Blue, straight from the installed theme. The Takemi branch
* overrode Fluent's greys with black here; Mocha needs no override — its own
* surfaces are the palette the rest of the rice is built on. */
@import url("/usr/share/themes/catppuccin-mocha-blue-standard+default/gtk-4.0/gtk.css");
/* YoRHa colours for libadwaita and adw-gtk3 — kept in step with
* ~/.config/quickshell/config/Theme.qml and gtk-3.0/gtk.css.
*
* libadwaita reads its palette from CSS variables; the named colours below
* the variables cover GTK4 apps that still go through the adw-gtk3 theme. */
:root {
--accent-color: #eef1ea;
--accent-bg-color: #bcc1bf;
--accent-fg-color: #090a08;
--destructive-color: #c8684f;
--destructive-bg-color: #c8684f;
--destructive-fg-color: #090a08;
--error-color: #c8684f;
--error-bg-color: #c8684f;
--error-fg-color: #090a08;
--warning-color: #c9b27c;
--warning-bg-color: #c9b27c;
--warning-fg-color: #090a08;
--success-color: #8fa37e;
--success-bg-color: #8fa37e;
--success-fg-color: #090a08;
--window-bg-color: #0f110e;
--window-fg-color: #ccd2cb;
--view-bg-color: #0c0d0b;
--view-fg-color: #ccd2cb;
--headerbar-bg-color: #161915;
--headerbar-fg-color: #ccd2cb;
--headerbar-border-color: #2a2f28;
--headerbar-backdrop-color: #0f110e;
--headerbar-shade-color: rgba(0, 0, 0, 0.36);
--sidebar-bg-color: #161915;
--sidebar-fg-color: #ccd2cb;
--sidebar-backdrop-color: #0f110e;
--sidebar-shade-color: rgba(0, 0, 0, 0.36);
--secondary-sidebar-bg-color: #0f110e;
--secondary-sidebar-fg-color: #ccd2cb;
--secondary-sidebar-backdrop-color: #0c0d0b;
--card-bg-color: #161915;
--card-fg-color: #ccd2cb;
--card-shade-color: rgba(0, 0, 0, 0.36);
--dialog-bg-color: #161915;
--dialog-fg-color: #ccd2cb;
--popover-bg-color: #20241f;
--popover-fg-color: #ccd2cb;
--popover-shade-color: rgba(0, 0, 0, 0.36);
--thumbnail-bg-color: #20241f;
--thumbnail-fg-color: #ccd2cb;
--shade-color: rgba(0, 0, 0, 0.36);
--scrollbar-outline-color: rgba(0, 0, 0, 0.5);
}
@define-color accent_color #eef1ea;
@define-color accent_bg_color #bcc1bf;
@define-color accent_fg_color #090a08;
@define-color destructive_color #c8684f;
@define-color destructive_bg_color #c8684f;
@define-color destructive_fg_color #090a08;
@define-color error_color #c8684f;
@define-color error_bg_color #c8684f;
@define-color error_fg_color #090a08;
@define-color warning_color #c9b27c;
@define-color warning_bg_color #c9b27c;
@define-color warning_fg_color #090a08;
@define-color success_color #8fa37e;
@define-color success_bg_color #8fa37e;
@define-color success_fg_color #090a08;
@define-color window_bg_color #0f110e;
@define-color window_fg_color #ccd2cb;
@define-color view_bg_color #0c0d0b;
@define-color view_fg_color #ccd2cb;
@define-color headerbar_bg_color #161915;
@define-color headerbar_fg_color #ccd2cb;
@define-color headerbar_border_color #2a2f28;
@define-color headerbar_backdrop_color #0f110e;
@define-color headerbar_shade_color rgba(0, 0, 0, 0.36);
@define-color sidebar_bg_color #161915;
@define-color sidebar_fg_color #ccd2cb;
@define-color sidebar_backdrop_color #0f110e;
@define-color sidebar_shade_color rgba(0, 0, 0, 0.36);
@define-color secondary_sidebar_bg_color #0f110e;
@define-color secondary_sidebar_fg_color #ccd2cb;
@define-color secondary_sidebar_backdrop_color #0c0d0b;
@define-color card_bg_color #161915;
@define-color card_fg_color #ccd2cb;
@define-color card_shade_color rgba(0, 0, 0, 0.36);
@define-color dialog_bg_color #161915;
@define-color dialog_fg_color #ccd2cb;
@define-color popover_bg_color #20241f;
@define-color popover_fg_color #ccd2cb;
@define-color popover_shade_color rgba(0, 0, 0, 0.36);
@define-color thumbnail_bg_color #20241f;
@define-color thumbnail_fg_color #ccd2cb;
@define-color shade_color rgba(0, 0, 0, 0.36);
@define-color scrollbar_outline_color rgba(0, 0, 0, 0.5);
+1 -1
View File
@@ -1,5 +1,5 @@
[Settings]
gtk-theme-name=catppuccin-mocha-blue-standard+default
gtk-theme-name=adw-gtk3-dark
gtk-icon-theme-name=Papirus-Dark
gtk-font-name=Adwaita Sans 11
gtk-cursor-theme-name=Bibata-Modern-Classic
-545
View File
@@ -1,545 +0,0 @@
# For more information visit https://wiki.hyprland.org/Hypr-Ecosystem/hyprlock/
source = ~/.config/hypr/catppuccin-hyprland/themes/mocha.conf
$scrPath = ~/.config/hypr/hyprlock/
$imgPath = ~/.config/hypr/hyprlock/assets/
$USER = narl # Your Alias
$host = uname -n #Your Alias
$wifi-mode = false # Change the WiFi output. true = show SSID, false = Connected.
$bt-mode = false # Change the Bluetooth output. true = show Device name, false = Connected.
# Default geolocation provider is IP-API.COM, hardcode it if the location does not match the current location.
# Hardcode your location (If hardcode does not recognize your City, change it to IP Geolocation provider in weatherinfo.sh)
$CITY =
$COUNTRY =
# WALLPAPER
# Just comment and uncomment to select
$wallpaper = screenshot
#$wallpaper = ~/your/wallpaper-path.png
$blur = 2
# ADJUST HERE
$rounding = 12
$shape-rd = -1
$font-text = GeistMono Nerd Font Bold
$font-text0 = GeistMono Nerd Font
$font-display = PP Neue Machina Ultra-Bold
$font-symbol = JetBrainsMono Nerd Font Mono
# Input Var
$weight = 2
$inner-color = rgba($baseAlpha80)
$border-color = rgba($mauveAlpha80)
# Colors
$clock-color = $text
$fg0 = $text
$fg1 = $base
$shape-col0 = $mauve
$shape-col1 = rgba($surface0Alpha80)
$shape-col2 = rgba($surface1Alpha80)
$shadow-pass = 2
$shadow-size = 3
$shadow-color = $crust
$shadow-boost = 1.2
$text-shadow-pass = 1
$text-shadow-boost = 0.5
# GENERAL
general {
no_fade_in = true
grace = 1
disable_loading_bar = false
hide_cursor = true
ignore_empty_input = true
text_trim = true
}
#BACKGROUND
background {
monitor =
path = $wallpaper
blur_passes = $blur
contrast = 0.8916
brightness = 0.7172
vibrancy = 0.1696
vibrancy_darkness = 0
}
# PROFILE PICTURE
image {
monitor =
path = $imgPath/profile.jpg
size = 120 # lesser side if not 1:1 ratio
opacity = 0.25
shadow_pass = $shadow-pass
shadow_size = $shadow-size
shadow_color = $shadow-color
shadow_boost = $shadow-boost
rounding = $rounding # negative values mean circle
border_size = 4
border_color = $mauve
rotate = 0 # degrees, counter-clockwise
position = 20, -20
halign = left
valign = top
zindex = 1
}
# USER INFO
label {
monitor =
text = cmd[update:1000] echo -e "$USER\n󰁥 $($host)\n\n$(uname -r)\nPackages: $(pacman -Q | wc -l) pacman"
shadow_passes = 1
shadow_boost = 0.5
color = $fg0
font_size = 11
font_family = $font-text
position = 170, -35
halign = left
valign = top
}
# WEATHERCAST & LOCATION
label {
monitor =
text = cmd[update:1000] echo "$(bash $scrPath/weatherinfo.sh)"
shadow_passes = 1
shadow_boost = 0.5
color = $fg0
font_size = 11
font_family = $font-text
position = 0, -20
halign = center
valign = top
}
# INPUT FIELD
input-field {
monitor =
size = 275, 55
rounding = $rounding
outline_thickness = $weight
outer_color = $border-color
dots_size = 0.1 # Scale of input-field height, 0.2 - 0.8
dots_spacing = 1 # Scale of dots' absolute size, 0.0 - 1.0
dots_center = true
inner_color = $inner-color
font_color = $fg0
fade_on_empty = false
font_family = $font-text
placeholder_text = <span foreground="##$textAlpha">󰢏 $USER</span>
hide_input = false
check_color = $mauve
fail_color = $red
fail_text = <i>$FAIL <b>($ATTEMPTS)</b></i>
capslock_color = $yellow
position = 0, -240
halign = center
valign = center
zindex = 10
}
# TIME HR
label {
monitor =
text = cmd[update:1000] echo -e "$(date +"%H")" # 24-Hour Format
#text = cmd[update:1000] echo -e "$(date +"%I")" # 12-Hour Format
color = $clock-color
shadow_pass = $shadow-pass
shadow_size = $shadow-size
shadow_color = $shadow-color
shadow_boost = $shadow-boost
font_size = 150
font_family = $font-display
position = 0, -155
halign = center
valign = top
}
# TIME MM
label {
monitor =
text = cmd[update:1000] echo -e "$(date +"%M")"
color = $clock-color
shadow_pass = $shadow-pass
shadow_size = $shadow-size
shadow_color = $shadow-color
shadow_boost = $shadow-boost
font_size = 150
font_family = $font-display
position = 0, -325
halign = center
valign = top
}
# AM/PM for 12-Hour Format
#label {
monitor =
text = cmd[update:1000] echo -e "$(date +"%p")"
color = $clock-color
shadow_pass = $shadow-pass
shadow_size = $shadow-size
shadow_color = $shadow-color
shadow_boost = $shadow-boost
font_size = 16
font_family = $font-display
position = 0, 17
halign = center
valign = center
zindex = 5
}
# AM/PM BG
#shape {
monitor =
size = 70, 40
shadow_passes = $text-shadow-pass
shadow_boost = $text-shadow-boost
color = $shape-col2
rounding = $rounding
border_size =
border_color =
position = 0, 20
halign = center
valign = center
zindex = 1
}
# GREETING
label {
monitor =
text = cmd[update:1000] echo "$(bash $scrPath/greeting.sh)"
shadow_passes = $text-shadow-pass
shadow_boost = $text-shadow-boost
color = $fg0
font_size = 11
font_family = $font-text
position = 0, -55
halign = center
valign = center
}
# TODAY IS
label {
monitor =
text = cmd[update:1000] bash -c 'day=$(date +%A); echo "Today is $day"'
shadow_passes = $text-shadow-pass
shadow_boost = $text-shadow-boost
color = $fg0
font_size = 11
font_family = $font-text
position = 0, -75
halign = center
valign = center
}
# DATE
label {
monitor =
text = cmd[update:1000] bash -c 'day=$(date +%d); case "$day" in 1) suffix="st";; 2) suffix="nd";; 3) suffix="rd";; *) suffix="th";; esac; echo -e "$(date +"%B %e")'$day'$suffix, $(date +%Y)"'
shadow_passes = $text-shadow-pass
shadow_boost = $text-shadow-boost
color = $fg0
font_size = 14
font_family = $font-text
position = 0, -115
halign = center
valign = center
}
# BATTERY
label {
monitor =
text = cmd[update:1000] echo -e "$($scrPath/battery.sh)"
color = $fg1
font_size = 12
font_family = $font-text
position = -37, 29
halign = right
valign = bottom
zindex = 5
}
# NETWORK
label {
monitor =
text = cmd[update:1000] echo -e "$($scrPath/network.sh)"
color = $fg1
font_size = 12
font_family = $font-text
position = 37, 29
halign = left
valign = bottom
zindex = 5
}
# BLUETOOTH
label {
monitor =
text = cmd[update:1000] echo -e "$(~/.config/hypr/hyprlock/bluetooth.sh)"
color = $fg1
font_size = 12
font_family = $font-text
position = 37, 85
halign = left
valign = bottom
zindex = 5
}
# BATTERY BG
shape {
monitor =
size = 90, 40
shadow_passes = $text-shadow-pass
shadow_boost = $text-shadow-boost
color = $shape-col0
rounding = $shape-rd
border_size =
border_color =
position = -20, 20
halign = right
valign = bottom
zindex = 1
}
# NETWORK BG
shape {
monitor =
size = 150, 40
shadow_passes = $text-shadow-pass
shadow_boost = $text-shadow-boost
color = $shape-col0
rounding = $shape-rd
border_size =
border_color =
position = 20, 20
halign = left
valign = bottom
zindex = 1
}
# BLUETOOTH BG
shape {
monitor =
size = 150, 40
shadow_passes = $text-shadow-pass
shadow_boost = $text-shadow-boost
color = $shape-col0
rounding = $shape-rd
border_size =
border_color =
position = 20, 75
halign = left
valign = bottom
zindex = 1
}
# MEDIA BG
image {
monitor =
path = $imgPath/media-bg-dark-25.png
size = 8%
opacity = 0.25
rounding = 5
border_size = 0
rotate = 0
position = 0, -10%
halign = center
valign = center
zindex = 1
}
shape {
monitor =
size = 35%, 8%
shadow_passes = $text-shadow-pass
shadow_boost = $text-shadow-boost
color = $shape-col1
rounding = $rounding
border_size =
border_color =
position = 0, 5%
halign = center
valign = bottom
zindex = 1
}
# PLAYER TITLE
label {
monitor =
text = cmd[update:1000] echo "$($scrPath/playerctl.sh --title)"
color = $fg0
font_size = 14
font_family = $font-text
position = 0, -40%
halign = center
valign = center
zindex = 5
}
# PLAYER ARTIST
label {
monitor =
text = cmd[update:1000] echo "$($scrPath/playerctl.sh --artist)"
color = $fg0
font_size = 11
font_family = $font-text
position = 0, -42%
halign = center
valign = center
zindex = 5
}
# PLAYER ALBUM
label {
monitor =
text = cmd[update:1000] echo "$($scrPath/playerctl.sh --album)"
color = $fg0
font_size = 11
font_family = $font-text0
position = 0, -44%
halign = center
valign = center
zindex = 5
}
# PLAYER STATUS SYMBOL
label {
monitor =
text = cmd[update:1000] echo "$($scrPath/playerctl.sh --status-symbol)"
color = $fg0
font_size = 16
font_family = $font-symbol
position = 33.5%, -38%
halign = left
valign = center
zindex = 5
}
# PLAYER STATUS
label {
monitor =
text = cmd[update:1000] echo "$($scrPath/playerctl.sh --status)"
color = $fg0
font_size = 10
font_family = $font-text
position = 35%, -38%
halign = left
valign = center
zindex = 5
}
# PLAYER SOURCE SYMBOL
label {
monitor =
text = cmd[update:1000] echo "$($scrPath/playerctl.sh --source-symbol)"
color = rgba($subtext0Alpha99)
font_size = 16
font_family = $font-symbol
position = -33.5%, -38%
halign = right
valign = center
zindex = 5
}
# PLAYER SOURCE
label {
monitor =
text = cmd[update:1000] echo "$($scrPath/playerctl.sh --source)"
color = rgba($subtext0Alpha99)
font_size = 10
font_family = $font-text
position = -35%, -38%
halign = right
valign = center
zindex = 5
}
label {
monitor =
text =
color = $fg0
font_size = 24
font_family = $font-symbol
position = 0, 1%
halign = center
valign = bottom
}
-155
View File
@@ -1,155 +0,0 @@
source = $HOME/.config/hypr/catppuccin-hyprland/themes/mocha.conf
$accent = $mauve
$accentAlpha = $mauveAlpha
$font = JetBrainsMono Nerd Font
# GENERAL
general {
hide_cursor = true
no_fade_in = false
grace = 0
disable_loading_bar = true
}
# BACKGROUND
background {
monitor =
path = $HOME/Pictures/reze/reze.png
blur_passes = 3
blur_size = 7
noise = 0.0117
contrast = 0.8916
brightness = 0.8172
vibrancy = 0.1696
vibrancy_darkness = 0.0
color = $base
}
# TIME
label {
monitor =
text = $TIME
color = $text
font_size = 120
font_family = $font
position = 0, 300
halign = center
valign = center
}
# DATE
label {
monitor =
text = cmd[update:43200000] date +"%A, %d %B %Y"
color = $text
font_size = 30
font_family = $font
position = 0, 200
halign = center
valign = center
}
# USER AVATAR (Center)
image {
monitor =
path = $HOME/.face
size = 120
border_color = rgba(255, 255, 255, 0.1)
position = 0, 50
halign = center
valign = center
}
# INPUT FIELD (Center)
input-field {
monitor =
size = 300, 60
outline_thickness = 2
dots_size = 0.2
dots_spacing = 0.2
dots_center = true
outer_color = rgba(255, 255, 255, 0.1)
inner_color = rgba(0, 0, 0, 0.2)
font_color = $text
fade_on_empty = false
placeholder_text = <span foreground="##$textAlpha"><i>󰌾 Logged in as </i><span foreground="##$accentAlpha">$USER</span></span>
hide_input = false
check_color = $accent
fail_color = $red
fail_text = <i>$FAIL <b>($ATTEMPTS)</b></i>
capslock_color = $yellow
position = 0, -60
halign = center
valign = center
}
# --- MUSIC DASHBOARD (TOP RIGHT) ---
# ALBUM ART (At the very top right, 80px high, adjusted -3px for perfect alignment)
image {
monitor =
path = /tmp/hyprlock_art.png
size = 80
rounding = 15
border_color = rgba(255, 255, 255, 0.1)
reload_time = 2
reload_cmd = ~/.config/hypr/scripts/album_art.sh
position = -20, -17
halign = right
valign = top
}
# MUSIC TEXT BOX (To the left of the image, same height)
shape {
monitor =
size = 300, 80
color = rgba(0, 0, 0, 0.3)
rounding = 15
border_size = 1
border_color = rgba(255, 255, 255, 0.1)
position = -110, -20
halign = right
valign = top
}
# MUSIC TEXT (Right aligned and vertically centered inside the 80px box)
label {
monitor =
text = cmd[update:1000] ~/.config/hypr/scripts/songdetail.sh
color = $text
font_size = 14
font_family = $font
position = -125, -37
halign = right
valign = top
text_align = right
}
# --- STATUS DASHBOARD (BOTTOM RIGHT) ---
# SYSTEM INFO BOX (Vertical stack container)
shape {
monitor =
size = 300, 100
color = rgba(0, 0, 0, 0.3)
rounding = 15
border_size = 1
border_color = rgba(255, 255, 255, 0.1)
position = -20, 20
halign = right
valign = bottom
}
# SYSTEM INFO TEXT (3 lines, top of each other, right aligned)
label {
monitor =
text = cmd[update:5000] ~/.config/hypr/scripts/status_info.sh
color = $text
font_size = 14
font_family = $font
position = -35, 35
halign = right
valign = bottom
text_align = right
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

-33
View File
@@ -1,33 +0,0 @@
#!/bin/bash
# Get the current battery percentage
battery_percentage=$(cat /sys/class/power_supply/BAT0/capacity)
# Get the battery status (Charging or Discharging)
battery_status=$(cat /sys/class/power_supply/BAT0/status)
# Define the battery icons for each 10% segment
battery_icons=("󰂃" "󰁺" "󰁻" "󰁼" "󰁽" "󰁾" "󰁿" "󰂀" "󰂁" "󰁹")
# Define the charging icon
charging_icon="󰂄"
# Calculate the index for the icon array
# Ensure the index is within bounds (0 to 9) for battery percentages 0 to 100
icon_index=$((battery_percentage / 10))
# If the battery is 100%, use the last icon (index 9)
if [ "$battery_percentage" -eq 100 ]; then
icon_index=9
fi
# Get the corresponding icon
battery_icon=${battery_icons[$icon_index]}
# Check if the battery is charging
if [ "$battery_status" = "Charging" ]; then
battery_icon="$charging_icon"
fi
# Output the battery percentage and icon
echo "$battery_percentage% $battery_icon"
-47
View File
@@ -1,47 +0,0 @@
#!/bin/bash
# Path to the Hyprland configuration file
config_file="$HOME/.config/hypr/hyprlock.conf"
# Read the bt-mode value from the configuration file
bt_mode=$(grep -oP '^\$bt-mode\s*=\s*\K\S+' ~/.config/hypr/hyprlock.conf)
# Get Bluetooth power status
bluetooth_status=$(bluetoothctl show | grep "Powered:" | awk '{print $2}')
# Check if Bluetooth is powered on
if [ "$bluetooth_status" != "yes" ]; then
echo "󰂯 Bluetooth Off"
exit 0
fi
# Initialize connected devices
connected_devices=$(echo "$connected_devices" | sed 's/[[:space:]]*$//')
# Process Bluetooth devices
while read -r line; do
echo "Processing line: $line" >&2
device_mac=$(echo "$line" | awk '{print $2}')
device_name=$(echo "$line" | cut -d' ' -f3-)
echo "Device MAC: $device_mac" >&2
echo "Device Name: $device_name" >&2
if bluetoothctl info "$device_mac" | grep -q "Connected: yes"; then
connected_devices+="$device_name "
fi
echo "Finished processing $device_mac" >&2
echo "---" >&2
done < <(bluetoothctl devices)
# If no connected devices, show "No Devices"
if [ -z "$connected_devices" ]; then
echo "󰂲 No Devices"
exit 0
fi
# Display output based on bt-mode
if [ "$bt_mode" = "true" ]; then
echo "󰂯 $connected_devices"
else
echo "󰂯 Connected"
fi
-22
View File
@@ -1,22 +0,0 @@
#!/bin/bash
# Only works if you use SWWW for your wallpapers
SWWW_DIR="$HOME/.cache/swww/"
HYPRLOCK="$HOME/.config/hypr/hyprlock.conf"
# Get a Monitor Cache File
FIRST_FILE=$(find "$SWWW_DIR" -type f | head -n 1)
# Check if SWWW file exists
if [ -n "$FIRST_FILE" ] && [ -f "$FIRST_FILE" ]; then
WALLPAPER=$(sed -n '2p' "$FIRST_FILE")
sed -i "s|^\(\$wallpaper[[:space:]]*=[[:space:]]*\).*|\1$WALLPAPER # (screenshot or /path/to/your/wallpaper.jpg)|" "$HYPRLOCK"
echo "Wallpaper path updated to $WALLPAPER"
exit 0
fi
# Error
echo "Fehler: kein Wallpaper gefunden."
exit 1
-32
View File
@@ -1,32 +0,0 @@
#!/bin/bash
# Set your Username
# username="TamarindX"
# Read the username alias from hyprlock.conf
username=$(grep -oP '^\$USER\s*=\s*\K\S+' ~/.config/hypr/hyprlock.conf)
# Check if the username was successfully extracted
if [ -z "$username" ]; then
echo "Username not found in hyprlock.conf."
exit 1
fi
# Get the current hour
hour=$(date +%H)
# Determine the greeting based on the time
if [ "$hour" -ge 5 ] && [ "$hour" -lt 12 ]; then
greeting="Good Morning"
elif [ "$hour" -ge 12 ] && [ "$hour" -lt 17 ]; then
greeting="Good Afternoon"
elif [ "$hour" -ge 17 ] && [ "$hour" -lt 21 ]; then
greeting="Good Evening"
elif [ "$hour" -ge 21 ] && [ "$hour" -lt 24 ]; then
greeting="Good Night"
else
greeting="GO TO SLEEP!"
fi
# Output the combined text
echo -e "Hello, $username! $greeting"
-65
View File
@@ -1,65 +0,0 @@
#!/bin/bash
# Set this variable to control the output
# Set to "true" to show SSID, "false" to show "Connected"
# show_ssid=false
# Read the wifi-mode alias from hyprlock.conf
show_ssid=$(grep -oP '^\$wifi-mode\s*=\s*\K\S+' ~/.config/hypr/hyprlock.conf)
# Check if the SSID was successfully extracted else fallback?!
if [ -z "$show_ssid" ]; then
show_ssid=false
fi
# Check if any Ethernet connection is active
ethernet_connected=$(nmcli -t -f DEVICE,TYPE,STATE dev | grep -E 'ethernet:connected')
# If Ethernet
if [ -n "$ethernet_connected" ]; then
echo "󰈀 Ethernet"
exit 0
fi
# Get Wi-Fi connection status
wifi_status=$(nmcli -t -f WIFI g)
# Check if Wi-Fi is enabled
if [ "$wifi_status" != "enabled" ]; then
echo "󰤮 Wi-Fi Off"
exit 0
fi
# Get active Wi-Fi connection details
wifi_info=$(nmcli -t -f ACTIVE,SSID,SIGNAL dev wifi | grep '^yes')
# If no active connection, show "Disconnected"
if [ -z "$wifi_info" ]; then
echo "󰤮 No Wi-Fi"
exit 0
fi
# Extract SSID
ssid=$(echo "$wifi_info" | cut -d':' -f2)
# Extract signal strength
signal_strength=$(echo "$wifi_info" | cut -d':' -f3)
# Define Wi-Fi icons based on signal strength
wifi_icons=("󰤯" "󰤟" "󰤢" "󰤥" "󰤨") # From low to high signal
# Clamp signal strength between 0 and 100
signal_strength=$((signal_strength < 0 ? 0 : (signal_strength > 100 ? 100 : signal_strength)))
# Calculate the icon index based on signal strength
icon_index=$((signal_strength / 25))
# Get the corresponding icon
wifi_icon=${wifi_icons[$icon_index]}
# Output based on show_ssid variable
if [ "$show_ssid" = true ]; then
echo "$wifi_icon $ssid"
else
echo "$wifi_icon Connected"
fi
-116
View File
@@ -1,116 +0,0 @@
#!/bin/bash
if [ $# -eq 0 ]; then
echo "Usage: $0 --title | --artist | --album | --source | --source-symbol"
exit 1
fi
# Function to get metadata using playerctl
get_metadata() {
key=$1
playerctl metadata --player=%any,chromium,firefox --format "{{ $key }}" 2>/dev/null
}
# Check for arguments
# Function to determine the source and return an icon and text
get_source_info_symbol() {
trackid=$(get_metadata "mpris:trackid")
if [[ "$trackid" == *"firefox"* ]]; then
echo -e "󰈹"
elif [[ "$trackid" == *"spotify"* ]]; then
echo -e ""
elif [[ "$trackid" == *"chromium"* ]]; then
echo -e ""
else
echo ""
fi
}
get_source_info() {
trackid=$(get_metadata "mpris:trackid")
if [[ "$trackid" == *"firefox"* ]]; then
echo -e "Firefox"
elif [[ "$trackid" == *"spotify"* ]]; then
echo -e "Spotify"
elif [[ "$trackid" == *"chromium"* ]]; then
echo -e "Chrome"
else
echo ""
fi
}
# Function to truncate text with ellipsis if necessary
truncate_with_ellipsis() {
text=$1
max_length=$2
if [ ${#text} -gt $max_length ]; then
echo "${text:0:$((max_length - 3))}..."
else
echo "$text"
fi
}
# Parse the argument
case "$1" in
--title)
title=$(get_metadata "xesam:title")
if [ -z "$title" ]; then
echo ""
else
truncate_with_ellipsis "$title" 28 # Limit the output to 50 characters
fi
;;
--artist)
artist=$(get_metadata "xesam:artist")
if [ -z "$artist" ]; then
echo ""
else
truncate_with_ellipsis "$artist" 28 # Limit the output to 50 characters
fi
;;
--status-symbol)
status=$(playerctl status 2>/dev/null)
if [[ $status == "Playing" ]]; then
echo "󰎆"
elif [[ $status == "Paused" ]]; then
echo "󰏥"
else
echo ""
fi
;;
--status)
status=$(playerctl status 2>/dev/null)
if [[ $status == "Playing" ]]; then
echo "Playing Now"
elif [[ $status == "Paused" ]]; then
echo "Paused"
else
echo ""
fi
;;
--album)
album=$(playerctl metadata --player=%any,chromium,firefox --format "{{ xesam:album }}" 2>/dev/null)
if [[ -n $album ]]; then
echo "$album"
else
status=$(playerctl status 2>/dev/null)
if [[ -n $status ]]; then
echo "Not album"
else
truncate_with_ellipsis "$album" 28 # Limit the output to 50 characters
fi
fi
;;
--source-symbol)
get_source_info_symbol
;;
--source)
get_source_info
;;
*)
echo "Invalid option: $1"
echo "Usage: $0 --title | --artist | --album | --source | --source-symbol"
exit 1
;;
esac
-39
View File
@@ -1,39 +0,0 @@
#!/bin/bash
# Get location data using IP Geolocation
# Recommended with API, but you can try w/o API [IPINFO.IO]
# API_TOKEN="YOUR_API_TOKEN"
# location_data=$(curl -s "https://ipinfo.io?token=$API_TOKEN" 2>/dev/null)
# location_data=$(curl -s https://ipinfo.io 2>/dev/null) #[Limited requests. Require API to send 50k request/month]
# Extract city and country code (ISO 3166-1 alpha-2 code)
# CITY=$(echo "$location_data" | jq -r '.city // empty')
# COUNTRY=$(echo "$location_data" | jq -r '.country // empty')
# IPINFO Alternative [IP-API.COM]
location_data=$(curl -s "http://ip-api.com/json/" 2>/dev/null)
# Extract city and country code
CITY=$(echo "$location_data" | jq -r '.city // empty')
COUNTRY=$(echo "$location_data" | jq -r '.countryCode // empty')
# Visit http://ip-api.com/json/ to find other variables
# HARDCODE Location
# CITY=$(grep -oP '^\$CITY\s*=\s*\K.+' ~/.config/hypr/hyprlock.conf)
# COUNTRY=$(grep -oP '^\$COUNTRY\s*=\s*\K.+' ~/.config/hypr/hyprlock.conf)
# Check if CITY and COUNTRY are valid
if [[ -n "$CITY" && -n "$COUNTRY" ]]; then
# Fetch weather info for the detected city from wttr.in
weather_info=$(curl -s "wttr.in/$CITY?format=%c+%C+%t" 2>/dev/null)
# Check if the weather info is valid
if [[ -n "$weather_info" ]]; then
echo "$COUNTRY, $CITY: $weather_info"
else
echo "Weather info unavailable for $COUNTRY, $CITY"
fi
else
echo "Unable to determine your location"
fi
+1 -1
View File
@@ -1,6 +1,6 @@
wallpaper {
monitor =
path = ~/.wallpapers/arch.png
path = ~/.wallpapers/space-1.jpg
fit_mode = cover
}
-97
View File
@@ -1,97 +0,0 @@
#!/bin/bash
# Set paths
CONFIG_DIR="$HOME/.config/hypr"
BACKUP_DIR="$CONFIG_DIR/hyprlock-backup-$(date +%Y%m%d%H%M%S)"
REPO_URL="https://github.com/Tamarindtype/googlish-hyprlock-theme.git"
TEMP_DIR="$(mktemp -d)"
read -p "Do you want to see every command executed? [y/N]: " choice
choice=${choice,,}
if [[ "$choice" == "y" ]]; then
SHOW_COMMANDS=true
else
SHOW_COMMANDS=false
fi
run_cmd() {
local desc="$1"
local cmd="$2"
if [[ "$SHOW_COMMANDS" == true ]]; then
echo -e "\n$desc"
echo "-> $cmd"
read -p "Run command? [Y/n]: " confirm
confirm=${confirm,,}
if [[ "$confirm" == "n" ]]; then
echo "⏭ Skipped."
else
eval "$cmd"
fi
else
eval "$cmd"
fi
}
# Backup existing config
if [ -d "$CONFIG_DIR/hyprlock" ] || [ -f "$CONFIG_DIR/hyprlock.conf" ]; then
read -p "Do you want to create a backup of your config? [Y/n]: " choice
choice=${choice,,}
if [[ "$choice" != "n" ]]; then
run_cmd "Creating backup folder" "mkdir -p \"$BACKUP_DIR\""
if [ -d "$CONFIG_DIR/hyprlock" ]; then
run_cmd "Backing up folder" "mv \"$CONFIG_DIR/hyprlock\" \"$BACKUP_DIR/\""
fi
if [ -f "$CONFIG_DIR/hyprlock.conf" ]; then
run_cmd "Backing up file" "mv \"$CONFIG_DIR/hyprlock.conf\" \"$BACKUP_DIR/\""
fi
fi
else
echo "No existing hyprlock config found. Skipping backup."
fi
# Clone the repo
run_cmd "Cloning repository..." "git clone \"$REPO_URL\" \"$TEMP_DIR\""
# Move files
run_cmd "Creating config directory" "mkdir -p \"$CONFIG_DIR\""
if [ -d "$TEMP_DIR/hyprlock" ]; then
run_cmd "Moving hyprlock folder" "mv \"$TEMP_DIR/hyprlock\" \"$CONFIG_DIR/\""
fi
if [ -f "$TEMP_DIR/hyprlock.conf" ]; then
run_cmd "Moving hyprlock.conf" "mv \"$TEMP_DIR/hyprlock.conf\" \"$CONFIG_DIR/\""
fi
# Make scripts executable
if [ -d "$CONFIG_DIR/hyprlock" ]; then
run_cmd "Making scripts executable..." "chmod +x \"$CONFIG_DIR/hyprlock\"/*.sh"
fi
# Optional: Run hyprlock
run_cmd "Running Hyprlock" "hyprlock"
read -p "Do you want to install the 'hyprlock-change-wallpaper' command? [Y/n]: " choice
choice=${choice,,}
if [[ "$choice" != "n" ]]; then
run_cmd "Installing hyprlock-change-wallpaper shortcut" "sudo ln -sf $CONFIG_DIR/hyprlock/change_wallpaper.sh /usr/local/bin/hyprlock-change-wallpaper"
run_cmd "Make command runable" "chmod +x $CONFIG_DIR/hyprlock/change_wallpaper.sh"
if command -v hyprlock-change-wallpaper &>/dev/null; then
echo "hyprlock-change-wallpaper was succesfully installed"
else
echo "Error while installing hyprlock-change-wallpaper."
fi
else
echo "⏭ installation of hyprlock-change-wallpaper skipped"
fi
# Cleanup
run_cmd "Cleaning up temp files..." "rm -rf \"$TEMP_DIR\""
+52 -27
View File
@@ -1,11 +1,25 @@
-- https://wiki.hypr.land/Configuring/Advanced-and-Cool/Animations/
--
-- Quick and clean. Motion should get out of the way, not perform.
-- YoRHa motion: calm and mechanical. Things glide in, settle and stop; nothing
-- overshoots, nothing pops. Matches Theme.easeSnap / easeGlide in
-- ~/.config/quickshell/config/Theme.qml.
--
-- An earlier pass overdid it: a 1.56 overshoot on every window, an 88% popin
-- scale, and a borderangle looping forever so the focused border was in
-- constant motion. The bounce is dialled back to a hint, the popin barely
-- scales, and the border angle does not animate at all.
-- The screen is a porthole: the hull (quickshell/bar/Frame.qml) sits on the
-- top layer, so anything that moves past the edge of the opening passes
-- behind the frame instead of over the bar.
--
-- windows resolve in from a hair smaller and fade, the way a NieR menu
-- panel appears rather than arrives; leave the same way, faster.
-- workspaces drift: the old one sinks a little way behind the rim as it
-- fades, the new one rises into its place. A short, slow
-- vertical drift rather than a full-screen sweep, which on a
-- 2560px panel is a lot of motion for "you are somewhere else
-- now". Direction follows workspace order.
-- scratchpad a drawer: SUPER+S lowers it out from under the bar, the same
-- gesture as the dashboard and the popouts, and raises it back.
-- layers only fade; quickshell animates its own surfaces.
--
-- Nothing overshoots and the border angle never animates.
hl.config({
animations = {
@@ -14,34 +28,45 @@ hl.config({
})
-- ------------------------------------------------------------------- curves
hl.curve("snap", { type = "bezier", points = { { 0.16, 1.00 }, { 0.30, 1.00 } } })
hl.curve("overshoot", { type = "bezier", points = { { 0.34, 1.12 }, { 0.64, 1.00 } } })
hl.curve("hard", { type = "bezier", points = { { 0.55, 0.00 }, { 0.90, 0.35 } } })
-- outQuint: fast off the mark, long quiet settle.
hl.curve("glide", { type = "bezier", points = { { 0.22, 1.00 }, { 0.36, 1.00 } } })
-- Material standard, for things that move from one place to another.
hl.curve("standard", { type = "bezier", points = { { 0.40, 0.00 }, { 0.20, 1.00 } } })
-- Leaving: ease in, so the exit gathers pace and is gone.
hl.curve("exit", { type = "bezier", points = { { 0.55, 0.00 }, { 0.90, 0.35 } } })
hl.curve("linear", { type = "bezier", points = { { 0, 0 }, { 1, 1 } } })
-- easeInOutSine-ish with a long tail: starts without a jolt, settles slowly.
-- For motion big enough that a fast start would read as a shove.
hl.curve("drift", { type = "bezier", points = { { 0.35, 0.00 }, { 0.15, 1.00 } } })
-- --------------------------------------------------------------------- tree
-- speed is in ds (1ds = 100ms). Unset leaves inherit their parent.
hl.animation({ leaf = "global", enabled = true, speed = 4, bezier = "snap" })
hl.animation({ leaf = "border", enabled = true, speed = 5, bezier = "snap" })
hl.animation({ leaf = "global", enabled = true, speed = 4, bezier = "glide" })
hl.animation({ leaf = "border", enabled = true, speed = 4, bezier = "glide" })
hl.animation({ leaf = "borderangle", enabled = false })
hl.animation({ leaf = "windows", enabled = true, speed = 4, bezier = "snap" })
hl.animation({ leaf = "windowsIn", enabled = true, speed = 4, bezier = "overshoot", style = "popin 96%" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 2.6, bezier = "hard", style = "popin 97%" })
hl.animation({ leaf = "windowsMove", enabled = true, speed = 3.6, bezier = "snap" })
hl.animation({ leaf = "windows", enabled = true, speed = 4, bezier = "glide" })
hl.animation({ leaf = "windowsIn", enabled = true, speed = 4, bezier = "glide", style = "popin 94%" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 2.4, bezier = "exit", style = "popin 96%" })
hl.animation({ leaf = "windowsMove", enabled = true, speed = 4, bezier = "standard" })
hl.animation({ leaf = "fade", enabled = true, speed = 3, bezier = "snap" })
hl.animation({ leaf = "fadeIn", enabled = true, speed = 2.4, bezier = "snap" })
hl.animation({ leaf = "fadeOut", enabled = true, speed = 1.8, bezier = "hard" })
hl.animation({ leaf = "fadeSwitch", enabled = true, speed = 2, bezier = "snap" })
hl.animation({ leaf = "fadeShadow", enabled = true, speed = 3, bezier = "snap" })
hl.animation({ leaf = "fadeDim", enabled = true, speed = 2.4, bezier = "snap" })
hl.animation({ leaf = "fade", enabled = true, speed = 3.4, bezier = "glide" })
hl.animation({ leaf = "fadeIn", enabled = true, speed = 3.4, bezier = "glide" })
hl.animation({ leaf = "fadeOut", enabled = true, speed = 2.2, bezier = "exit" })
hl.animation({ leaf = "fadeSwitch", enabled = true, speed = 3, bezier = "glide" })
hl.animation({ leaf = "fadeShadow", enabled = true, speed = 3.4, bezier = "glide" })
hl.animation({ leaf = "fadeDim", enabled = true, speed = 3, bezier = "glide" })
hl.animation({ leaf = "layers", enabled = true, speed = 3, bezier = "snap" })
hl.animation({ leaf = "layersIn", enabled = true, speed = 3, bezier = "snap", style = "slide" })
hl.animation({ leaf = "layersOut", enabled = true, speed = 2, bezier = "hard", style = "slide" })
-- Quickshell animates its own surfaces; the compositor only fades them, so the
-- two motions never stack.
hl.animation({ leaf = "layers", enabled = true, speed = 2.6, bezier = "glide" })
hl.animation({ leaf = "layersIn", enabled = true, speed = 2.6, bezier = "glide", style = "fade" })
hl.animation({ leaf = "layersOut", enabled = true, speed = 2, bezier = "exit", style = "fade" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 3, bezier = "snap", style = "slidevert" })
hl.animation({ leaf = "workspacesIn", enabled = true, speed = 3, bezier = "snap", style = "slidevert" })
hl.animation({ leaf = "workspacesOut", enabled = true, speed = 2.4, bezier = "hard", style = "slidevert" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 5.5, bezier = "drift", style = "slidefadevert 18%" })
hl.animation({ leaf = "workspacesIn", enabled = true, speed = 5.5, bezier = "drift", style = "slidefadevert 18%" })
hl.animation({ leaf = "workspacesOut", enabled = true, speed = 4.5, bezier = "drift", style = "slidefadevert 18%" })
hl.animation({ leaf = "specialWorkspace", enabled = true, speed = 3, bezier = "snap", style = "slidevert" })
hl.animation({ leaf = "specialWorkspace", enabled = true, speed = 4.5, bezier = "glide", style = "slidevert" })
hl.animation({ leaf = "specialWorkspaceIn", enabled = true, speed = 4.5, bezier = "glide", style = "slidevert" })
hl.animation({ leaf = "specialWorkspaceOut", enabled = true, speed = 3.5, bezier = "exit", style = "slidevert" })
+3 -2
View File
@@ -10,7 +10,8 @@ hl.on("hyprland.start", function()
hl.exec_cmd("uwsm app -- qs")
hl.exec_cmd("uwsm app -- nm-applet --indicator")
hl.exec_cmd("uwsm app -- hyprpaper")
-- hyprpaper is not started here: its systemd unit is enabled for
-- graphical-session.target, and a second copy here ran alongside it.
-- clipboard history, text and images
hl.exec_cmd("wl-paste --type text --watch cliphist store")
@@ -19,7 +20,7 @@ hl.on("hyprland.start", function()
-- pdfs-prompt has no hidden/resident mode, so the first search of the
-- session is cold. Run one throwaway query once the Proton Drive daemon
-- is up to pull the binary into the page cache and warm the daemon's index.
hl.exec_cmd("sh -c 'sleep 15; pdfs-prompt --feed a >/dev/null 2>&1'")
hl.exec_cmd("uwsm app -- pdfs-prompt --preload")
-- hl.exec_cmd("uwsm app -- nextcloud --background")
-- hl.exec_cmd("uwsm app -- rclone mount google_drive: ~/gdrive")
+2
View File
@@ -9,7 +9,9 @@ end
shell("lock", { key = mod .. " + CTRL + L", description = "lock screen" })
shell("power", { key = mod .. " + P", description = "power menu" })
shell("ask", { key = mod .. " + A", description = "ask claude" })
shell("dashboard", { key = mod .. " + D", description = "dashboard" })
shell("toggleDnd", { key = mod .. " + SHIFT + N", description = "toggle do not disturb" })
shell("toggleStayAwake", { key = mod .. " + SHIFT + I", description = "keep the screen awake" })
shell("clearNotifications", { key = mod .. " + CTRL + SHIFT + N", description = "clear notifications" })
shell("reloadConfig", { key = mod .. " + SHIFT + R", description = "reload shell config" })
+4 -8
View File
@@ -1,4 +1,4 @@
-- Workspaces, including the two scratchpads.
-- Workspaces, including the scratchpad.
local mod = "SUPER"
@@ -13,10 +13,6 @@ end
hl.bind(mod .. " + mouse_down", hl.dsp.focus({ workspace = "e+1" }))
hl.bind(mod .. " + mouse_up", hl.dsp.focus({ workspace = "e-1" }))
-- Scratchpads: S holds anything virtual, D holds chat.
local special = { S = "virtual", D = "discord" }
for key, name in pairs(special) do
hl.bind(mod .. " + " .. key, hl.dsp.workspace.toggle_special(name))
hl.bind(mod .. " + SHIFT + " .. key, hl.dsp.window.move({ workspace = "special:" .. name }))
end
-- Scratchpad: S holds anything virtual. Chat opens wherever it is launched.
hl.bind(mod .. " + S", hl.dsp.workspace.toggle_special("virtual"))
hl.bind(mod .. " + SHIFT + S", hl.dsp.window.move({ workspace = "special:virtual" }))
+1 -8
View File
@@ -79,9 +79,6 @@ local function reconcile(ignore)
return
end
-- First decision of the session, and it agrees with what lua/monitors.lua
-- already applied: nothing actually moved, so no daemon needs poking.
local settled = panel_off == nil and not want_off
panel_off = want_off
if want_off then
@@ -91,11 +88,7 @@ local function reconcile(ignore)
end
monitors.apply_external()
-- Quickshell follows monitor hotplug by itself; only the wallpaper daemon
-- needs a kick.
if not settled then
hl.exec_cmd("systemctl restart --user hyprpaper")
end
-- Quickshell follows monitor hotplug by itself, wallpaper included.
end
-- ------------------------------------------------------------------ triggers
+48 -33
View File
@@ -1,5 +1,5 @@
-- Persona × Mocha — Persona 5 shapes on the Catppuccin Mocha palette, matched
-- to ~/.config/quickshell.
-- YoRHa dark — NieR:Automata's interface language on the space-1 wallpaper's
-- palette, matched to ~/.config/quickshell.
--
-- Everything static about how the desktop is painted. Motion lives next door
-- in lua/animations.lua.
@@ -9,17 +9,20 @@ local c = require("lua/palette")
hl.config({
-- https://wiki.hypr.land/Configuring/Basics/Variables/
general = {
-- Measured from the quickshell hull's opening, not the screen edge: the
-- hull reserves its own exclusive zones. 10px clears the opening's 18px
-- corner radius, so a tiled window never touches the rim.
gaps_in = 5,
gaps_out = { top = 6, right = 8, bottom = 8, left = 8 },
gaps_out = 10,
border_size = 2,
border_size = 1,
col = {
active_border = c.activeBorder,
-- Surface-level, not outline: an idle window's frame is structure,
-- Overlay, not outline: an idle window's frame is structure,
-- and the outline grey was bright enough to compete with the
-- focused border.
inactive_border = c.surfaceAlt,
inactive_border = c.overlay,
},
resize_on_border = false,
@@ -32,29 +35,32 @@ hl.config({
},
decoration = {
-- Persona 5 has no rounded corners. Neither do we.
rounding = 0,
-- Concentric with the porthole: the opening's corners are 18px and
-- the windows sit 10px inside them, so 8px keeps every gap the same
-- width all the way round the curve. Matches Theme.frameRadius -
-- gaps_out; change one and the other has to follow.
rounding = 8,
rounding_power = 2,
-- Fully opaque, both states. Flat ink on flat ground.
active_opacity = 1.0,
inactive_opacity = 1.0,
-- A hard crust offset, the same printed drop the shell's panels
-- carry, rather than a soft blur. Sharp with a 1px range leaves a
-- clean band down the right and bottom edges only.
-- A soft, wide shadow: windows float in the opening rather than
-- being printed onto it. Dark against the planet light, invisible
-- against the void, which is how a shadow behaves out there.
shadow = {
enabled = true,
sharp = true,
range = 1,
offset = { 4, 4 },
color = c.ink,
color_inactive = c.ink,
sharp = false,
range = 24,
render_power = 3,
offset = { 0, 6 },
color = "rgba(000000aa)",
color_inactive = "rgba(00000066)",
},
-- Off. Blur is the signature of the glass-and-frost look, which is the
-- opposite of what this theme is doing — every surface here is opaque,
-- flat and hard-edged. With nothing translucent left to blur it also
-- costs three render passes for no visible result.
-- Off. Every surface here is opaque, so there is nothing to blur, and
-- blur would cost three render passes for no visible result.
blur = {
enabled = false,
},
@@ -68,32 +74,41 @@ hl.config({
font_family = "FiraCode Nerd Font",
background_color = c.base,
focus_on_activate = true,
-- The shell powers displays down after idle (quickshell/services/Idle.qml)
-- and back up when idle ends; these wake them even if it is not running.
key_press_enables_dpms = true,
mouse_move_enables_dpms = true,
},
group = {
col = {
border_active = c.activeBorder,
border_inactive = c.surfaceAlt,
border_inactive = c.overlay,
},
-- Tabs are thin rounded lines, not titled slabs. The bar already
-- writes the focused title and its place in the group ("2/3", in
-- quickshell/bar/ActiveWindow.qml), so titles here only repeated it in
-- a second font. Hyprland paints a tab's indicator in the same colour
-- as its fill, so with no fill the current tab is simply the light one.
groupbar = {
font_family = "JetBrainsMono Nerd Font",
font_size = 11,
height = 24,
render_titles = false,
gradients = false,
-- --- Active tab ---
indicator_height = 3,
indicator_gap = 0,
rounding = 2,
round_only_edges = false,
keep_upper_gap = false,
gaps_in = 4,
gaps_out = 0,
col = {
active = c.accent,
inactive = c.surface,
inactive = c.overlay,
locked_active = c.alarm,
locked_inactive = c.overlay,
},
text_color = c.ink,
-- --- Inactive tab ---
text_color_inactive = c.muted,
indicator_height = 3,
},
},
+2 -3
View File
@@ -49,10 +49,9 @@ local function mirror_all(panel)
end
end
-- The bar and popouts stay on the outputs they were created for, so
-- respawn the shell the same way lua/autostart.lua starts it.
-- The bar, popouts and wallpaper stay on the outputs they were created for,
-- so respawn the shell the same way lua/autostart.lua starts it.
local function restart_clients()
hl.exec_cmd("systemctl restart --user hyprpaper")
hl.exec_cmd("sh -c 'qs kill; exec uwsm app -- qs'")
end
+24 -22
View File
@@ -1,6 +1,7 @@
-- Mocha palette — kept in step with ~/.config/quickshell/config/Theme.qml.
-- YoRHa dark palette — kept in step with ~/.config/quickshell/config/Theme.qml.
--
-- Catppuccin Mocha ground, blue accent, mauve alarm. Nothing else carries hue.
-- The wallpaper's grey-green void for the ground, its planet light as the
-- accent, the hull's red indicator as the alarm. Nothing else carries hue.
--
-- Returned as a table, so every module that paints something does
-- local c = require("lua/palette")
@@ -9,44 +10,45 @@
local c = {}
-- ---------------------------------------------------------------- THE KNOBS
-- Two colours in this setup: blue for anything live (window borders, group
-- indicators, focus), mauve for anything wrong. Change these lines and every
-- Two colours in this setup: the planet light for anything live (window
-- borders, group indicators, focus), the hull red for anything wrong. Change these lines and every
-- painted edge recolours with them.
--
-- Keep them in step with `accent` / `alarm` in
-- ~/.config/quickshell/config/Theme.qml, which is what colours the shell.
-- Those are the only places the colours are written.
c.accent = "rgb(89b4fa)"
c.alarm = "rgb(cba6f7)"
c.accent = "rgb(eef1ea)"
c.alarm = "rgb(c8684f)"
c.accentDim = "rgb(5a7dc0)"
c.accentSoft = "rgb(b4befe)"
c.accentDim = "rgb(8e978d)"
c.accentSoft = "rgb(bcc1bf)"
-- ------------------------------------------------------------------ neutrals
c.ink = "rgb(11111b)"
c.ink = "rgb(090a08)"
c.base = "rgb(1e1e2e)"
c.mantle = "rgb(181825)"
c.surface = "rgb(313244)"
c.surfaceAlt = "rgb(45475a)"
c.overlay = "rgb(585b70)"
c.outline = "rgb(6c7086)"
c.base = "rgb(0f110e)"
c.mantle = "rgb(0c0d0b)"
c.surface = "rgb(161915)"
c.surfaceAlt = "rgb(20241f)"
c.overlay = "rgb(383e35)"
c.outline = "rgb(545c52)"
c.text = "rgb(cdd6f4)"
c.subtext = "rgb(a6adc8)"
c.muted = "rgb(7f849c)"
c.text = "rgb(ccd2cb)"
c.subtext = "rgb(9fa7a2)"
c.muted = "rgb(79847a)"
-- Semantic aliases, so existing references resolve without inventing hues.
c.primary = c.text
c.primaryDim = "rgb(9399b2)"
c.primaryDim = "rgb(8e978d)"
c.glow = c.accent
c.blue = c.accent
c.deep = c.surfaceAlt
c.warn = c.alarm
c.ok = c.text
-- Blue into lavender, on the diagonal. Both ends are the accent's own family,
-- so the border never reads as two colours arguing.
c.activeBorder = { colors = { c.accent, c.accentSoft }, angle = 135 }
-- The focused window is lit by the planet: full light at the top-left, the
-- suit's grey at the far corner. One family, so it reads as light falling
-- across the frame rather than as two colours.
c.activeBorder = { colors = { c.accent, c.accentDim }, angle = 135 }
return c
+10
View File
@@ -11,3 +11,13 @@ for _, namespace in ipairs({ "bottom", "top", "takemi-launcher" }) do
ignore_alpha = 0.5,
})
end
-- The hull shares the top layer with the bar and the rail, which draw on it,
-- so they are ordered above it whatever order they map in. See
-- quickshell/bar/Frame.qml.
for _, namespace in ipairs({ "takemi-bar", "takemi-rail" }) do
hl.layer_rule({
match = { namespace = namespace },
order = -1,
})
end
-1
View File
@@ -54,7 +54,6 @@ hl.window_rule({ match = { class = GAMESCOPE }, workspace = "1" })
-- Special workspaces
hl.window_rule({ match = { class = "^(Spotify|spotify)$" }, workspace = "special:virtual" })
hl.window_rule({ match = { class = "^(discord|vesktop)$" }, workspace = "special:discord" })
-- ==========================================
-- STEAM & GAMING
+1 -1
Submodule nvim updated: 4736228253...19e0db60a1.
+2 -1
View File
@@ -3,6 +3,7 @@ aalib
abseil-cpp
acl
acpi
adw-gtk-theme
adwaita-cursors
adwaita-fonts
adwaita-icon-theme
@@ -87,7 +88,6 @@ calibre
cargo-audit
catppuccin-gtk-theme-mocha
catppuccin-qt5ct-git
catppuccinifier-cli-git
cbindgen
cblas
cdparanoia
@@ -111,6 +111,7 @@ cups-pk-helper
curl
cxxbridge
cython
darkly-bin
dav1d
db5.3
dbus
+4
View File
@@ -0,0 +1,4 @@
[ColorScheme]
active_colors=#ffccd2cb, #ff20241f, #ff383e35, #ff2a2f28, #ff0c0d0b, #ff161915, #ffccd2cb, #ffeef1ea, #ffccd2cb, #ff0c0d0b, #ff0f110e, #ff090a08, #ffbcc1bf, #ff090a08, #ffeef1ea, #ff9fa7a2, #ff161915, #ff0f110e, #ff20241f, #ffccd2cb, #ff79847a, #ffbcc1bf
inactive_colors=#ffccd2cb, #ff20241f, #ff383e35, #ff2a2f28, #ff0c0d0b, #ff161915, #ffccd2cb, #ffeef1ea, #ffccd2cb, #ff0c0d0b, #ff0f110e, #ff090a08, #ff383e35, #ffccd2cb, #ffeef1ea, #ff9fa7a2, #ff161915, #ff0f110e, #ff20241f, #ffccd2cb, #ff79847a, #ff383e35
disabled_colors=#ff545c52, #ff161915, #ff2a2f28, #ff20241f, #ff0c0d0b, #ff161915, #ff545c52, #ff79847a, #ff545c52, #ff0c0d0b, #ff0f110e, #ff090a08, #ff20241f, #ff545c52, #ff79847a, #ff545c52, #ff161915, #ff0f110e, #ff20241f, #ff545c52, #ff464d44, #ff20241f
+3 -3
View File
@@ -1,13 +1,13 @@
[Appearance]
color_scheme_path=/usr/share/qt6ct/colors/catppuccin-mocha-blue.conf
color_scheme_path=/home/narl/.config/qt6ct/colors/yorha.conf
custom_palette=true
icon_theme=Papirus-Dark
standard_dialogs=default
style=Fusion
style=Darkly
[Fonts]
fixed="Sans Serif,9,-1,5,400,0,0,0,0,0,0,0,0,0,0,1"
general="Sans Serif,9,-1,5,400,0,0,0,0,0,0,0,0,0,0,1"
general="Adwaita Sans,11,-1,5,400,0,0,0,0,0,0,0,0,0,0,1"
[Interface]
activate_item_on_single_click=1
+221 -101
View File
@@ -1,10 +1,12 @@
# Takemi — Persona × Mocha
# Takemi — YoRHa dark
A Persona 5 flavoured Quickshell desktop for Hyprland, painted in Catppuccin
Mocha. Blue for anything live, mauve for anything wrong, the Mocha greys for
everything else, and no gradients anywhere. Everything leans by the same 14°,
corners are chopped rather than rounded, and every surface is one continuous
slab divided by hairlines rather than a cluster of floating cards.
A NieR:Automata flavoured Quickshell desktop for Hyprland, kept dark and lit
like its wallpaper: a lone figure at a porthole, the void outside, one planet's
light. The planet white for anything live, the hull red for anything wrong,
grey-green for everything else. Hairlines, small dots, soft rounded corners,
letter-spaced capitals, and selection shown by inverting the row. The bar, the
rail and a thin border are one hull around the desktop, and the desktop is the
porthole cut out of it.
Replaces Waybar, dunst and hyprlock.
@@ -25,43 +27,46 @@ costs 2.5% of the width, where another 48px strip along the bottom would have
cost 4.4% of the height on top of what the bar already takes.
```
┌────────────────────────────────────────────┐
│ ◆ │ 1 2 3 │ window title ♪ TRACK │ 42% │ 00:09 │ bar — 48px exclusive
├────────────────────────────────────────────┤
│ ▐█▌│ rail — 48px exclusive
│ windows ▐ ▌│
│ ▐ ▌│
│ ▐ ▌│
└────────────────────────────────────────────┘
████████████████████████████████████████████████
█ ◉ │ 1 2 3 │ window title ♪ TRACK │ CPU │ 20:04 █ bar — 40px, part of the hull
█╭──────────────────────────────────────────╮ ▐▌█
█│ │ ▐▌█ rail — 48px, part of the hull
█│ windows │ ▐▌█
█│ │ ▐▌█
█╰──────────────────────────────────────────╯ █ frame — 8px left and bottom
████████████████████████████████████████████████
```
- **bar** — one slab: identity and workspaces on the left; the focused window,
the player, the CPU/MEM/GPU readout and the clock on the right.
- **rail** — one slab: network, bluetooth, audio, mic, backlight, battery, tray,
notifications, power. Popouts open leftward, into the screen.
- **hull** — `bar/Frame.qml`: one surface round the whole screen, with the
desktop as a rounded opening. The bar and the rail sit on it.
- **bar** — identity and workspaces on the left; the focused window, the
player, the CPU/MEM/GPU readout and the clock on the right.
- **rail** — network, bluetooth, audio, mic, backlight, battery, tray,
notifications, power. Popouts grow leftward out of it, into the screen.
Each permanent surface is a single continuous `ChromeSurface` with modules
divided by sheared hairlines. `BarPill` is chromeless by default — a module is
*content inside* a surface, not a surface of its own — and marks hover with an
accent underline. Pass `standalone: true` for the rare thing that really is its
own panel.
Modules are divided by hairlines. `BarPill` is chromeless by default — a module
is *content inside* the hull, not a surface of its own — and marks hover with
an accent underline. Pass `standalone: true` for the rare thing that really is
its own panel.
The shell uses three deliberate surface tiers. **Chrome** (`ChromeSurface`) is
the calm, texture-free bar, rail, and tooltip layer. **Transient**
The shell uses three deliberate surface tiers. **Chrome** (the hull and
`ChromeSurface`) is the calm, texture-free bar, rail, and tooltip layer. **Transient**
(`PopoutSurface`) is reserved for expressive menus and detail panels, with one
clear composition and stronger separation from the desktop. **Cinematic**
(`P5Backdrop`) belongs only to scene-scale experiences such as power and lock;
it is never used as decoration inside ordinary chrome or popouts.
clear composition and stronger separation from the desktop. **Scene**
surfaces — the lock screen and the power menu — have no panel at all: the
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 — bar, rail, power menu, toasts, OSD per screen
shell.qml entrypoint — hull, bar, rail, dashboard, power menu, toasts, OSD per screen
config/ palette, type scale, geometry, motion curves
components/ the P5 primitives: panels, sliders, list rows, print-shadowed text
components/ the primitives: panels, sliders, list rows, display text
services/ data sources, all singletons
bar/ the two surfaces and their modules
popouts/ hover panels hanging off rail and bar modules
overlays/ power menu, notification toasts, volume/brightness OSD
lock/ session lock
backdrop/ the wallpaper, drawn by the shell
assets/ halftone and stripe tiles, generated with ImageMagick
```
@@ -69,13 +74,40 @@ assets/ halftone and stripe tiles, generated with ImageMagick
callers set implicit sizes from their own layout (`row.implicitWidth +
contentPad * 2`), which keeps the shape maths out of binding loops.
## Popouts
Hover panels (`components/Popout.qml`) are part of the hull, not cards
floating near their trigger. Each one sits flush against the frame edge it
opens from, is painted in the hull colour, joins the frame through concave
fillets, carries the rim round its open side, and slides out from behind the
frame. `PopoutSurface` notices it is inside a `Popout` and leaves the fill and
the frame to the hull; in the launcher and the ask panel it is still a
free-standing panel with corner ticks.
Placement is explicit: centred on the trigger, then — if that would land the
panel within `dockReach` of one of the opening's corners — docked into the
corner instead, attached to the bar and the rail at once (`dock` is `top`,
`right`, `topRight` or `bottomRight`). Without docking, a panel near the rail
left a thin strip of opening between itself and the rail.
## Dashboard
`dashboard/Dashboard.qml` is a drawer that slides down out of the underside of
the bar — clock click, `SUPER + D`, or `qs ipc call shell dashboard`. It is
painted in the hull colour and joins the bar through two concave fillets, so it
reads as more of the hull rather than as a floating card (caelestia's idea).
Four columns divided by hairlines: operator and time, the month, the system
meters, and what is playing. Escape or focusing anything else puts it away; a
`HyprlandFocusGrab` handles the latter, and `Actions.dashboardClosedAt` stops
the clock click that cleared the grab from opening it straight back up.
## Modules
| Module | Where | Left click | Right click | Middle | Scroll | Hover |
|---|---|---|---|---|---|---|
| Sigil | bar | app menu | reload shell | | | |
| Workspaces | bar | switch | | | | |
| Clock | bar | pin calendar | | | | calendar |
| Clock | bar | dashboard | | | | calendar |
| Network | rail | pin details | | | | state + networks |
| Bluetooth | rail | pin details | | | | state + devices |
| Volume | rail | pin audio | | | volume | state + scroll hint + audio |
@@ -112,6 +144,7 @@ Set in `~/.config/hypr/lua/binds/shell.lua`:
- `Super+Ctrl+L` — lock
- `Super+P` — power menu
- `Super+Shift+N` — toggle do not disturb
- `Super+Shift+I` — stay awake (no dim, lock or screen-off until toggled back)
- `Super+Ctrl+Shift+N` — clear notifications
- `Super+A` — ask panel
- `Super+Shift+R` — reload the shell
@@ -122,6 +155,8 @@ All of these go through Quickshell's IPC, so they work from any script:
qs ipc call shell lock
qs ipc call shell power
qs ipc call shell toggleDnd
qs ipc call shell toggleStayAwake
qs ipc call shell wallpaper ~/.wallpapers/space-1.jpg
qs ipc call shell ask
qs ipc call shell askAbout "why is the sky blue"
qs ipc call shell clearNotifications
@@ -180,6 +215,34 @@ NetworkManager for Wi-Fi, UPower for battery, MPRIS for media, and Quickshell's
own notification server. These are event-driven, so the bar and OSD react the
instant a volume key is pressed instead of on the next poll.
## Wallpaper
`backdrop/Backdrop.qml` draws it on the background layer; there is no
hyprpaper. The picture is drawn 8% larger than the screen and drifts with the
workspace: 1 sits at the top of it and each step down moves it a fixed
distance, a little slower than Hyprland's own slide, so the windows arrive
first and the scene behind catches up. It never moves on its own, so an idle
desktop costs the compositor nothing. While the workspace holds windows it
settles a shade darker.
The choice is one line in `quickshell/wallpaper` (`~` allowed), watched live.
`qs ipc call shell wallpaper <path>` rewrites it.
## Idle
`services/Idle.qml`, on Wayland's idle-notify protocol instead of hypridle:
| After | Does |
|---|---|
| 4 min | the screen dims slowly (`overlays/IdleDim.qml`); any input lifts it |
| 5 min | lock |
| 6 min | displays off; any input wakes them |
| 15 min | suspend, on battery only |
Every stage respects idle inhibitors, so a fullscreen video holds them all
off. `Super+Shift+I` does the same by hand and puts a cup on the rail while it
is on.
## Lock screen
Authenticates through `/etc/pam.d/hyprlock`, which on this box is just
@@ -187,6 +250,14 @@ Authenticates through `/etc/pam.d/hyprlock`, which on this box is just
styled in an ordinary window (`qs -p lockpreview.qml`) without locking the
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, "Take your
time." glowing faintly with the odd flicker (`components/GlowText.qml`), 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
session secured by protocol and there is no unlocker left to talk to — recovery
means restarting Hyprland from a TTY.
@@ -194,6 +265,9 @@ means restarting Hyprland from a TTY.
## What was retired
- `waybar.service` — disabled. Its config is still in `~/.config/waybar`.
- `hyprpaper` — replaced by `backdrop/`. Turn its unit off with
`systemctl --user disable --now hyprpaper`.
- `hypridle` — never installed; `services/Idle.qml` does the job.
- `dunst.service` — masked, so it cannot be D-Bus activated and steal
`org.freedesktop.Notifications`. Undo with
`systemctl --user unmask dunst.service`.
@@ -204,40 +278,45 @@ means restarting Hyprland from a TTY.
`power-profiles-daemon` is not installed (TLP is), so the power-profile
switching in `services/Battery.qml` stays dormant. Nothing else depends on it.
The display face is Archivo Black, set italic almost everywhere. Change
`Theme.fontDisplay` and everything follows.
The display face is Noto Sans, upright and widely tracked — the nearest free
face to NieR's FOT-Rodin that is already installed. Change `Theme.fontDisplay`
and everything follows.
## Palette discipline
Catppuccin Mocha, spent on two hues. **Hue is meaning**; nothing gets a colour
for decoration.
YoRHa dark: NieR:Automata's interface language, painted from the wallpaper
(`~/.wallpapers/space-1.jpg`) rather than from the game's beige. Two hues, and
**hue is meaning**; nothing gets a colour for decoration.
- **Blue (`accent`, Mocha blue)** means live: the focused workspace, the
selected row, the hovered control, a channel that is on, an unread badge.
- **Mauve (`alarm`, Mocha mauve)** means wrong or destructive: critical battery,
an urgent workspace, a critical notification, reboot and shut down.
- **Grey** is everything else, and that includes *off*. A muted speaker, a
muted mic and Do Not Disturb drop to `muted` grey with their off glyph. They
used to be painted in the accent, which made a silenced channel look the most
alive thing on the rail.
- **Crust (`ink`)** is structure: the hard keyline, the offset under display
type and panels, the text on an accent slab.
- **Light (`accent`, `#eef1ea`)** means live: the focused workspace, the
selected row, the hovered control, a channel that is on, an unread badge. It
is the planet's white, barely a colour at all — NieR marks attention with
brightness and inversion, not with hue.
- **Red (`alarm`, `#c8684f`)** means wrong or destructive: critical battery, an
urgent workspace, a critical notification, reboot and shut down. It is the
hull's indicator lights, the only warm thing in the wallpaper.
- **Grey-green** is everything else, and that includes *off*. A muted speaker,
a muted mic and Do Not Disturb drop to `muted` grey with their off glyph.
- **Ink** is the text on an inverted slab and the faint drop under display
type.
**Selection is an inverted slab.** Focus and selection fill with the accent and
print their text in crust (about 9:1). Hover stays on the surface tone with an
accent mark. Destructive-active is the same slab in mauve. The `treatment*`
tokens in `Theme.qml` encode all of this; use them rather than picking colours
per widget.
**Selection is the NieR cursor.** Focus and selection invert the row: a slab of
the light with ink punched out of it. Hover lifts the surface one step and
brings the marker up to full light, but never inverts. Destructive-active is
the same slab in red. The `treatment*` tokens in `Theme.qml` encode all of
this; use them rather than picking colours per widget.
**Print, not blur.** Display text (`SplitText`) carries one hard crust offset.
The blue and mauve ghosts appear only while `glitch()` runs. Drawn permanently
1.6px off the body, they read as a focus problem on Mocha's pastels rather than
as misregistered print. The lock screen and the power menu are the exceptions;
they are the cinematic surfaces and keep their split.
**Dots, not slashes.** The current row, a button under the pointer, the end of
a header rule and a notification's urgency carry a small dot (`Theme.marker`).
Borders are structure and stay neutral.
**Texture is tiered.** Chrome (bar, rail, tooltips) and transient panels
(popouts, OSD, toasts) are flat. Stripes and halftone live only in
`P5Backdrop`, behind the lock screen and the power menu.
**Flicker, not print.** Display text (`SplitText`) is upright, tracked by
`Theme.tracking`, on a one-pixel soft drop. The two ghosts either side of it
appear only while `glitch()` runs — NieR's brief data-corruption flicker.
**Texture is rare.** Chrome (bar, rail, tooltips), transient panels (popouts,
OSD, toasts) and the two scene surfaces are flat. Depth comes from the hull's
dark, the rim hairline and the wallpaper, not from patterns.
**Type has a floor.** Nothing renders below `Theme.fsMicro` (10px).
@@ -245,60 +324,101 @@ they are the cinematic surfaces and keep their split.
| File | Covers |
|---|---|
| `quickshell/config/Theme.qml` | the shell: `accent`, `alarm` and the neutral ramp |
| `hypr/lua/palette.lua` | window borders, groups, shadows |
| `alacritty/catppuccin-mocha.toml` | the terminal (stock Mocha, cursor in the accent) |
| `quickshell/config/Theme.qml` | the shell: `accent`, `alarm`, the neutral ramp, the hull |
| `hypr/lua/palette.lua` | window borders, groups |
| `alacritty/yorha.toml` | the terminal |
| `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 |
| `gtk-3.0/gtk.css`, `gtk-4.0/gtk.css` | GTK and libadwaita apps |
| `qt6ct/colors/yorha.conf` | Qt apps |
| `zathura/yorha` | the document viewer |
Everything else derives. `accentDim` and `accentSoft` come off `accent`, `glow`
*is* `accent`, and `primary` is plain `text`. `Theme.heat()` is a continuous
text → blue → mauve ramp, so a CPU figure that has gone mauve means the same
thing as a battery that has gone mauve. Tray icons are flattened to one colour
(subtext, the accent on hover) for the same reason: app brand colours wreck the
palette faster than anything else in the shell. nm-applet and blueman are
hidden from the tray, since the rail already shows network and bluetooth, but
keep running as the secret and pairing agents.
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
has gone red means the same thing as a battery that has gone red. Tray icons
are flattened to one colour (subtext, the accent on hover) for the same reason:
app brand colours wreck the palette faster than anything else in the shell.
nm-applet and blueman are hidden from the tray, since the rail already shows
network and bluetooth, but keep running as the secret and pairing agents.
## The rest of the desktop
- **Hyprland** — `hypr/lua/look.lua`. No rounding, no blur, both opacities at
`1.0`. The active border runs blue into lavender and the inactive border is
`surfaceAlt`. The window shadow is a sharp 4px crust offset, the same printed
drop the shell's panels carry, not a soft glow.
- **Alacritty** — `alacritty/catppuccin-mocha.toml`, selected by the `import`
in `alacritty.toml`. Stock Mocha except for the cursor, which is the accent.
`takemi.toml` is left in place, unreferenced.
- **Neovim** — the `catppuccin` plugin, flavour mocha, configured in
`nvim/lua/plugins/ui.lua`. Blue is the accent there too. `colors/takemi.lua`
is left in place, unused.
- **Fish** — `fish/conf.d/takemi_theme.fish` is Catppuccin Mocha's fish theme
with the Takemi selection rows: a solid accent block with crust punched out.
- **Hyprland** — `hypr/lua/look.lua`. Windows round at 8px, concentric with
the opening's 18px corners at a 10px gap (`Theme.frameRadius - gaps_out`);
change one and the others follow. No blur, both opacities at `1.0`, 1px
borders running from the light into the suit's grey. The shadow is soft and
wide, so windows float in the opening. Group tabs are thin rounded lines with
no titles, the current one in the light; the bar writes the title once and
adds the window's place in the group (`2/3`). Motion
(`hypr/lua/animations.lua`) glides and settles with no overshoot: windows
resolve in from a hair smaller, workspaces drift a short way vertically and
fade (sliding behind the hull), the `SUPER+S` scratchpad lowers out from
under the bar like the dashboard, and layers only fade, since quickshell
animates its own surfaces.
- **Alacritty** — `alacritty/yorha.toml`, selected by the `import` in
`alacritty.toml`. Muted earth hues on the void; selection inverts.
Alacritty only live-reloads when `alacritty.toml` itself changes, so touch
it after editing the palette.
The Catppuccin and `takemi.toml` files are left in place, unreferenced.
- **Neovim** — `nvim/lua/yorha.lua` (loaded by `colors/yorha.lua`, with a
matching lualine theme). Syntax in the terminal's earth hues, floats as hull
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** — the `adw-gtk3-dark` theme (package `adw-gtk-theme`) for the
libadwaita shapes, recoloured by `gtk-3.0/gtk.css` and `gtk-4.0/gtk.css`.
libadwaita apps such as Nautilus read the same overrides as CSS variables.
Selection is the light slab with ink text; only destructive actions are red.
The theme name is also in `xsettingsd/xsettingsd.conf` and has to be set with
`gsettings` for portal-aware apps.
- **Qt** — qt6ct with `qt6ct/colors/yorha.conf`, the same palette in
`QPalette` roles, drawn with the Darkly style (package `darkly-bin`) for
rounded controls. Fusion is the fallback if Darkly is missing.
- **zathura** — `zathura/yorha`, included from `zathurarc`. Pages are
recoloured onto the hull with text in bone; images keep their colours.
Search hits are a soft bone wash, and the current hit is sand.
- **Fish** — `fish/conf.d/yorha_theme.fish`.
- **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
`fuzzel --dmenu` where a script needs a picker.
- **No glass anywhere.** Alacritty is `opacity = 1.0, blur = false`, and
Hyprland's `decoration:blur` is disabled. Blur is the signature of the
frosted-glass look, which is the opposite of flat ink on flat ground — and
with nothing translucent left it was costing three render passes for no
visible result.
Hyprland's `decoration:blur` is disabled.
There are **no gradients in this shell**; every fill is flat. `Theme.sheen`
still exists as a no-op property because a teal-to-transparent wash on every
panel was once the biggest source of murk in the design.
There are **no gradients in the chrome**; every fill is flat. `Theme.sheen`
still exists as a no-op property so old call sites need not be chased down.
## Geometry discipline
**One angle, one chamfer, everywhere.** `Theme.skew` is `tan(14°)` and every
edge in the shell leans by exactly that, in the same direction; every corner is
chopped by exactly `Theme.cut`. A vertical panel (the rail) leans its horizontal
edges by a fraction of its *width*, which works out to the same 14°.
**The hull.** Borrowed from caelestia's screen border: the bar, the rail and a
thin band down the left and along the bottom (`Theme.frame`) are one surface,
drawn by `bar/Frame.qml`, and the desktop is a rounded opening cut out of it
(`Theme.frameRadius`). Read against the wallpaper, you are inside the ship and
the windows sit in the porthole. The hull is on the top layer, over the
windows, with a transparent opening and no input region, so a window moving
past the opening's edge — a workspace switch, the scratchpad drawer — passes
behind the frame rather than over the bar. `hypr/lua/rules/layers.lua` orders
the bar and the rail above it. The bar and the rail draw
no surface of their own and ignore exclusive zones; Frame reserves all four
zones itself, so every piece is placed from the screen edge.
This is deliberate and was learned the hard way. An earlier pass gave every
module a different lean, chamfer and height from per-index variance tables, on
the theory that Persona geometry is "chaotic". It isn't — Persona UI is
meticulously clean, and the chaos lives in composition and motion, not in each
element being a different shape. Randomised geometry just reads as noise. If you
find yourself adding a variance table, don't.
**Soft, everywhere else.** The porthole is the shape the desktop is built
around, and every smaller surface is a gentler echo of it. `Theme.skew` and
`Theme.cut` are `0`; `P5Panel` and `Skew` draw `Geom.roundedRect` with
`Theme.radiusS` (pills, chips, meter tracks), `radiusM` (rows, buttons) or
`radiusL`. `radiusL` is `frameRadius - padS`: anything inset by `padS` inside
one of the frame's curves — a hovered notification in the drawer's corner —
gets a corner concentric with it, so the gap stays even and nothing pokes past
the curve. Free-standing panels (launcher, ask) take `frameRadius` itself.
Toggles, transport buttons, workspace pips and today's date are pills or
discs; album art is rounded via `ClippingRectangle`.
Each surface is **one continuous slab**, with modules divided by sheared
hairline rules rather than by gaps. A row of separate floating cards reads as
clutter; that is what the dividers exist to avoid.
**Motion glides.** No overshoot anywhere: things ease in on `OutCubic` or
`OutExpo` and settle. Nothing scales up or nudges sideways on hover either;
hover brightens and marks, the way NieR's menus do. Meters are one continuous
rounded line that glides to its level, and the bar's player shows a
five-line signal meter.
Each surface is **one continuous slab**, with modules divided by hairline rules
rather than by gaps. A row of separate floating cards reads as clutter; that is
what the dividers exist to avoid.
+1 -1
View File
@@ -394,7 +394,7 @@ PanelWindow {
NumberAnimation {
target: drop; property: "y"
from: -shell.height; to: 0
duration: Theme.durSlow; easing.type: Easing.OutBack
duration: Theme.durSlow; easing.type: Easing.OutCubic
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

+90
View File
@@ -0,0 +1,90 @@
import QtQuick
import Quickshell
import Quickshell.Hyprland
import Quickshell.Wayland
import "root:/config"
import "root:/services"
// The wallpaper, seen through the porthole. It is drawn a little larger than
// the screen and drifts when the workspace changes: windows slide past
// vertically (hypr/lua/animations.lua) and the scene behind them moves a few
// pixels the same way and a little later, the way distant things do out of a
// moving window. Only on switches — it never moves on its own, so an idle
// desktop costs the compositor nothing.
//
// It also settles a shade darker while the workspace holds windows, so the
// band of wallpaper round them reads as the far side of the glass rather than
// as part of the desktop.
PanelWindow {
id: root
required property var modelData
screen: modelData
WlrLayershell.layer: WlrLayer.Background
WlrLayershell.namespace: "takemi-backdrop"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
exclusionMode: ExclusionMode.Ignore
anchors { top: true; bottom: true; left: true; right: true }
color: Theme.ink
mask: Region {}
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen)
readonly property HyprlandWorkspace workspace: root.monitor?.activeWorkspace ?? null
// Overscan: how much bigger than the screen the picture is drawn, which is
// the room it has to drift in.
readonly property real overscan: 1.08
readonly property real slack: root.height * (root.overscan - 1)
// Workspaces 1–10 spread across the slack, so 1 sits at the top of the
// picture and each step down moves it a fixed distance. Special and
// named workspaces (negative ids) leave it where it was.
property real drift: 0
readonly property int wsId: root.workspace?.id ?? 1
onWsIdChanged: if (root.wsId > 0) root.drift = Math.min(root.wsId - 1, 9) / 9
readonly property bool occupied: (root.workspace?.toplevels?.values?.length ?? 0) > 0
Image {
id: picture
width: root.width * root.overscan
height: root.height * root.overscan
x: -(width - root.width) / 2
y: -root.drift * root.slack
source: Wallpaper.forMonitor(root.screen?.name ?? "")
sourceSize: Qt.size(width, height)
fillMode: Image.PreserveAspectCrop
asynchronous: true
smooth: true
cache: false
opacity: status === Image.Ready ? 1 : 0
Behavior on opacity {
NumberAnimation { duration: Theme.durLazy; easing.type: Easing.OutCubic }
}
// Longer than Hyprland's 550ms workspace slide, on the same curve, so
// the windows arrive first and the scene catches up.
Behavior on y {
NumberAnimation {
duration: 900
easing.type: Easing.BezierSpline
easing.bezierCurve: [0.35, 0.0, 0.15, 1.0, 1.0, 1.0]
}
}
}
Rectangle {
anchors.fill: parent
color: Theme.ink
opacity: root.occupied ? 0.18 : 0
Behavior on opacity {
NumberAnimation { duration: Theme.durLazy; easing.type: Easing.InOutSine }
}
}
Component.onCompleted: root.drift = Math.min(Math.max(root.wsId, 1) - 1, 9) / 9
}
+43 -6
View File
@@ -20,6 +20,31 @@ Item {
|| toplevel.monitor.id === root.monitor.id)
readonly property string incomingTitle: mine && toplevel.title ? toplevel.title : "—"
// Where the focused window sits in its group, as "2/3", or "" when it is
// not grouped. Hyprland's group bar draws only indicator lines (see
// hypr/lua/look.lua), so the bar is the one place the title is written.
readonly property string groupLabel: {
const ipc = root.mine ? root.toplevel.lastIpcObject : null;
const members = ipc?.grouped ?? [];
if (members.length < 2) return "";
const bare = a => String(a).replace(/^0x/, "");
const at = members.findIndex(m => bare(m) === bare(root.toplevel.address));
return (at < 0 ? "?" : at + 1) + "/" + members.length;
}
// lastIpcObject is a snapshot, refreshed only on request. Ask again
// whenever focus or group membership may have changed.
Connections {
target: Hyprland
function onRawEvent(event) {
const n = event.name;
if (n === "activewindowv2" || n === "togglegroup"
|| n === "moveintogroup" || n === "moveoutofgroup"
|| n === "closewindow" || n === "openwindow")
Hyprland.refreshToplevels();
}
}
property string displayedTitle: "—"
// Which toplevel the landing transition last played for. The landing is a
@@ -30,7 +55,8 @@ Item {
// The bar shrinks this when the three clusters would otherwise collide.
property real maxWidth: 240
implicitWidth: Math.min(maxWidth, Math.max(70, label.implicitWidth + 4))
implicitWidth: Math.min(maxWidth,
Math.max(70, label.implicitWidth + 4 + (groupTag.visible ? groupTag.width + Theme.padS : 0)))
implicitHeight: 20
Behavior on implicitWidth {
@@ -45,24 +71,23 @@ Item {
Text {
id: label
anchors.verticalCenter: parent.verticalCenter
width: parent.width
width: parent.width - (groupTag.visible ? groupTag.width + Theme.padS : 0)
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
}
// Accent duplicate: visible only during the landing transition. The
// small opposing offset gives the title Persona's rough print snap
// small opposing offset gives the title NieR's brief corruption flicker
// without leaving permanent motion in the bar.
Text {
id: ghost
anchors.verticalCenter: parent.verticalCenter
width: parent.width
width: label.width
text: root.displayedTitle
color: Theme.accent
opacity: 0
@@ -70,6 +95,18 @@ Item {
font: label.font
renderType: Text.NativeRendering
}
Text {
id: groupTag
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
visible: root.groupLabel !== ""
text: root.groupLabel
color: Theme.muted
font.family: Theme.fontMono
font.pixelSize: Theme.fsMicro
renderType: Text.NativeRendering
}
}
// Adopt the settled caption, and stamp it only when focus actually moved.
@@ -144,7 +181,7 @@ Item {
NumberAnimation {
target: label; property: "x"
from: -10; to: 0
duration: Theme.durBase; easing.type: Easing.OutBack
duration: Theme.durBase; easing.type: Easing.OutCubic
}
NumberAnimation {
target: label; property: "opacity"
+13 -33
View File
@@ -27,29 +27,16 @@ PanelWindow {
right: true
}
margins {
top: Theme.barMargin
left: Theme.barMargin
right: Theme.barMargin
}
// Flush with the screen edge and drawn on nothing: the hull behind it is
// painted by Frame, which is what joins the bar, the rail and the border
// into one surface. Exclusive zones are Frame's job too, so every piece
// of the hull is placed from the screen edge rather than from whichever
// piece the compositor happened to arrange first.
exclusionMode: ExclusionMode.Ignore
implicitHeight: Theme.barHeight
exclusiveZone: Theme.barHeight + Theme.barMargin
color: "transparent"
// 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
active: true
slashWidth: 5
}
readonly property real edgeInset: Theme.barHeight * Theme.skew + Theme.padM
readonly property real edgeInset: Theme.padM
// ------------------------------------------------------------------ left
Row {
@@ -108,25 +95,18 @@ PanelWindow {
}
}
// A hairline rule between modules, sheared to the house angle. This is what
// divides the bar now — gaps between cards are what made it look scattered.
// A hairline rule between modules. This is what divides the bar — gaps
// between cards are what made it look scattered.
component Divider: Item {
anchors.verticalCenter: parent.verticalCenter
implicitWidth: Theme.padM * 2
implicitHeight: Theme.barHeight
// A leaning hairline needs a box wide enough to hold the lean: at 20px
// tall the top edge sits 20 * skew ≈ 5px right of the bottom one, so a
// 1px-wide Skew folded into a bowtie instead. Width is the lean plus the
// stroke the rule should actually read as.
readonly property real ruleWeight: 1.5
readonly property real ruleHeight: 20
Skew {
Rectangle {
anchors.centerIn: parent
width: parent.ruleHeight * Theme.skew + parent.ruleWeight
height: parent.ruleHeight
color: Theme.alpha(Theme.outline, 0.5)
width: 1
height: 16
color: Theme.alpha(Theme.outline, 0.6)
}
}
}
+12 -11
View File
@@ -3,8 +3,10 @@ import Quickshell
import "root:/config"
import "root:/components"
import "root:/popouts"
import "root:/services"
// Centre-stage clock. Hovering drops the calendar.
// Centre-stage clock. Hovering drops the calendar; clicking drops the
// dashboard, which carries the calendar too.
BarPill {
id: root
@@ -20,7 +22,7 @@ BarPill {
precision: SystemClock.Minutes
}
onClicked: calendar.pinned = !calendar.pinned
onClicked: Actions.toggleDashboard()
Row {
id: layout
@@ -51,16 +53,15 @@ BarPill {
}
}
// Declared with room for its own lean, so the slash sits inside the box
// the Row gave it instead of hanging several px into the date column.
Skew {
// A hairline between the date and the time.
Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: 22 * Theme.skew + 3
height: 22
color: Theme.alpha(Theme.accent, 0.6)
width: 1
height: 18
color: Theme.alpha(Theme.outline, 0.6)
}
// Archivo Black's digits are not all the same width, so a clock sized to
// The display face's digits are not all the same width, so a clock sized to
// its own text changed width as the minutes rolled over — 11:11 is
// several px narrower than 08:48 — and shoved the rest of the bar
// around. Reserve the widest possible reading and centre inside it.
@@ -70,7 +71,6 @@ BarPill {
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsTitle
font.weight: Theme.weightDisplay
font.italic: true
font.letterSpacing: 0.5
}
@@ -117,7 +117,8 @@ BarPill {
CalendarPopout {
id: calendar
anchorItem: root
triggerHovered: root.hovered
// Not while the dashboard is down: it already shows the month.
triggerHovered: root.hovered && !Actions.dashboardOpen
clockDate: clock.date
}
}
+124
View File
@@ -0,0 +1,124 @@
import QtQuick
import QtQuick.Shapes
import Quickshell
import Quickshell.Wayland
import "root:/config"
// The hull: one surface for the bar, the rail and the thin band down the left
// and along the bottom, with the desktop as a rounded opening cut out of it.
// The idea is caelestia's screen border; the reading is the wallpaper's — you
// are inside the ship, and the windows sit in the porthole.
//
// The hull lives on the top layer, over every window, and its opening is
// transparent with no input region, so it covers nothing inside the opening
// and catches no clicks. Being above the windows is the point: a workspace
// switch slides windows *behind* the frame, the way things pass behind the
// rim of a porthole, instead of drawing over the bar. A fullscreen window
// still covers it, since Hyprland draws fullscreen above the top layer.
// hypr/lua/rules/layers.lua orders it under the bar and the rail.
//
// The bar and the rail draw no surface of their own and ignore exclusive
// zones, so every piece is placed from the screen edge. The four zones are
// reserved here instead, by windows that draw nothing — that way the usable
// area is exactly the opening, whichever surface the compositor arranges
// first.
Scope {
id: root
required property var modelData
PanelWindow {
id: hull
screen: root.modelData
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.namespace: "takemi-frame"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
exclusionMode: ExclusionMode.Ignore
anchors {
top: true
bottom: true
left: true
right: true
}
color: "transparent"
mask: Region {}
// The opening, in window coordinates.
readonly property real x0: Theme.frame
readonly property real y0: Theme.barHeight
readonly property real x1: width - Theme.railWidth
readonly property real y1: height - Theme.frame
readonly property real r: Theme.frameRadius
readonly property string opening: {
const x0 = hull.x0, y0 = hull.y0, x1 = hull.x1, y1 = hull.y1, r = hull.r;
return `M ${x0 + r} ${y0} H ${x1 - r} A ${r} ${r} 0 0 1 ${x1} ${y0 + r}`
+ ` V ${y1 - r} A ${r} ${r} 0 0 1 ${x1 - r} ${y1}`
+ ` H ${x0 + r} A ${r} ${r} 0 0 1 ${x0} ${y1 - r}`
+ ` V ${y0 + r} A ${r} ${r} 0 0 1 ${x0 + r} ${y0} Z`;
}
Shape {
anchors.fill: parent
preferredRendererType: Shape.CurveRenderer
// The hull itself: the whole screen, minus the opening.
ShapePath {
fillColor: Theme.hull
fillRule: ShapePath.OddEvenFill
strokeColor: "transparent"
PathSvg {
path: `M 0 0 H ${hull.width} V ${hull.height} H 0 Z ` + hull.opening
}
}
// The rim — a hairline round the opening, the lit edge of the
// porthole in the wallpaper.
ShapePath {
fillColor: "transparent"
strokeColor: Theme.rim
strokeWidth: 1
PathSvg {
path: hull.opening
}
}
}
}
ExclusionZone {
anchors.top: true
exclusiveZone: Theme.barHeight
}
ExclusionZone {
anchors.right: true
exclusiveZone: Theme.railWidth
}
ExclusionZone {
anchors.bottom: true
exclusiveZone: Theme.frame
}
ExclusionZone {
anchors.left: true
exclusiveZone: Theme.frame
}
component ExclusionZone: PanelWindow {
screen: root.modelData
WlrLayershell.layer: WlrLayer.Bottom
WlrLayershell.namespace: "takemi-frame-zone"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
color: "transparent"
mask: Region {}
implicitWidth: 1
implicitHeight: 1
}
}
+28 -32
View File
@@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Shapes
import QtQuick.Window
import Quickshell
import Quickshell.Widgets
import "root:/config"
import "root:/components"
import "root:/services"
@@ -41,40 +42,28 @@ BarPill {
anchors.left: parent.left
spacing: Theme.padS
// Album art, chopped into the house shape.
Item {
// Album art, rounded, in a hairline frame.
ClippingRectangle {
id: artFrame
anchors.verticalCenter: parent.verticalCenter
width: 24
height: 24
Shape {
id: artMask
anchors.fill: parent
visible: false
layer.enabled: true
layer.smooth: true
preferredRendererType: Shape.CurveRenderer
ShapePath {
fillColor: "white"
strokeColor: "transparent"
PathPolyline {
path: Geom.chamfer(Geom.parallelogram(24, 24, 0.18), 5, [1, 3])
}
}
}
radius: Theme.radiusS
color: Theme.surface
border.width: 1
border.color: Theme.overlay
Image {
id: art
anchors.fill: parent
anchors.margins: 1
source: Media.artUrl
fillMode: Image.PreserveAspectCrop
cache: true
asynchronous: true
visible: status === Image.Ready
layer.enabled: true
layer.smooth: true
layer.effect: MaskedArt { maskItem: artMask }
sourceSize.width: 48
sourceSize.height: 48
}
// Fallback: the source app's glyph.
@@ -109,7 +98,6 @@ BarPill {
font.pixelSize: Theme.fsSmall
font.family: Theme.fontDisplay
font.weight: Font.DemiBold
font.italic: true
renderType: Text.NativeRendering
}
@@ -125,8 +113,10 @@ BarPill {
}
}
// Compact play-state equaliser. It moves only while audio is playing
// and freezes into three quiet bars when paused.
// Signal meter: five thin rounded bars, NieR's audio readout rather than a
// chunky equaliser. It moves only while audio is playing and settles
// into a row of dots when paused — the channel is open, there
// is just nothing on it.
//
// Sampled from a timer rather than tweened by an infinite
// NumberAnimation. DP-1 runs at 240 Hz, and a frame-synced animation
@@ -145,7 +135,7 @@ BarPill {
Row {
id: eq
anchors.verticalCenter: parent.verticalCenter
spacing: 2
spacing: 3
// Sampling interval — the one knob trading smoothness against cost.
// Frames are the whole expense: a full-bar repaint runs about 0.3% of
@@ -164,8 +154,8 @@ BarPill {
// sine would, is three times the motion and reads far busier than
// this meter is supposed to.
function level(i: int): real {
const lo = 4 + (i * 5) % 13; // old first NumberAnimation `to`
const hi = 14 - (i * 4) % 9; // old second NumberAnimation `to`
const lo = 2 + (i * 3) % 5;
const hi = 14 - (i * 4) % 7;
const d1 = 320 + i * 90; // ...and their durations
const d2 = 280 + i * 70;
@@ -189,12 +179,13 @@ BarPill {
}
Repeater {
model: 3
model: 5
Rectangle {
required property int index
width: 3
height: Media.playing ? eq.level(index) : 6
width: 2
radius: 1
height: Media.playing ? eq.level(index) : 2
anchors.verticalCenter: parent.verticalCenter
color: Media.playing ? Theme.primary : Theme.muted
@@ -213,11 +204,16 @@ BarPill {
function onTrackChanged() { titleGlitch.restart(); }
}
// A new track resolves in, the way NieR text does: a brief dip and back.
SequentialAnimation {
id: titleGlitch
NumberAnimation {
target: artFrame; property: "rotation"
from: -8; to: 0; duration: Theme.durSlow; easing.type: Easing.OutBack
target: meta; property: "opacity"
to: 0.25; duration: 60
}
NumberAnimation {
target: meta; property: "opacity"
to: 1; duration: Theme.durSlow; easing.type: Easing.OutCubic
}
}
+23 -35
View File
@@ -30,30 +30,14 @@ PanelWindow {
right: true
}
margins {
top: Theme.barHeight + Theme.barMargin * 2
bottom: Theme.barMargin
right: Theme.barMargin
}
// Hangs from the underside of the bar, flush with the screen edge. Like
// the bar it draws no surface of its own; see Frame.
margins.top: Theme.barHeight
exclusionMode: ExclusionMode.Ignore
implicitWidth: Theme.railWidth
exclusiveZone: Theme.railWidth + Theme.barMargin
color: "transparent"
// The same calm permanent-chrome preset used by the top bar.
ChromeSurface {
id: slab
anchors.fill: parent
vertical: true
lean: Theme.skew
chop: Theme.cut
chopCorners: [1, 3]
padding: 0
active: true
slashWidth: 5
}
readonly property real edgeInset: Theme.railWidth * Theme.skew + Theme.padM
readonly property real edgeInset: Theme.padM
// ------------------------------------------------------------------- top
Column {
@@ -153,6 +137,19 @@ PanelWindow {
Divider { visible: tray.visible }
// Only there while the idle chain is held off by hand, so it is
// obvious why the screen never locked.
RailIcon {
id: awakeIcon
visible: Idle.stayAwake
icon: "󰅶"
label: "Stay awake"
statusText: "Screen will not dim or lock"
hint: "Click to allow sleep"
live: true
onClicked: Idle.toggleStayAwake()
}
RailIcon {
id: bellIcon
icon: Notifs.icon
@@ -183,25 +180,16 @@ PanelWindow {
}
}
// The rail's counterpart to the bar's divider: a horizontal hairline,
// sheared to the same angle the rail's own edges lean at.
// The rail's counterpart to the bar's divider: a horizontal hairline.
component Divider: Item {
implicitWidth: Theme.railWidth
implicitHeight: Theme.padM + 4
// Same correction as the bar's divider, on the other axis: a 22px rule
// rises 22 * skew ≈ 5px across its run, so the box has to be that tall
// plus the weight of the stroke. At height 1 the polygon inverted and
// the rule rendered as a stubby wedge sitting left of centre.
readonly property real ruleWeight: 1.5
readonly property real ruleWidth: 22
Skew {
Rectangle {
anchors.centerIn: parent
width: parent.ruleWidth
height: parent.ruleWidth * Theme.skew + parent.ruleWeight
vertical: true
color: Theme.alpha(Theme.outline, 0.5)
width: 16
height: 1
color: Theme.alpha(Theme.outline, 0.6)
}
}
+4 -9
View File
@@ -122,12 +122,10 @@ Item {
}
// Leading-edge wipe. On the rail this runs vertically down the left side,
// mirroring the slash P5Panel puts on horizontal modules.
// mirroring the accent bar 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.
// at x = 0 this landed exactly on the slab's own border.
//
// 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).
@@ -137,7 +135,8 @@ Item {
anchors.leftMargin: root.wipeInset
anchors.verticalCenter: parent.verticalCenter
width: 2
height: root.hovered ? root.height - 4 : 0
radius: 1
height: root.hovered ? Theme.marker * 2 : 0
color: Theme.glow
Behavior on height {
@@ -145,10 +144,6 @@ Item {
}
}
scale: root.hovered ? 1.08 : 1.0
Behavior on scale {
NumberAnimation { duration: Theme.durBase; easing.type: Easing.OutBack }
}
onAlarmChanged: if (alarm) glyph.bump()
onLiveChanged: if (live) glyph.bump()
-1
View File
@@ -121,7 +121,6 @@ BarPill {
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsBody
font.weight: Theme.weightDisplay
font.italic: true
font.letterSpacing: 0.5
renderType: Text.NativeRendering
+3 -21
View File
@@ -12,33 +12,15 @@ BarPill {
required property var screenRef
accent: Theme.accent
slash: true
padding: Theme.padS
lean: Theme.skew
implicitWidth: Theme.barHeight + contentPad
onClicked: Actions.toggleLauncher()
onRightClicked: Actions.reloadShell()
// The wordmark. Was the Death arcana skull (nf-md-skull, U+F068C) — a
// pictogram in a shell that otherwise says everything in heavy italic
// type. SplitText puts it in the display face with the ink print
// offset every other heading carries, so the house mark is built out of
// the same parts as the rest of the bar.
SplitText {
id: mark
Porthole {
anchors.centerIn: parent
text: "P5"
pixelSize: Theme.fsLarge
letterSpacing: 0
color: root.hovered ? Theme.accent : Theme.primary
}
// Kick the ghosts out and let them snap back. The skull could spin a full
// turn and still read as a skull; a wordmark upside down just reads as
// broken, so the click gets the glitch instead.
Connections {
target: root
function onClicked() { mark.glitch(); }
size: 18
lit: root.hovered
}
}
-4
View File
@@ -77,10 +77,6 @@ Column {
}
}
scale: itemHover.hovered ? 1.18 : 1.0
Behavior on scale {
NumberAnimation { duration: Theme.durBase; easing.type: Easing.OutBack }
}
HoverHandler {
id: itemHover
+4 -4
View File
@@ -4,7 +4,7 @@ import Quickshell.Hyprland
import "root:/config"
import "root:/components"
// Workspace indicator: leaning ticks that stretch open when focused. Occupied
// Workspace indicator: soft pills that stretch open when focused. Occupied
// workspaces fill in, urgent ones snap to the alarm colour.
Item {
id: root
@@ -75,14 +75,14 @@ Item {
Behavior on width {
NumberAnimation {
duration: Theme.durSlow
easing.type: Easing.OutBack
easing.overshoot: 1.6
easing.type: Easing.OutCubic
}
}
Skew {
id: tick
anchors.fill: parent
radius: height / 2
// Focused is a solid accent block. Urgent is a solid alarm
// block — it used to be the accent too, which made an
@@ -122,7 +122,6 @@ Item {
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsSmall
font.weight: Theme.weightDisplay
font.italic: true
renderType: Text.NativeRendering
Behavior on opacity {
@@ -134,6 +133,7 @@ Item {
Rectangle {
visible: pip.info !== undefined && pip.info.fullscreen && !pip.focused
width: 4; height: 4
radius: 2
color: Theme.accent
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.bottom
+3 -8
View File
@@ -3,7 +3,7 @@ import "root:/config"
import "root:/components"
// Calm, permanent-chrome preset. It is deliberately flat and opaque: one
// neutral keyline, no texture, no offset copy, and no accent unless active.
// neutral hairline, no texture, no ticks, and no accent unless active.
P5Panel {
property bool active: false
property color tone: Theme.treatmentActiveMark
@@ -12,16 +12,11 @@ P5Panel {
fillOpacity: 1.0
border: Theme.treatmentRestMark
borderWidth: 1
// Keep the hard ink contour. Removing both texture and keyline made the
// permanent slabs read as generic broadcast HUD chrome.
keyline: true
drop: false
halftone: false
sheen: false
slash: active
slashColor: tone
slashWidth: 4
lean: 0.06
chop: Theme.cut
slashWidth: 2
padding: Theme.padS
radius: Theme.radiusM
}
+24 -1
View File
@@ -3,7 +3,11 @@ pragma Singleton
import QtQuick
import Quickshell
// Polygon maths for the Persona 5 panel language: everything leans, and corners
// 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 {
id: root
@@ -86,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();
+110
View File
@@ -0,0 +1,110 @@
import QtQuick
import QtQuick.Effects
import "root:/config"
// A line of type lit from inside, like a tube in a dark console: a soft halo
// that breathes slowly, and every so often a short, uneven stutter where the
// light catches — two or three dips, then steady again for a while. The
// stutters come at random intervals so it never settles into a rhythm you
// could count.
//
// Only the halo and the opacity move, never the geometry, so the glyphs stay
// on the pixel grid. Set `active` false while the text is off screen to stop
// the animation.
Item {
id: root
property string text: ""
property color color: Theme.accent
property color glowColor: root.color
property string family: Theme.fontDisplay
property int pixelSize: Theme.fsLarge
property int weight: Font.Light
property real letterSpacing: 0
property bool active: true
// 0..1, how lit the tube is at this instant.
property real lit: 1
// Slow breathing on the halo alone, between `low` and 1.
property real breath: 1
implicitWidth: label.implicitWidth
implicitHeight: label.implicitHeight
Text {
id: label
visible: false
text: root.text
color: root.color
font.family: root.family
font.pixelSize: root.pixelSize
font.weight: root.weight
font.letterSpacing: root.letterSpacing
// Greyscale antialiasing: the glyphs are drawn into the effect's
// texture, and native subpixel rendering there leaves colour fringes.
renderType: Text.CurveRendering
}
// A wide, faint halo under a tight, brighter one, so the light both
// spreads into the dark and hugs the strokes.
MultiEffect {
source: label
anchors.fill: label
autoPaddingEnabled: true
opacity: root.lit
shadowEnabled: true
shadowColor: root.glowColor
shadowHorizontalOffset: 0
shadowVerticalOffset: 0
shadowBlur: 1.0
blurMax: Math.max(32, Math.round(root.pixelSize * 2))
shadowOpacity: 0.7 * root.breath
shadowScale: 1.02
}
MultiEffect {
source: label
anchors.fill: label
autoPaddingEnabled: true
opacity: 0.55 + 0.45 * root.lit
shadowEnabled: true
shadowColor: root.glowColor
shadowHorizontalOffset: 0
shadowVerticalOffset: 0
shadowBlur: 1.0
blurMax: Math.max(8, Math.round(root.pixelSize * 0.35))
shadowOpacity: 0.9 * root.lit
}
SequentialAnimation on breath {
running: root.active
loops: Animation.Infinite
NumberAnimation { to: 0.6; duration: 2600; easing.type: Easing.InOutSine }
NumberAnimation { to: 1.0; duration: 2600; easing.type: Easing.InOutSine }
}
// One stutter: uneven dips of uneven length, then back to full.
SequentialAnimation {
id: stutter
NumberAnimation { target: root; property: "lit"; to: 0.25; duration: 40 }
NumberAnimation { target: root; property: "lit"; to: 0.9; duration: 60 }
PauseAnimation { duration: 70 }
NumberAnimation { target: root; property: "lit"; to: 0.4; duration: 30 }
NumberAnimation { target: root; property: "lit"; to: 1.0; duration: 180; easing.type: Easing.OutCubic }
}
Timer {
id: nextStutter
running: root.active
repeat: true
interval: 6000
onTriggered: {
stutter.restart();
nextStutter.interval = 4000 + Math.random() * 10000;
}
}
onActiveChanged: if (!root.active) { stutter.stop(); root.lit = 1; }
}
+7 -3
View File
@@ -13,7 +13,11 @@ Text {
color: Theme.primary
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
renderType: Text.NativeRendering
// Curve glyphs, not native ones. Native glyphs are rasterised once at rest
// size and the bitmap is stretched when `scale` moves, so every pulse and
// bump came out blurred and stepped. Curves are drawn from the outline at
// whatever size the icon is at that frame.
renderType: Text.CurveRendering
Behavior on color {
ColorAnimation { duration: Theme.durBase }
@@ -35,11 +39,11 @@ Text {
id: bumpAnim
NumberAnimation {
target: root; property: "scale"
to: 1.35; duration: Theme.durFast; easing.type: Easing.OutExpo
to: 1.12; duration: Theme.durFast; easing.type: Easing.OutExpo
}
NumberAnimation {
target: root; property: "scale"
to: 1.0; duration: Theme.durSlow; easing.type: Easing.OutBack
to: 1.0; duration: Theme.durSlow; easing.type: Easing.OutCubic
}
}
}
+10 -8
View File
@@ -7,8 +7,8 @@ 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 an accent tick down its leading edge and an
// accent glyph, and its label goes to full weight.
// 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 {
@@ -40,12 +41,14 @@ Item {
Rectangle {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
width: 3
height: root.current ? root.height - 10 : 0
anchors.leftMargin: 2
width: root.current ? Theme.marker : 0
height: width
radius: width / 2
color: Theme.accent
Behavior on height {
NumberAnimation { duration: Theme.durBase; easing.type: Easing.OutBack }
Behavior on width {
NumberAnimation { duration: Theme.durBase; easing.type: Easing.OutCubic }
}
}
@@ -76,8 +79,7 @@ Item {
color: root.current || root.hovered ? Theme.text : Theme.subtext
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsSmall
font.italic: true
font.weight: root.current ? Font.Black : Font.Medium
font.weight: root.current ? Font.DemiBold : Font.Medium
renderType: Text.NativeRendering
}
+26 -31
View File
@@ -2,8 +2,12 @@ import QtQuick
import "root:/config"
import "root:/components"
// A segmented load meter — slanted 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 }
}
}
}
+3 -3
View File
@@ -2,8 +2,8 @@ import QtQuick
import "root:/config"
import "root:/components"
// One metric in a popout: a label and its reading on a line, with a wide
// sheared segmented meter underneath.
// One metric in a popout: a label and its reading on a line, with a thin
// segmented meter underneath.
//
// This replaces the radial Gauge. A 270° dial was the only curved thing in a
// shell whose whole geometry is sheared slabs and hard chamfers, so it read as
@@ -78,7 +78,7 @@ Item {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: 12
height: 4
segments: root.segments
spacing: root.segmentSpacing
// Fill the row exactly rather than leaving a ragged tail on the right.
+207 -126
View File
@@ -1,185 +1,266 @@
import QtQuick
import Quickshell
import Quickshell.Widgets
import Quickshell.Services.Notifications
import "root:/config"
import "root:/components"
import "root:/services"
// One notification, used both in the history list and as a floating toast.
// 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 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 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
required property var notif
property bool showActions: true
readonly property bool critical: root.notif?.urgency === NotificationUrgency.Critical
readonly property color tone: root.notif
? Notifs.urgencyColor(root.notif.urgency)
: Theme.primary
// Kept for callers that size things to the card's edges; there is no lean
// or chop under the YoRHa theme.
readonly property real lean: 0
readonly property real chop: 0
readonly property bool hovered: hover.hovered
signal dismissed()
// The card's own geometry, published so anything drawn along its edges —
// the toast countdown rule — can stay inside the chopped corners instead of
// running out past them.
readonly property real lean: 0.05
readonly property real chop: 12
implicitHeight: layout.implicitHeight + Theme.padM * 2
implicitHeight: panel.height
// 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"
P5Panel {
id: panel
width: parent.width
height: layout.implicitHeight + Theme.padM * 2
lean: root.lean
chop: root.chop
fill: Theme.surface
fillOpacity: 0.97
// Urgency lives in the slash, and in the frame only when critical. A
// tone-tinted frame on every card drew a pale outline round ordinary
// notifications, and the critical halftone was painted in the accent,
// so the most urgent card wore the colour that means "live".
border: root.notif?.urgency === NotificationUrgency.Critical
? root.tone
: (hover.hovered ? Theme.treatmentHoverMark : Theme.surfaceAlt)
slash: true
slashColor: root.tone
slashWidth: 4
padding: Theme.padM
Behavior on color {
ColorAnimation { duration: Theme.durFast }
}
}
Column {
id: layout
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 3
Column {
id: layout
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Theme.padM
spacing: 4
// App line
Row {
spacing: Theme.padS
// Sender: marker, glyph, name, then the hairline to the edge.
Item {
width: parent.width
height: 16
Icon {
anchors.verticalCenter: parent.verticalCenter
text: Notifs.appIconFor(root.notif)
color: root.tone
font.pixelSize: Theme.fsBody
}
Text {
anchors.verticalCenter: parent.verticalCenter
text: (root.notif?.appName || "System").toUpperCase()
color: root.tone
font.family: Theme.fontMono
font.pixelSize: Theme.fsMicro
font.letterSpacing: 2
renderType: Text.NativeRendering
}
Rectangle {
id: marker
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
width: Theme.marker
height: Theme.marker
radius: width / 2
color: root.tone
}
Text {
width: parent.width
text: root.notif?.summary || ""
color: Theme.text
elide: Text.ElideRight
maximumLineCount: 2
wrapMode: Text.WordWrap
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsBody
font.weight: Font.Black
font.italic: true
renderType: Text.NativeRendering
}
Text {
width: parent.width
Icon {
id: glyph
visible: text !== ""
text: root.notif?.body || ""
color: Theme.subtext
elide: Text.ElideRight
maximumLineCount: 4
wrapMode: Text.WordWrap
textFormat: Text.StyledText
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsSmall
anchors.left: marker.right
anchors.leftMargin: Theme.padS
anchors.verticalCenter: parent.verticalCenter
text: Notifs.appIconFor(root.notif)
color: root.critical ? Theme.alarm : Theme.muted
font.pixelSize: Theme.fsBody
}
Text {
id: sender
anchors.left: glyph.visible ? glyph.right : marker.right
anchors.leftMargin: Theme.padS
anchors.verticalCenter: parent.verticalCenter
text: (root.notif?.appName || "System").toUpperCase()
color: root.critical ? Theme.alarm : Theme.muted
font.family: Theme.fontMono
font.pixelSize: Theme.fsMicro
font.letterSpacing: 2
renderType: Text.NativeRendering
}
// Inline image, when the sender supplied one.
Rectangle {
anchors.left: sender.right
anchors.leftMargin: Theme.padS
anchors.right: close.left
anchors.rightMargin: Theme.padS
anchors.verticalCenter: parent.verticalCenter
height: 1
color: Theme.alpha(Theme.outline, 0.5)
}
// Close affordance: always there, quiet until the row is hovered.
Icon {
id: close
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
text: "󰅖"
opacity: root.hovered ? 1 : 0.0
color: closeHover.hovered ? Theme.accent : Theme.muted
font.pixelSize: Theme.fsBody
Behavior on opacity {
NumberAnimation { duration: Theme.durFast }
}
HoverHandler {
id: closeHover
cursorShape: Qt.PointingHandCursor
}
TapHandler {
onTapped: root.dismissed()
}
}
}
Text {
width: parent.width
leftPadding: Theme.marker + Theme.padS
text: root.notif?.summary || ""
color: root.hovered ? Theme.accent : Theme.text
elide: Text.ElideRight
maximumLineCount: 2
wrapMode: Text.WordWrap
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsBody
font.weight: Theme.weightDisplay
renderType: Text.NativeRendering
Behavior on color {
ColorAnimation { duration: Theme.durFast }
}
}
Text {
width: parent.width
leftPadding: Theme.marker + Theme.padS
visible: text !== ""
text: root.notif?.body || ""
color: Theme.subtext
elide: Text.ElideRight
maximumLineCount: 4
wrapMode: Text.WordWrap
textFormat: Text.StyledText
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsSmall
lineHeight: 1.1
renderType: Text.NativeRendering
}
// 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
Image {
visible: source != "" && status === Image.Ready
id: inline
x: 1
y: 1
source: root.notif?.image || ""
sourceSize.height: 90
fillMode: Image.PreserveAspectFit
asynchronous: true
}
}
Row {
visible: root.showActions && (root.notif?.actions?.length ?? 0) > 0
spacing: Theme.padS
topPadding: Theme.padS
Row {
x: Theme.marker + Theme.padS
visible: root.showActions && (root.notif?.actions?.length ?? 0) > 0
spacing: Theme.padS
topPadding: 4
Repeater {
model: root.notif?.actions ?? []
Repeater {
model: root.notif?.actions ?? []
Item {
id: actionChip
required property var modelData
// A hairline pill. Hover lifts and lights it; it never inverts.
Item {
id: actionChip
required property var modelData
implicitWidth: actionLabel.implicitWidth + Theme.padM
implicitHeight: 22
implicitWidth: chipRow.implicitWidth + Theme.padM * 2
implicitHeight: 24
Skew {
anchors.fill: parent
color: actionHover.hovered ? Theme.accent : Theme.surfaceAlt
borderWidth: 1
borderColor: actionHover.hovered ? Theme.accent : Theme.overlay
Rectangle {
anchors.fill: parent
radius: height / 2
color: actionHover.hovered ? Theme.treatmentHoverFill : "transparent"
border.width: 1
border.color: actionHover.hovered ? Theme.treatmentHoverMark : Theme.overlay
Behavior on color {
ColorAnimation { duration: Theme.durFast }
}
}
Row {
id: chipRow
anchors.centerIn: parent
spacing: Theme.padS
Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: 4
height: 4
radius: 2
color: actionLabel.color
opacity: actionHover.hovered ? 1 : 0.4
}
Text {
id: actionLabel
anchors.centerIn: parent
text: actionChip.modelData.text
color: actionHover.hovered ? Theme.ink : Theme.text
anchors.verticalCenter: parent.verticalCenter
text: actionChip.modelData.text.toUpperCase()
color: actionHover.hovered ? Theme.accent : Theme.text
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsMicro
font.italic: true
font.weight: Font.DemiBold
font.letterSpacing: Theme.tracking
renderType: Text.NativeRendering
}
}
HoverHandler {
id: actionHover
cursorShape: Qt.PointingHandCursor
}
HoverHandler {
id: actionHover
cursorShape: Qt.PointingHandCursor
}
TapHandler {
onTapped: {
actionChip.modelData.invoke();
root.dismissed();
}
TapHandler {
onTapped: {
actionChip.modelData.invoke();
root.dismissed();
}
}
}
}
}
// Close affordance.
Icon {
anchors.right: parent.right
anchors.top: parent.top
visible: hover.hovered
text: "󰅖"
color: closeHover.hovered ? Theme.accent : Theme.muted
font.pixelSize: Theme.fsBody
HoverHandler {
id: closeHover
cursorShape: Qt.PointingHandCursor
}
TapHandler {
onTapped: root.dismissed()
}
}
}
HoverHandler {
-105
View File
@@ -1,105 +0,0 @@
import QtQuick
import QtQuick.Shapes
import "root:/config"
import "root:/components"
// The shared ground for the two full-screen surfaces: an accent wedge leaning
// at the house angle, a tiled stripe field that sweeps in from the left, and a
// halftone dot wash over the top.
//
// The lock screen and the power menu each used to draw their own version of
// this. They disagreed — the power menu had a wedge and a full stripe field,
// the lock had a 42px stripe band and no wedge, and the wedge leaned at 0.32
// where every panel in the shell leans at Theme.skew. Building both on one
// component 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 stripe slide, the stripe
// opacity and the wedge 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
property bool wedge: true
property real wedgeOpacity: 0.35
// Bottom-left corner of the wedge and its span, as fractions of width. The
// wedge leans right going up, like every other edge in the shell, so its
// top edge sits `leanOffset` further right than its bottom.
property real wedgeLeft: 0
property real wedgeSpan: 0.34
property bool stripes: true
// Tuned down from 0.10 for Mocha. Crust stripes on the blue-grey base read
// far heavier than they did on black, and buzzed against the text.
property real stripeOpacity: 0.07
property bool halftone: true
property real halftoneOpacity: 0.05
property color halftoneColor: Theme.primary
// ONE angle, everywhere — the same rule the panels follow.
readonly property real leanOffset: root.height * Theme.skew
// Diagonal stripe field, sliding in from off-screen left.
Image {
anchors.fill: parent
visible: root.stripes && root.reveal > 0
source: Theme.texStripes
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 wedge. Drawn after the stripes so it reads as a slab laid over them.
Shape {
anchors.fill: parent
visible: root.wedge && root.reveal > 0
opacity: root.reveal
preferredRendererType: Shape.CurveRenderer
ShapePath {
fillColor: Theme.alpha(Theme.accentDim, root.wedgeOpacity)
strokeColor: "transparent"
joinStyle: ShapePath.MiterJoin
PathPolyline {
path: {
const x = root.wedgeLeft * root.width;
const span = root.wedgeSpan * root.width;
const off = root.leanOffset;
return Geom.closed([
Qt.point(x + off, 0),
Qt.point(x + off + span, 0),
Qt.point(x + span, root.height),
Qt.point(x, root.height)
]);
}
}
}
}
// Screen-printed dot wash over the whole thing.
Image {
anchors.fill: parent
visible: root.halftone
source: Theme.texHalftone
fillMode: Image.Tile
opacity: root.halftoneOpacity
smooth: false
layer.enabled: true
layer.effect: TintEffect { tintColor: root.halftoneColor }
}
}
+24 -8
View File
@@ -1,7 +1,8 @@
import QtQuick
import "root:/config"
// Chopped, leaning button with one activation path for pointer and keyboard.
// NieR menu button: a hairline box with a square marker that lights when the
// button is pointed at or chosen. One activation path for pointer and keyboard.
FocusScope {
id: root
@@ -11,7 +12,9 @@ FocusScope {
property bool destructive: false
property bool selected: false
property real lean: Theme.skew
property real lunge: 6
// Sideways nudge on hover. Zero: NieR buttons do not move, the marker
// and the inversion carry the state.
property real lunge: 0
property bool wide: false
signal clicked()
@@ -24,7 +27,7 @@ FocusScope {
// Keyboard focus and roving selection invert the slab to solid accent.
// Pointer hover stays a keyline change, so the two never look alike and a
// mouse user never sees the "selected" treatment by accident. Destructive
// buttons keep their own mauve inversion for both.
// buttons keep their own red inversion for both.
readonly property bool inverted: root.enabled && !root.destructive && root.focusedOrSelected
// Pointer activation must not turn a transient action into the black/white
@@ -70,8 +73,6 @@ FocusScope {
return Theme.alpha(root.accent, 0.55);
}
borderWidth: Theme.stroke
slash: !root.highlighted
slashColor: root.destructive ? Theme.alarm : root.accent
Behavior on fillOpacity {
NumberAnimation { duration: Theme.durFast }
@@ -83,6 +84,21 @@ FocusScope {
anchors.left: parent.left
spacing: Theme.padM
// The marker. Dim at rest, full strength under the pointer or the
// cursor, in whatever colour the label is wearing.
Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: Theme.marker
height: Theme.marker
radius: width / 2
color: label.color
opacity: root.highlighted ? 1.0 : 0.35
Behavior on opacity {
NumberAnimation { duration: Theme.durFast }
}
}
Icon {
anchors.verticalCenter: parent.verticalCenter
visible: root.icon !== ""
@@ -100,6 +116,7 @@ FocusScope {
}
Text {
id: label
anchors.verticalCenter: parent.verticalCenter
text: root.text
color: {
@@ -114,8 +131,7 @@ FocusScope {
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsBody
font.weight: Theme.weightDisplay
font.italic: true
font.letterSpacing: 1.5
font.letterSpacing: Theme.tracking
font.capitalization: Font.AllUppercase
renderType: Text.NativeRendering
@@ -132,7 +148,7 @@ FocusScope {
Behavior on x {
NumberAnimation {
duration: Theme.durBase
easing.type: Easing.OutBack
easing.type: Easing.OutCubic
}
}
}
+74 -19
View File
@@ -4,8 +4,12 @@ import QtQuick.Effects
import "root:/config"
import "root:/components"
// The base surface for everything in this shell: a leaning, corner-chopped slab
// with an optional halftone wash and a accent slash along its leading edge.
// The base surface for everything in this shell. Under the YoRHa theme it is a
// 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.
Item {
id: root
@@ -29,12 +33,16 @@ Item {
property real chop: Theme.cut
property var chopCorners: [1, 3]
// Tone slash down the leading edge — the Persona 5 tell.
// 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
property real slashWidth: 4
// Screen-printed dot wash.
// Dot-grid wash.
property bool halftone: false
property color halftoneColor: Theme.primary
property real halftoneOpacity: 0.07
@@ -45,19 +53,28 @@ Item {
// call sites that still set it don't have to be chased down.
property bool sheen: false
// The screen-print stack. A pure-black copy of the silhouette sits behind
// the fill, offset down-and-right, and the fill carries a black keyline
// outside its coloured border. Together these are what stop the panel from
// reading as a flat clip-path.
property bool drop: true
// Optional print stack: an ink copy of the silhouette offset behind the
// fill, and an ink keyline outside the border. Both off by default — on a
// dark ground they only thicken the edge.
property bool drop: false
property real dropOffset: Theme.dropOffset
property color dropColor: Theme.ink
property bool keyline: true
property bool keyline: false
property real keylineWidth: Theme.keylineWidth
// Corner ticks: a short L at each corner, just outside the border.
property bool ticks: false
property color tickColor: Theme.outline
property real tickLength: Theme.tick
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))
@@ -90,8 +107,8 @@ Item {
strokeColor: "transparent"
joinStyle: ShapePath.MiterJoin
PathPolyline {
path: Geom.chamfer(root.polygon, root.chop, root.chopCorners)
PathSvg {
path: root.outline
}
}
}
@@ -114,8 +131,8 @@ Item {
strokeColor: "transparent"
joinStyle: ShapePath.MiterJoin
PathPolyline {
path: Geom.chamfer(root.polygon, root.chop, root.chopCorners)
PathSvg {
path: root.outline
}
}
}
@@ -139,8 +156,8 @@ Item {
joinStyle: ShapePath.MiterJoin
capStyle: ShapePath.FlatCap
PathPolyline {
path: Geom.chamfer(root.polygon, root.chop, root.chopCorners)
PathSvg {
path: root.outline
}
}
@@ -152,8 +169,8 @@ Item {
joinStyle: ShapePath.MiterJoin
capStyle: ShapePath.FlatCap
PathPolyline {
path: Geom.chamfer(root.polygon, root.chop, root.chopCorners)
PathSvg {
path: root.outline
}
}
}
@@ -165,7 +182,7 @@ Item {
asynchronous: true
sourceComponent: Image {
anchors.fill: parent
source: Theme.texHalftone
source: Theme.texDots
fillMode: Image.Tile
opacity: root.halftoneOpacity
smooth: false
@@ -237,6 +254,44 @@ Item {
}
}
// Corner ticks. Two hairlines per corner, sitting 3px outside the panel so
// they read as a frame around it rather than as part of its border.
Loader {
anchors.fill: parent
anchors.margins: -3
active: root.ticks
sourceComponent: Item {
Repeater {
model: 4
Item {
required property int index
readonly property bool atRight: index === 1 || index === 2
readonly property bool atBottom: index >= 2
x: atRight ? parent.width - width : 0
y: atBottom ? parent.height - height : 0
width: root.tickLength
height: root.tickLength
Rectangle {
y: parent.atBottom ? parent.height - 1 : 0
width: parent.width
height: 1
color: root.tickColor
}
Rectangle {
x: parent.atRight ? parent.width - 1 : 0
width: 1
height: parent.height
color: root.tickColor
}
}
}
}
}
Item {
id: contentHolder
anchors.fill: parent
+7 -6
View File
@@ -2,7 +2,7 @@ import QtQuick
import "root:/config"
import "root:/components"
// Leaning slider with a chopped handle. Drag or click anywhere on the rail.
// Hairline slider with a round handle. Drag or click anywhere on the rail.
Item {
id: root
@@ -30,7 +30,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
height: root.activeFocus ? 10 : 6
height: root.activeFocus ? 4 : 2
// Focus widens the rail and rings it in the accent rather than filling
// it: the level fill is often the accent itself, and a solid accent
// rail swallowed it.
@@ -50,7 +50,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
width: Math.max(0, Math.min(1, root.value)) * root.width
height: 6
height: root.activeFocus ? 4 : 2
color: root.enabled ? root.accent : Theme.treatmentDisabledText
Behavior on width {
@@ -66,14 +66,15 @@ Item {
// Handle
Skew {
id: handle
width: 6
height: root.activeFocus || hover.hovered || drag.active ? 18 : 14
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
x: Math.max(0, Math.min(1, root.value)) * root.width - width / 2
Behavior on height {
NumberAnimation { duration: Theme.durFast; easing.type: Easing.OutBack }
NumberAnimation { duration: Theme.durFast; easing.type: Easing.OutCubic }
}
Behavior on x {
enabled: !drag.active
-2
View File
@@ -69,7 +69,6 @@ PopupWindow {
ChromeSurface {
anchors.fill: parent
padding: root.pad
lean: 0.06
Column {
id: textColumn
@@ -84,7 +83,6 @@ PopupWindow {
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsSmall
font.weight: Theme.weightDisplay
font.italic: true
renderType: Text.NativeRendering
}
+249 -88
View File
@@ -1,10 +1,22 @@
import QtQuick
import QtQuick.Shapes
import Quickshell
import "root:/config"
// Hover-driven panel that hangs off a bar item. Opens on a short dwell, stays
// Hover-driven panel that grows out of the hull. Opens on a short dwell, stays
// open while the pointer is over either the trigger or the panel itself, and
// can be pinned with a click. Slides in from behind the bar with an overshoot.
// can be pinned with a click.
//
// A popout is not a card floating near its trigger; it is more of the frame.
// It sits flush against the edge it opens from — the underside of the bar, or
// the inner edge of the rail — is painted in the hull colour, joins that edge
// through two concave fillets, and carries the porthole's rim round its open
// side. It slides out from behind the frame rather than fading in. This is
// caelestia's idea, and the same construction as the dashboard drawer.
//
// The panel is placed explicitly rather than by the compositor's slide
// adjustment: centred on its trigger, then clamped so the fillets never run
// into the opening's rounded corners or onto the rail.
PopupWindow {
id: root
@@ -19,9 +31,14 @@ PopupWindow {
property Item initialFocusItem: null
property bool remappingPinned: false
// Extra surface around the content so the entry overshoot and glow are not
// clipped by the window edge.
readonly property int bleed: 24
// Fillet radius, and the radius of the panel's own free corners — the same
// as the opening's, so a popout meets the frame the way the opening does.
readonly property real fillet: Theme.frameRadius
readonly property real corner: Theme.frameRadius
// How far the panel reaches back over the frame. One pixel, which covers
// the rim the hull draws along the edge so the seam disappears.
readonly property real seam: 1
default property alias body: contentHolder.data
@@ -31,28 +48,122 @@ PopupWindow {
signal dismissedFully()
// Panels hanging off the vertical rail open sideways, so the entry slide has
// to follow whichever axis the popout actually travels along, and start on
// the side nearest its trigger.
// Panels hanging off the vertical rail open sideways, out of its inner
// edge; everything else drops from the underside of the bar.
readonly property bool horizontal:
fromEdge === Edges.Left || fromEdge === Edges.Right
readonly property string slideProperty: horizontal ? "x" : "y"
readonly property real slideOrigin: bleed + (fromEdge === Edges.Left
|| fromEdge === Edges.Top ? 14 : -14)
anchor.item: anchorItem
anchor.edges: fromEdge
anchor.gravity: fromEdge
anchor.adjustment: horizontal
? PopupAdjustment.SlideY
: PopupAdjustment.SlideX
anchor.margins.top: Theme.popoutGap
anchor.margins.bottom: Theme.popoutGap
anchor.margins.left: Theme.popoutGap
anchor.margins.right: Theme.popoutGap
// Which edges of the frame the panel is attached to. A panel that would
// land within reach of the opening's corner docks into the corner instead,
// attached to the bar and the rail at once — otherwise it leaves a thin
// sliver of opening between itself and the rail, two walls a few pixels
// apart.
//
// "top" hangs from the bar fillets left and right
// "right" juts from the rail fillets above and below
// "topRight" fills the top-right corner fillets left and below
// "bottomRight" fills the bottom-right one fillets above and left
property string dock: horizontal ? "right" : "top"
implicitWidth: contentWidth + bleed * 2
implicitHeight: contentHeight + bleed * 2
// How close to a corner a panel may come before it docks into it.
readonly property real dockReach: 48
// The window: the content, plus a fillet beside each attached edge, plus
// the seam reaching back over the frame.
readonly property real windowWidth: {
switch (dock) {
case "top": return contentWidth + fillet * 2;
case "right": return contentWidth + seam;
default: return contentWidth + fillet + seam;
}
}
readonly property real windowHeight: {
switch (dock) {
case "top": return contentHeight + seam;
case "right": return contentHeight + fillet * 2;
default: return contentHeight + fillet + seam;
}
}
// Where the content sits inside the window.
readonly property real contentX: dock === "right" ? 0 : fillet
readonly property real contentY: dock === "top" || dock === "topRight" ? seam : fillet
// 0 = tucked behind the frame, 1 = fully out.
property real reveal: 0
// Placement, in the trigger window's coordinates. Worked out once per open
// in place(): item positions are not bindable, and a popout should not
// drift while it is up anyway.
property real placeX: 0
property real placeY: 0
// The popout anchors to the trigger's window, not to the trigger. Rail
// icons scale up on hover, and an anchor rect given in an item's own
// coordinates is mapped through that item's transform: a panel placed
// 360px left of a 1.08-scaled icon landed 29px further left still, off
// the rail — by however far the hover scale had got when the popout
// opened. The window's content item never transforms.
readonly property Item anchorFrame: anchorItem?.Window.contentItem ?? null
function place() {
if (!anchorItem) return;
// The trigger's centre. A transform about the centre leaves it where
// it is, so this is the same whatever the hover scale is doing.
const c = anchorItem.mapToItem(null, anchorItem.width / 2, anchorItem.height / 2);
const winW = anchorItem.Window.width;
const winH = anchorItem.Window.height;
const f = root.fillet, R = Theme.frameRadius;
if (root.horizontal) {
// The rail's window starts under the bar at the opening's right
// edge, so its x = 0 is that edge and its y = 0 is the bar's
// underside.
const h = root.contentHeight + f * 2;
const y = c.y - h / 2;
const lo = R, hi = winH - Theme.frame - R - h;
if (y < lo + root.dockReach) {
root.dock = "topRight";
root.placeY = -root.seam;
} else if (y > hi - root.dockReach) {
root.dock = "bottomRight";
root.placeY = winH - Theme.frame + root.seam - root.windowHeight;
} else {
root.dock = "right";
root.placeY = y;
}
root.placeX = root.seam - root.windowWidth;
} else {
// The bar's window is the full screen width, so the opening runs
// from `frame` to `width - railWidth`.
const w = root.contentWidth + f * 2;
const x = c.x - w / 2;
const lo = Theme.frame + R - f;
const hi = winW - Theme.railWidth - R + f - w;
if (x > hi - root.dockReach) {
root.dock = "topRight";
root.placeX = winW - Theme.railWidth + root.seam - root.windowWidth;
} else {
root.dock = "top";
root.placeX = Math.max(lo, x);
}
root.placeY = winH - root.seam;
}
}
anchor.item: anchorFrame
anchor.rect.x: root.placeX
anchor.rect.y: root.placeY
anchor.rect.w: 1
anchor.rect.h: 1
anchor.edges: Edges.Top | Edges.Left
anchor.gravity: Edges.Bottom | Edges.Right
anchor.adjustment: PopupAdjustment.None
implicitWidth: windowWidth
implicitHeight: windowHeight
color: "transparent"
visible: shown
grabFocus: pinned && shown
@@ -97,6 +208,7 @@ PopupWindow {
function open() {
PopupCoordinator.claim(root);
root.place();
exitAnim.stop();
shown = true;
enterAnim.restart();
@@ -152,10 +264,7 @@ PopupWindow {
remappingPinned = false;
pinned = false;
shown = false;
contentHolder.opacity = 0;
contentHolder.scale = 0.94;
contentHolder.x = root.bleed;
contentHolder.y = root.bleed;
root.reveal = 0;
PopupCoordinator.release(root);
root.dismissedFully();
}
@@ -178,83 +287,135 @@ PopupWindow {
}
}
// Everything slides as one sheet, clipped to the window, so the panel
// appears to come out from behind the frame.
Item {
id: contentHolder
focus: root.pinned
x: root.bleed
y: root.bleed
width: root.contentWidth
height: root.contentHeight
anchors.fill: parent
clip: true
opacity: 0
transformOrigin: {
switch (root.fromEdge) {
case Edges.Left: return Item.Right;
case Edges.Right: return Item.Left;
case Edges.Top: return Item.Bottom;
default: return Item.Top;
Item {
id: sheet
width: root.windowWidth
height: root.windowHeight
x: root.horizontal ? root.windowWidth * (1 - root.reveal) : 0
y: root.horizontal ? 0 : -root.windowHeight * (1 - root.reveal)
Shape {
id: silhouette
anchors.fill: parent
preferredRendererType: Shape.CurveRenderer
readonly property real w: width
readonly property real h: height
readonly property real f: root.fillet
readonly property real r: root.corner
// The open side only: from the frame, round the panel, back to
// the frame. The rim follows this; the fill closes it back
// along the frame with `closing`.
readonly property string edge: {
switch (root.dock) {
case "right":
// Down the top fillet, round the two free corners on
// the left, out through the bottom fillet.
return `M ${w} 0 A ${f} ${f} 0 0 1 ${w - f} ${f}`
+ ` H ${r} A ${r} ${r} 0 0 0 0 ${f + r}`
+ ` V ${h - f - r} A ${r} ${r} 0 0 0 ${r} ${h - f}`
+ ` H ${w - f} A ${f} ${f} 0 0 1 ${w} ${h}`;
case "topRight":
// Off the bar through the left fillet, round the one
// free corner, onto the rail through the bottom fillet.
return `M 0 0 A ${f} ${f} 0 0 1 ${f} ${f}`
+ ` V ${h - f - r} A ${r} ${r} 0 0 0 ${f + r} ${h - f}`
+ ` H ${w - f} A ${f} ${f} 0 0 1 ${w} ${h}`;
case "bottomRight":
// Off the rail through the top fillet, round the one
// free corner, onto the bottom band through the left.
return `M ${w} 0 A ${f} ${f} 0 0 1 ${w - f} ${f}`
+ ` H ${f + r} A ${r} ${r} 0 0 0 ${f} ${f + r}`
+ ` V ${h - f} A ${f} ${f} 0 0 1 0 ${h}`;
default:
// Hanging from the bar: the dashboard's silhouette.
return `M 0 0 A ${f} ${f} 0 0 1 ${f} ${f}`
+ ` V ${h - r} A ${r} ${r} 0 0 0 ${f + r} ${h}`
+ ` H ${w - f - r} A ${r} ${r} 0 0 0 ${w - f} ${h - r}`
+ ` V ${f} A ${f} ${f} 0 0 1 ${w} 0`;
}
}
// Back along the frame to the start, through the corner the
// panel is docked into if it is docked into one.
readonly property string closing: {
switch (root.dock) {
case "topRight": return ` L ${w} 0 Z`;
case "bottomRight": return ` L ${w} ${h} Z`;
default: return " Z";
}
}
ShapePath {
fillColor: Theme.hull
strokeColor: "transparent"
PathSvg { path: silhouette.edge + silhouette.closing }
}
ShapePath {
fillColor: "transparent"
strokeColor: Theme.rim
strokeWidth: 1
PathSvg { path: silhouette.edge }
}
}
}
scale: 0.94
// The handler has to live on the content item, not the window: a
// HoverHandler parented to the PopupWindow itself latches on and the
// panel never learns the pointer left.
//
// Disabled while the popout is down, which clears `hovered`. An unmapped
// surface gets no leave event, so a handler that was hovered when the
// window went away stayed hovered — `wantsOpen` never went false again
// and the panel came back up and refused to close.
HoverHandler {
id: panelHover
enabled: root.shown
}
Item {
id: contentHolder
focus: root.pinned
x: root.contentX
y: root.contentY
width: root.contentWidth
height: root.contentHeight
Keys.onEscapePressed: event => {
if (root.pinned) {
event.accepted = true;
root.dismissImmediately();
// Read by PopoutSurface, which leaves the fill and the frame to
// the hull when it finds itself in here.
readonly property bool frameAttached: true
// The handler has to live on the content item, not the window:
// a HoverHandler parented to the PopupWindow itself latches on
// and the panel never learns the pointer left.
//
// Disabled while the popout is down, which clears `hovered`. An
// unmapped surface gets no leave event, so a handler that was
// hovered when the window went away stayed hovered — `wantsOpen`
// never went false again and the panel came back up and refused
// to close.
HoverHandler {
id: panelHover
enabled: root.shown
}
Keys.onEscapePressed: event => {
if (root.pinned) {
event.accepted = true;
root.dismissImmediately();
}
}
}
}
}
ParallelAnimation {
NumberAnimation {
id: enterAnim
NumberAnimation {
target: contentHolder; property: "opacity"
to: 1.0; duration: Theme.durBase; easing.type: Easing.OutExpo
}
NumberAnimation {
target: contentHolder; property: "scale"
to: 1.0; duration: Theme.durSlow; easing.type: Easing.OutBack
}
NumberAnimation {
target: contentHolder; property: root.slideProperty
from: root.slideOrigin; to: root.bleed
duration: Theme.durSlow; easing.type: Easing.OutBack
}
target: root; property: "reveal"
to: 1; duration: Theme.durSlow; easing.type: Easing.OutCubic
}
ParallelAnimation {
NumberAnimation {
id: exitAnim
NumberAnimation {
target: contentHolder; property: "opacity"
to: 0.0; duration: Theme.durFast; easing.type: Easing.InQuad
}
NumberAnimation {
target: contentHolder; property: "scale"
to: 0.96; duration: Theme.durFast; easing.type: Easing.InQuad
}
NumberAnimation {
target: contentHolder; property: root.slideProperty
to: root.bleed + (root.slideOrigin - root.bleed) * 0.6
duration: Theme.durFast; easing.type: Easing.InQuad
}
target: root; property: "reveal"
to: 0; duration: Theme.durBase; easing.type: Easing.InCubic
onFinished: {
root.shown = false;
root.pinned = false;
contentHolder.x = root.bleed;
contentHolder.y = root.bleed;
PopupCoordinator.release(root);
root.dismissedFully();
}
+43
View File
@@ -0,0 +1,43 @@
import QtQuick
import "root:/config"
// The house mark: a porthole with a planet low in it — the wallpaper's one
// composition, drawn in a hairline ring and a disc. The bar's sigil and the
// lock screen both draw it, so the two are the same mark at two sizes.
Item {
id: root
property real size: 18
property bool lit: false
implicitWidth: root.size
implicitHeight: root.size
Rectangle {
anchors.fill: parent
radius: width / 2
color: "transparent"
border.width: Math.max(1, Math.round(root.size / 18))
border.color: root.lit ? Theme.accent : Theme.primaryDim
Behavior on border.color {
ColorAnimation { duration: Theme.durFast }
}
}
// The planet's limb, sitting on the ring's lower edge. Always the planet
// white; `lit` only lets it rise a little.
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: root.size / 6
width: root.size * (root.lit ? 0.44 : 0.34)
height: width
radius: width / 2
color: Theme.accent
Behavior on width {
NumberAnimation { duration: Theme.durBase; easing.type: Easing.OutCubic }
}
}
}
+14 -6
View File
@@ -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));
}
}
}
+11 -12
View File
@@ -1,14 +1,13 @@
import QtQuick
import "root:/config"
// Display text, screen-printed: the body sits on a hard crust copy knocked a
// pixel and a half down-right — the same offset `P5Panel` puts behind every
// slab. Drives most headings in the shell.
// Display text: upright, widely tracked, sitting on a soft ink copy a pixel
// down-right — the faint drop NieR puts under every line of its interface.
// Drives most headings in the shell.
//
// This used to carry a permanent RGB split, a blue and a mauve ghost either
// side of the body. On crimson-and-black that read as misregistered print; on
// Mocha's pastels it read as blur, and it turned every heading in the shell
// soft. The two ghosts still exist, but only for the moment `glitch()` runs.
// The two ghosts either side of the body are the data-corruption flicker NieR
// uses when something changes under you. They are invisible at rest and only
// appear for the moment `glitch()` runs.
Item {
id: root
@@ -16,16 +15,16 @@ Item {
property int pixelSize: Theme.fsLarge
property int weight: Theme.weightDisplay
property string family: Theme.fontDisplay
property bool italic: true
property bool italic: false
property color color: Theme.text
property int elide: Text.ElideNone
property int horizontalAlignment: Text.AlignLeft
property real letterSpacing: 0.5
property real letterSpacing: Theme.tracking
// Print shadow. Scales gently with size so a 34px heading does not sit on
// Drop shadow. Scales gently with size so a 34px heading does not sit on
// the same hairline offset as an 11px label.
property real shadowOffset: Math.max(1, Math.round(root.pixelSize / 10))
property color shadowColor: Theme.ink
property real shadowOffset: Math.max(1, Math.round(root.pixelSize / 16))
property color shadowColor: Theme.alpha(Theme.ink, 0.85)
// Ghost offset and resting strength. Resting strength is zero, so the
// ghosts are invisible until `glitch()` kicks them out. A caller that sets
+123 -95
View File
@@ -3,85 +3,93 @@ pragma Singleton
import QtQuick
import Quickshell
// Mocha palette: Catppuccin Mocha ground, blue as the accent, mauve as the
// alarm. Persona geometry, softer colour.
// YoRHa dark: the NieR:Automata interface language, lit the way
// ~/.wallpapers/space-1.jpg is lit.
//
// The Takemi branch ran monochrome-plus-crimson, where one hue carried both
// "active" and "wrong". Mocha has room for two: blue is every resting accent —
// focus, selection, the thing you are pointing at — and mauve is reserved for
// the things that bite, so destructive and error still read at a glance
// without a third colour entering the shell.
// NieR's menus are sand paper and graphite — monochrome, hairline rules,
// letter-spaced capitals, a small square marking where you are, and selection
// shown by inverting the row rather than by colouring it. That language is
// kept whole. The colours are not NieR's: the game's beige is a lit room, and
// this rice is the dark outside the porthole. The ground is the wallpaper's
// grey-green void, the text is the same sage grey the suit catches, and the
// one bright thing on screen is the planet's white — so focus and selection
// are a slab of that light with the void punched out of it.
//
// Mauve rather than Mocha's pink: pink is a pastel, and a slab of it over this
// ground goes chalky. Mauve is saturated enough to hold an edge against the
// near-black surfaces the Persona layout is built from.
//
// Everything else is Mocha's own neutral ramp. Fills stay flat and keylines
// stay hard; only the hues moved.
// There is still no third colour. The only warm thing in the wallpaper is the
// tiny red of the hull's indicator lights, and that is what `alarm` is.
Singleton {
id: root
// ---------------------------------------------------------------- colors
// Crust, not pure black: it is the darkest thing in Mocha, so it keeps its
// job as the hard keyline and as text punched out of an accent fill.
readonly property color ink: "#11111b"
// Darker than the ground: text punched out of a light fill, and the
// shadow under display text.
readonly property color ink: "#090a08"
readonly property color base: "#1e1e2e"
readonly property color mantle: "#181825"
readonly property color surface: "#313244"
readonly property color surfaceAlt: "#45475a"
readonly property color overlay: "#585b70"
readonly property color outline: "#6c7086"
// The void. Green-black rather than neutral so the shell and the
// wallpaper read as one dark, not two.
readonly property color base: "#0f110e"
readonly property color mantle: "#0c0d0b"
readonly property color surface: "#161915"
readonly property color surfaceAlt: "#20241f"
readonly property color overlay: "#383e35"
readonly property color text: "#cdd6f4"
readonly property color subtext: "#a6adc8"
readonly property color muted: "#7f849c"
// The hull the desktop is framed by. Darker than anything inside the
// opening, so the frame reads as the inside of the ship and the windows as
// what you are looking out at.
readonly property color hull: root.mantle
readonly property color rim: "#2a2f28"
readonly property color outline: "#545c52"
readonly property color text: "#ccd2cb"
readonly property color subtext: "#9fa7a2"
readonly property color muted: "#79847a"
// ------------------------------------------------------------ THE KNOBS
// Two colours, two meanings, and nothing else in the shell carries hue.
//
// `accent` is blue: anything live, focused or selected. `alarm` is mauve:
// `accent` is the planet light: anything live, focused or selected. It is
// barely a colour at all, which is the point — NieR marks attention with
// brightness and inversion, not with hue. `alarm` is the hull's red:
// anything destructive, failed or critical. Things that are merely off —
// muted, disabled, do-not-disturb — carry no hue at all; see the `Off`
// treatment below. Swap either line and everything derived
// from it recolours.
// muted, disabled, do-not-disturb — stay in the grey ramp; see the `Off`
// treatment below.
//
// Keep them in step with `c.accent` / `c.alarm` in
// ~/.config/hypr/lua/palette.lua, which is what colours the Hyprland
// window borders. Those are the only places the colours are written.
readonly property color accent: "#89b4fa"
readonly property color alarm: "#cba6f7"
readonly property color accent: "#eef1ea"
readonly property color alarm: "#c8684f"
readonly property color accentDim: "#5a7dc0"
readonly property color accentSoft: "#b4befe"
readonly property color accentDim: "#8e978d"
readonly property color accentSoft: "#bcc1bf"
// `primary` is the resting voice of the UI, which is now simply white.
// Kept under the old name so the existing call sites don't all have to move.
// `primary` is the resting voice of the UI — the sage text grey. Kept under
// the old name so the existing call sites don't all have to move.
readonly property color primary: root.text
readonly property color primaryDim: "#9399b2"
readonly property color primaryDim: "#8e978d"
readonly property color deep: root.surfaceAlt
// `glow` is the hover highlight. Pointing at a thing lights it up blue —
// the same signal as focus, because it is the same statement about where
// your attention is.
// `glow` is the hover highlight: pointing at a thing brightens it toward
// the planet light, the same signal as focus at a lower volume.
readonly property color glow: root.accent
// ------------------------------------------------ semantic treatments
// These tokens describe interaction meaning, not new palette entries. Each
// state is composed only from crust, the neutral ramp, blue and mauve, so
// state is composed only from ink, the grey ramp, the light and the red, so
// callers do not invent one-off state colours.
readonly property color treatmentRestFill: root.surface
readonly property color treatmentRestMark: root.outline
readonly property color treatmentRestMark: root.overlay
readonly property color treatmentRestText: root.text
readonly property color treatmentHoverFill: root.surface
// Hover lifts the surface a step and brings the marker up to full light.
// It never inverts: inversion is the cursor, and the pointer resting on
// something is not the same statement as the keyboard choosing it.
readonly property color treatmentHoverFill: root.surfaceAlt
readonly property color treatmentHoverMark: root.accent
readonly property color treatmentHoverText: root.text
readonly property color treatmentHoverText: root.accent
// Focus and selection are an inverted slab: solid blue with crust punched
// out of it (9:1). This used to be a white fill, which was right against
// Takemi's black but glares as a lavender-white block on Mocha — and it
// made "selected" a third look that was neither hover nor accent.
// Focus and selection are the NieR cursor: the row inverted, a slab of the
// planet light with ink punched out of it.
readonly property color treatmentFocusFill: root.accent
readonly property color treatmentFocusMark: root.ink
readonly property color treatmentFocusText: root.ink
@@ -89,16 +97,16 @@ Singleton {
readonly property color treatmentSelectedMark: root.treatmentFocusMark
readonly property color treatmentSelectedText: root.treatmentFocusText
readonly property color treatmentActiveFill: root.surface
readonly property color treatmentActiveFill: root.surfaceAlt
readonly property color treatmentActiveMark: root.accent
readonly property color treatmentActiveText: root.text
readonly property color treatmentActiveText: root.accent
// Mauve carries destructive/error meaning — never blue, so a dangerous
// Red carries destructive/error meaning — never the light, so a dangerous
// button can never be mistaken for a merely focused one.
//
// At rest it is a keyline and a label, not a fill. The solid mauve is held
// At rest it is a hairline and a label, not a fill. The solid red is held
// back for the moment the pointer or the selection is actually on the
// thing — then it lands hard, with crust punched out of it at 9.1:1.
// thing, with ink punched out of it.
readonly property color treatmentDestructiveFill: root.surface
readonly property color treatmentDestructiveMark: root.alarm
readonly property color treatmentDestructiveText: root.alarm
@@ -115,12 +123,11 @@ Singleton {
readonly property color treatmentUrgentText: root.text
readonly property color treatmentDisabledFill: root.surface
readonly property color treatmentDisabledMark: root.outline
readonly property color treatmentDisabledMark: root.overlay
readonly property color treatmentDisabledText: root.muted
// Off is a state the user chose — muted, do-not-disturb, radio switched
// off. It is neither live nor wrong, so it gets no hue: painting a muted
// speaker blue said "active", and painting it mauve would say "broken".
// off. It is neither live nor wrong, so it sinks into the grey ramp.
readonly property color treatmentOffFill: root.surface
readonly property color treatmentOffMark: root.overlay
readonly property color treatmentOffText: root.muted
@@ -138,27 +145,30 @@ Singleton {
a.a + (b.a - a.a) * k);
}
// Load readouts sit at text until the number actually matters, then climb
// through the accent before they reach the alarm. Going straight from text
// to mauve passes through a washed-out mid-tone that reads as a faded red
// rather than as a rising number; routing through blue keeps every step of
// the ramp a colour the shell already uses.
// Load readouts sit at text until the number actually matters, brighten
// toward the light, and only then warm into the red. Every step of the ramp
// is a colour the shell already uses.
function heat(pct: real): color {
if (pct <= 45) return root.text;
if (pct <= 75) return root.mix(root.text, root.accent, (pct - 45) / 30);
return root.mix(root.accent, root.alarm, (pct - 75) / 25);
if (pct <= 55) return root.text;
if (pct <= 80) return root.mix(root.text, root.accent, (pct - 55) / 25);
return root.mix(root.accent, root.alarm, (pct - 80) / 20);
}
// ----------------------------------------------------------------- fonts
readonly property string fontDisplay: "Archivo Black"
// NieR sets its UI in FOT-Rodin, a plain Japanese gothic, upright and
// widely tracked. Noto Sans is the closest free face that is already on
// the machine. Display text is never italic and never heavy; the wide
// tracking is what makes it read as a heading.
readonly property string fontDisplay: "Noto Sans"
readonly property string fontMono: "JetBrainsMono Nerd Font"
readonly property string fontIcon: "JetBrainsMono Nerd Font"
readonly property int weightDisplay: Font.Black
readonly property int weightBody: Font.Medium
readonly property int weightDisplay: Font.Medium
readonly property int weightBody: Font.Normal
// Letter-spacing for uppercase labels and headings.
readonly property real tracking: 2.5
// Nothing in the shell renders below this. 9px mono was legible on
// crimson-on-black; on Mocha's lower-contrast ramp it was not.
readonly property int fsMicro: 10
readonly property int fsSmall: 11
readonly property int fsBody: 12
@@ -170,27 +180,44 @@ Singleton {
readonly property int fsRailIcon: 17
// -------------------------------------------------------------- geometry
// The hull. The bar along the top, the rail down the right and a thin band
// down the left and along the bottom are one continuous surface, and the
// desktop is the rounded opening cut out of it — the porthole the
// wallpaper is looking through. `frame` is the band's thickness where
// there is no bar or rail, `frameRadius` the opening's corner.
readonly property int barHeight: 40
readonly property int barMargin: 8
readonly property int barGap: 8
// The right-hand rail uses the same lean and chamfer as the bar — a
// vertical panel leans its horizontal edges by a fraction of its width,
// which works out to the same 14 degrees.
readonly property int railWidth: 48
readonly property int frame: 8
readonly property int frameRadius: 18
// ONE angle, everywhere. tan(14deg). Persona geometry is disciplined, not
// random: every edge in the shell leans by exactly this much in exactly the
// same direction, and every corner is chopped by exactly `cut`. Varying
// these per module reads as noise, not as style.
readonly property real skew: 0.249
readonly property int cut: 10
readonly property int stroke: 2
// Nothing leans and nothing is chopped. Both knobs are kept so the shape
// code still has one place to ask.
readonly property real skew: 0
readonly property int cut: 0
readonly property int stroke: 1
// Crust keyline and offset copy behind panels. This is the one piece of
// layering the style needs; it is not a soft shadow and never blurs.
readonly property real dropOffset: 3
readonly property real keylineWidth: 2
// No printed drop and no ink contour: on a dark ground they only thicken
// the edge. The shadow under display text is the one offset left.
readonly property real dropOffset: 0
readonly property real keylineWidth: 0
// Corners. Everything is soft — the porthole is the shape the whole
// desktop is built around, and every smaller surface is a gentler echo of
// it rather than a hard block.
//
// radiusS pills and small chips: buttons, tags, meter tracks
// radiusM rows and panels inside a surface
// radiusL anything that sits a padS inside the frame's own curve:
// frameRadius - padS, so its corner is concentric with the
// corner it sits in and the gap between them stays even
readonly property int radiusS: 6
readonly property int radiusM: 10
readonly property int radiusL: frameRadius - padS
// The dot that marks the current row, and the length of the corner
// ticks a panel can still ask for.
readonly property int marker: 6
readonly property int tick: 8
readonly property int padS: 6
readonly property int padM: 12
@@ -201,24 +228,25 @@ Singleton {
readonly property int popoutGap: 10
// ------------------------------------------------------------ animation
// P5 motion is snappy and slightly overshooting — never soft.
readonly property int durFast: 110
readonly property int durBase: 190
readonly property int durSlow: 340
readonly property int durLazy: 620
// NieR motion is calm and mechanical: things glide in and settle, they
// never bounce.
readonly property int durFast: 120
readonly property int durBase: 220
readonly property int durSlow: 380
readonly property int durLazy: 700
readonly property var easeSnap: [0.16, 1.0, 0.3, 1.0, 1, 1] // outExpo-ish
readonly property var easeBack: [0.34, 1.56, 0.64, 1.0, 1, 1] // overshoot
readonly property var easeSnap: [0.22, 1.0, 0.36, 1.0, 1, 1] // outQuint
readonly property var easeGlide: [0.4, 0.0, 0.2, 1.0, 1, 1] // standard
readonly property var easeIn: [0.55, 0.0, 0.9, 0.35, 1, 1]
// -------------------------------------------------------------- textures
readonly property url texHalftone: "root:/assets/halftone.png"
readonly property url texStripes: "root:/assets/stripes.png"
// A 1px dot every 8px, the ground NieR's menus are printed on.
readonly property url texDots: "root:/assets/dots.png"
readonly property url texScanline: "root:/assets/scanline.png"
// Last resort only. The live wallpaper comes from the Wallpaper singleton,
// which asks hyprpaper what is actually on screen — a path written down
// here goes stale the moment the wallpaper is switched, which is exactly
// what happened to the lock screen.
readonly property url fallbackWallpaper: "file:///home/narl/.wallpapers/arch.png"
readonly property url fallbackWallpaper: "file:///home/narl/.wallpapers/space-1.jpg"
}
+641
View File
@@ -0,0 +1,641 @@
import QtQuick
import QtQuick.Shapes
import Quickshell
import Quickshell.Widgets
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
import "root:/config"
import "root:/components"
import "root:/services"
// The dashboard: a drawer that slides down out of the underside of the bar.
//
// The idea is caelestia's — the drawer is not a floating card but more of the
// hull, so it is painted in the hull colour and joins the bar through two
// concave fillets, and the porthole's rim runs round it without a break. Read
// against the wallpaper it is a panel lowered from the ceiling of the ship.
//
// Four columns divided by hairlines: who and when, the month, the machine,
// and what is playing. The clock in the bar toggles it, as does
// `qs ipc call shell dashboard`; Escape or a click anywhere else puts it away.
PanelWindow {
id: root
required property var modelData
screen: modelData
readonly property bool open: Actions.dashboardOpen
&& Actions.dashboardScreen === modelData.name
// 0 = tucked under the bar, 1 = fully down.
property real reveal: root.open ? 1 : 0
Behavior on reveal {
NumberAnimation {
duration: Theme.durSlow
easing.type: root.open ? Easing.OutCubic : Easing.InCubic
}
}
visible: root.open || root.reveal > 0
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.namespace: "takemi-dashboard"
WlrLayershell.keyboardFocus: root.open
? WlrKeyboardFocus.OnDemand
: WlrKeyboardFocus.None
exclusionMode: ExclusionMode.Ignore
// One pixel up, so the drawer covers the rim the hull draws along the
// underside of the bar instead of sitting below it.
anchors.top: true
margins.top: Theme.barHeight - 1
// The fillets are the same radius as the porthole's corners, so the drawer
// meets the bar the way the opening meets the frame.
readonly property real fillet: Theme.frameRadius
readonly property real radius: Theme.frameRadius
readonly property real pad: Theme.padL
readonly property real bodyWidth: columns.implicitWidth + root.pad * 2
readonly property real bodyHeight: columns.implicitHeight + root.pad * 2
implicitWidth: root.bodyWidth + root.fillet * 2
implicitHeight: root.bodyHeight + 1
color: "transparent"
mask: Region { item: body }
HyprlandFocusGrab {
active: root.open
windows: [root]
onCleared: Actions.closeDashboard()
}
// Who is at the console. Read once; it does not change under a session.
readonly property string user: Quickshell.env("USER") ?? ""
FileView {
id: hostFile
path: "/etc/hostname"
}
readonly property string host: hostFile.text().trim()
SystemClock {
id: clock
precision: SystemClock.Seconds
}
// Everything below slides as one sheet, clipped to the window so it
// appears to come out from behind the bar rather than fading in over it.
Item {
anchors.fill: parent
clip: true
Item {
id: sheet
width: parent.width
height: parent.height
y: -height * (1 - root.reveal)
// ------------------------------------------------------ silhouette
Shape {
id: silhouette
anchors.fill: parent
preferredRendererType: Shape.CurveRenderer
readonly property real w: width
readonly property real h: height
readonly property real f: root.fillet
readonly property real r: root.radius
// Down the left fillet, round the bottom, up the right fillet.
readonly property string edge:
`M 0 0 A ${f} ${f} 0 0 1 ${f} ${f}`
+ ` V ${h - r} A ${r} ${r} 0 0 0 ${f + r} ${h}`
+ ` H ${w - f - r} A ${r} ${r} 0 0 0 ${w - f} ${h - r}`
+ ` V ${f} A ${f} ${f} 0 0 1 ${w} 0`
ShapePath {
fillColor: Theme.hull
strokeColor: "transparent"
PathSvg { path: silhouette.edge + " Z" }
}
// The rim, carried on round the drawer from the opening.
ShapePath {
fillColor: "transparent"
strokeColor: Theme.rim
strokeWidth: 1
PathSvg { path: silhouette.edge }
}
}
// ---------------------------------------------------------- body
FocusScope {
id: body
x: root.fillet
width: root.bodyWidth
height: parent.height
focus: root.open
Keys.onEscapePressed: Actions.closeDashboard()
Row {
id: columns
x: root.pad
y: root.pad
spacing: 0
// Who and when: the house mark, the operator, a large clock.
Item {
implicitWidth: 230
implicitHeight: root.columnHeight
Row {
id: who
spacing: Theme.padM
Porthole {
anchors.verticalCenter: parent.verticalCenter
size: 30
lit: true
}
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 1
Text {
text: root.user.toUpperCase()
color: Theme.text
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsBody
font.weight: Theme.weightDisplay
font.letterSpacing: Theme.tracking
renderType: Text.NativeRendering
}
Text {
text: root.host !== "" ? "@ " + root.host : ""
color: Theme.muted
font.family: Theme.fontMono
font.pixelSize: Theme.fsMicro
font.letterSpacing: 1
renderType: Text.NativeRendering
}
}
}
Row {
id: time
anchors.top: who.bottom
anchors.topMargin: Theme.padL
spacing: Theme.padS
SplitText {
id: hhmm
text: Qt.formatDateTime(clock.date, "HH:mm")
pixelSize: 54
weight: Font.Light
letterSpacing: 2
color: Theme.accent
}
Text {
anchors.baseline: hhmm.bottom
anchors.baselineOffset: -12
text: Qt.formatDateTime(clock.date, "ss")
color: Theme.muted
font.family: Theme.fontMono
font.pixelSize: Theme.fsBody
renderType: Text.NativeRendering
}
}
Text {
anchors.top: time.bottom
anchors.topMargin: Theme.padS
text: Qt.formatDateTime(clock.date, "dddd · dd MMMM").toUpperCase()
color: Theme.subtext
font.family: Theme.fontMono
font.pixelSize: Theme.fsMicro
font.letterSpacing: 2
renderType: Text.NativeRendering
}
Column {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
spacing: Theme.padS
Fact { label: "UPTIME"; value: Sys.uptime || "—" }
Fact {
visible: Battery.available
label: "POWER"
value: Math.round(Battery.percent) + "%"
+ (Battery.timeLabel !== "" ? " · " + Battery.timeLabel : "")
}
Fact { label: "NOTICES"; value: Notifs.count > 0 ? Notifs.count + " held" : "none" }
}
}
Rule { height: root.columnHeight }
// The month, Monday first. Today is the NieR cursor: an inverted disc.
Item {
id: month
implicitWidth: 238
implicitHeight: root.columnHeight
// Keyed by the day, not the clock: the clock ticks every
// second, and rebuilding 42 cells that often is waste.
readonly property string dayKey: Qt.formatDateTime(clock.date, "yyyy-MM-dd")
readonly property var cells: {
const now = new Date(month.dayKey + "T12:00:00");
const y = now.getFullYear(), m = now.getMonth();
const lead = (new Date(y, m, 1).getDay() + 6) % 7;
const out = [];
for (let i = 0; i < 42; i++) {
const d = new Date(y, m, 1 - lead + i);
out.push({
day: d.getDate(),
inMonth: d.getMonth() === m,
today: d.toDateString() === now.toDateString()
});
}
return out;
}
SectionRule {
id: monthHead
anchors.left: parent.left
anchors.right: parent.right
text: Qt.formatDateTime(clock.date, "MMMM yyyy")
}
Row {
id: weekdays
anchors.top: monthHead.bottom
anchors.topMargin: Theme.padS
Repeater {
model: ["MO", "TU", "WE", "TH", "FR", "SA", "SU"]
Text {
required property var modelData
required property int index
width: month.width / 7
horizontalAlignment: Text.AlignHCenter
text: modelData
color: index >= 5 ? Theme.subtext : Theme.muted
font.family: Theme.fontMono
font.pixelSize: Theme.fsMicro
renderType: Text.NativeRendering
}
}
}
Grid {
anchors.top: weekdays.bottom
anchors.topMargin: Theme.padS
columns: 7
Repeater {
model: month.cells
Item {
required property var modelData
width: month.width / 7
height: 26
Rectangle {
anchors.centerIn: parent
width: 22
height: 22
radius: 11
visible: parent.modelData.today
color: Theme.treatmentSelectedFill
}
Text {
anchors.centerIn: parent
text: parent.modelData.day
color: parent.modelData.today ? Theme.treatmentSelectedText
: parent.modelData.inMonth ? Theme.text
: Theme.alpha(Theme.muted, 0.5)
font.family: Theme.fontMono
font.pixelSize: Theme.fsSmall
font.weight: parent.modelData.today ? Font.Bold : Font.Normal
renderType: Text.NativeRendering
}
}
}
}
}
Rule { height: root.columnHeight }
// The machine: the same meters the bar's readout opens onto.
Item {
implicitWidth: 238
implicitHeight: root.columnHeight
SectionRule {
id: sysHead
anchors.left: parent.left
anchors.right: parent.right
text: "System"
}
Column {
anchors.top: sysHead.bottom
anchors.topMargin: Theme.padM
anchors.left: parent.left
anchors.right: parent.right
spacing: Theme.padM
MetricRow {
width: parent.width
value: Sys.cpuUsage
label: "CPU"
sub: Math.round(Sys.cpuTemp) + "°C"
}
MetricRow {
width: parent.width
value: Sys.memPercent
label: "MEM"
sub: Sys.gb(Sys.memUsed) + " / " + Sys.gb(Sys.memTotal)
}
MetricRow {
width: parent.width
visible: Sys.gpuAvailable
value: Sys.gpuUsage
label: "GPU"
sub: Math.round(Sys.gpuTemp) + "°C"
}
MetricRow {
width: parent.width
value: Sys.diskPercent
label: "DISK"
sub: Sys.diskFree + " free"
}
}
}
Rule { height: root.columnHeight }
// What is playing. With no player it says so, in the voice NieR's
// interface uses for an empty channel.
Item {
implicitWidth: 280
implicitHeight: root.columnHeight
SectionRule {
id: playHead
anchors.left: parent.left
anchors.right: parent.right
text: Media.hasPlayer ? Media.identity : "Audio"
}
Text {
anchors.centerIn: parent
visible: !Media.hasPlayer
text: "NO SIGNAL"
color: Theme.muted
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsBody
font.letterSpacing: Theme.tracking * 2
renderType: Text.NativeRendering
}
Item {
anchors.top: playHead.bottom
anchors.topMargin: Theme.padM
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
visible: Media.hasPlayer
ClippingRectangle {
id: artFrame
width: 96
height: 96
radius: Theme.radiusL
color: Theme.surface
border.width: 1
border.color: Theme.overlay
Image {
id: art
anchors.fill: parent
anchors.margins: 1
source: Media.artUrl
fillMode: Image.PreserveAspectCrop
asynchronous: true
sourceSize.width: 192
sourceSize.height: 192
}
Icon {
anchors.centerIn: parent
visible: art.status !== Image.Ready
text: "󰝚"
color: Theme.muted
font.pixelSize: Theme.fsHuge
}
}
Column {
anchors.left: artFrame.right
anchors.leftMargin: Theme.padM
anchors.right: parent.right
anchors.verticalCenter: artFrame.verticalCenter
spacing: 3
Text {
width: parent.width
text: Media.title || "Untitled"
elide: Text.ElideRight
maximumLineCount: 2
wrapMode: Text.Wrap
color: Theme.accent
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsLarge
font.weight: Theme.weightDisplay
renderType: Text.NativeRendering
}
Text {
width: parent.width
text: Media.artist
elide: Text.ElideRight
color: Theme.subtext
font.family: Theme.fontMono
font.pixelSize: Theme.fsSmall
renderType: Text.NativeRendering
}
}
// Position: a hairline with a square riding it.
Item {
id: timeline
anchors.top: artFrame.bottom
anchors.topMargin: Theme.padM
anchors.left: parent.left
anchors.right: parent.right
height: Theme.marker
visible: Media.hasTimeline
Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: parent.width
height: 1
color: Theme.overlay
}
Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: parent.width * Media.progress
height: 1
color: Theme.accent
}
Rectangle {
anchors.verticalCenter: parent.verticalCenter
x: parent.width * Media.progress - width / 2
width: Theme.marker
height: Theme.marker
radius: width / 2
color: Theme.accent
}
}
Row {
anchors.bottom: parent.bottom
anchors.left: parent.left
spacing: Theme.padS
Transport {
glyph: "󰒮"
enabled: Media.canGoPrevious
onClicked: Media.previous()
}
Transport {
glyph: Media.playing ? "󰏤" : "󰐊"
onClicked: Media.playPause()
}
Transport {
glyph: "󰒭"
enabled: Media.canGoNext
onClicked: Media.next()
}
}
Text {
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
anchors.right: parent.right
visible: Media.hasTimeline
text: Media.timeString(Media.position) + " / " + Media.timeString(Media.length)
color: Theme.muted
font.family: Theme.fontMono
font.pixelSize: Theme.fsMicro
renderType: Text.NativeRendering
}
}
}
}
}
}
}
// ------------------------------------------------------------ columns
// A vertical hairline between columns, the same rule the bar divides its
// modules with, at the drawer's scale.
component Rule: Item {
implicitWidth: Theme.padL * 2 + 1
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.topMargin: Theme.padS
anchors.bottomMargin: Theme.padS
width: 1
color: Theme.alpha(Theme.outline, 0.5)
}
}
readonly property real columnHeight: 214
// A label and its reading on one line, label left, reading right.
component Fact: Item {
property string label: ""
property string value: ""
width: parent.width
implicitHeight: 14
Text {
anchors.left: parent.left
text: parent.label
color: Theme.muted
font.family: Theme.fontMono
font.pixelSize: Theme.fsMicro
font.letterSpacing: 2
renderType: Text.NativeRendering
}
Text {
anchors.right: parent.right
text: parent.value
color: Theme.text
font.family: Theme.fontMono
font.pixelSize: Theme.fsMicro
renderType: Text.NativeRendering
}
}
// A round hairline button. Hover lifts it; nothing here inverts, since
// inversion is reserved for the keyboard's cursor.
component Transport: Item {
id: tb
property string glyph: ""
signal clicked()
width: 30
height: 30
opacity: tb.enabled ? 1 : 0.35
Rectangle {
anchors.fill: parent
radius: width / 2
color: tbHover.hovered ? Theme.treatmentHoverFill : "transparent"
border.width: 1
border.color: tbHover.hovered ? Theme.treatmentHoverMark : Theme.overlay
Behavior on color { ColorAnimation { duration: Theme.durFast } }
}
Icon {
anchors.centerIn: parent
text: tb.glyph
color: tbHover.hovered ? Theme.accent : Theme.text
}
HoverHandler {
id: tbHover
enabled: tb.enabled
cursorShape: Qt.PointingHandCursor
}
TapHandler {
enabled: tb.enabled
onTapped: tb.clicked()
}
}
}
+1 -1
View File
@@ -527,7 +527,7 @@ PanelWindow {
}
NumberAnimation {
target: rise; property: "y"
from: 24; to: 0; duration: Theme.durSlow; easing.type: Easing.OutBack
from: 24; to: 0; duration: Theme.durSlow; easing.type: Easing.OutCubic
}
}
+2 -1
View File
@@ -28,6 +28,7 @@ Item {
anchors.fill: parent
anchors.leftMargin: -Theme.padS
anchors.rightMargin: -Theme.padS
radius: Theme.radiusM
color: root.selected ? Theme.treatmentSelectedFill : "transparent"
opacity: root.selected ? 1 : 0
@@ -46,7 +47,7 @@ Item {
color: Theme.accent
Behavior on height {
NumberAnimation { duration: Theme.durFast; easing.type: Easing.OutBack }
NumberAnimation { duration: Theme.durFast; easing.type: Easing.OutCubic }
}
}
+302 -267
View File
@@ -10,6 +10,13 @@ import "root:/lock"
// The lock screen's whole face, kept separate from WlSessionLockSurface so it
// can be instantiated in an ordinary window for development without actually
// 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 {
id: surface
@@ -20,12 +27,12 @@ Item {
property string status: ""
property bool failed: false
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)))
Rectangle {
anchors.fill: parent
color: Theme.base
color: Theme.ink
}
// ------------------------------------------------------- background
@@ -43,297 +50,327 @@ Item {
fillMode: Image.PreserveAspectCrop
asynchronous: false
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.effect: MultiEffect {
blurEnabled: true
blur: 0.55
blurMax: 48
saturation: -0.15
brightness: -0.35
blur: 0.2
blurMax: 32
saturation: -0.1
brightness: -0.18
}
}
// Wash pulling the photo down toward the shell's ground. Was described as
// a teal wash back when the UI was teal; the colours it actually uses are
// the neutral surfaces, which is what it should have been all along.
// Shade deepening toward the left, where the type sits in the porthole's
// dark, and a breath of it along the foot.
Rectangle {
anchors.fill: parent
gradient: Gradient {
GradientStop { position: 0.0; color: Theme.alpha(Theme.deep, 0.55) }
GradientStop { position: 0.6; color: Theme.alpha(Theme.base, 0.75) }
GradientStop { position: 1.0; color: Theme.alpha(Theme.base, 0.92) }
orientation: Gradient.Horizontal
GradientStop { position: 0.0; color: Theme.alpha(Theme.ink, 0.72) }
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
// 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
Rectangle {
anchors.fill: parent
reveal: 0
wedgeSpan: 0.30
wedgeOpacity: 0.30
stripeOpacity: 0.07
gradient: Gradient {
GradientStop { position: 0.6; color: "transparent" }
GradientStop { position: 1.0; color: Theme.alpha(Theme.ink, 0.5) }
}
}
// ------------------------------------------------------------ clock
// ------------------------------------------------------------- face
SystemClock {
id: clock
precision: SystemClock.Minutes
}
// The house mark, same wordmark the bar carries far left. Small, above the
// clock — the lock's hero is the time, not the branding.
SplitText {
id: mark
anchors.left: timeBlock.left
anchors.bottom: timeBlock.top
anchors.bottomMargin: 6
text: "P5"
pixelSize: Theme.fsTitle
split: 2
letterSpacing: 0
opacity: timeBlock.opacity
}
Column {
id: timeBlock
anchors.left: parent.left
anchors.leftMargin: parent.width * 0.1
anchors.top: parent.top
anchors.topMargin: parent.height * 0.18
spacing: -18
id: face
// Sized by uiScale rather than drawn small and stretched with `scale`:
// a scale transform blurs native text, and every label here is native.
// Whole pixels, too, or the glyphs land between them.
x: Math.round(parent.width * 0.14)
y: Math.round(parent.height * 0.2)
width: Math.round(380 * surface.uiScale)
opacity: 0
spacing: 0
SplitText {
text: Qt.formatDateTime(clock.date, "HH")
pixelSize: Theme.fsMega * 1.7
split: 5
splitOpacity: 0.85
}
transform: Translate { id: drift }
SplitText {
text: Qt.formatDateTime(clock.date, "mm")
pixelSize: Theme.fsMega * 1.7
color: Theme.primary
split: 5
splitOpacity: 0.6
// The house mark, the same porthole the bar carries far left.
Porthole {
size: Math.round(26 * surface.uiScale)
}
Text {
text: Qt.formatDateTime(clock.date, "dddd, dd MMMM").toUpperCase()
topPadding: Math.round(Theme.padM * surface.uiScale)
text: Qt.formatDateTime(clock.date, "HH:mm")
color: Theme.accent
font.family: Theme.fontDisplay
font.pixelSize: Math.round(Theme.fsMega * 1.35 * surface.uiScale)
font.weight: Font.Light
font.letterSpacing: 2 * surface.uiScale
// Display size, where curves beat hinted bitmaps.
renderType: Text.CurveRendering
}
Text {
leftPadding: Math.round(4 * surface.uiScale)
text: Qt.formatDateTime(clock.date, "dddd · dd MMMM").toUpperCase()
color: Theme.subtext
font.family: Theme.fontMono
font.pixelSize: Theme.fsBody
font.letterSpacing: 6
topPadding: 34
font.family: Theme.fontDisplay
font.pixelSize: Math.round(Theme.fsBody * surface.uiScale)
font.weight: Theme.weightDisplay
font.letterSpacing: 5 * surface.uiScale
renderType: Text.NativeRendering
}
}
// ------------------------------------------------------ status rail
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
// The one line the face says to you. Nothing here is in a hurry.
Item { width: 1; height: Math.round(Theme.padL * surface.uiScale) }
RailRow {
GlowText {
x: Math.round(4 * surface.uiScale)
text: "Take your time."
color: Theme.subtext
glowColor: Theme.accent
pixelSize: Math.round(Theme.fsLarge * surface.uiScale)
}
Item { width: 1; height: Math.round(Theme.padXL * surface.uiScale) }
// ------------------------------------------------------ status log
Column {
width: parent.width
visible: Battery.available
glyph: Battery.icon
label: Math.round(Battery.percent) + "%"
detail: Battery.timeLabel
tone: Battery.tint
leftPadding: Math.round(4 * surface.uiScale)
StatusRow {
width: parent.width - parent.leftPadding
uiScale: surface.uiScale
visible: Battery.available
label: "Battery"
detail: Math.round(Battery.percent) + "%"
+ (Battery.timeLabel ? " " + Battery.timeLabel : "")
tone: Battery.tint
}
StatusRow {
width: parent.width - parent.leftPadding
uiScale: surface.uiScale
label: "Network"
detail: Sys.netUp ? Net.label : "no link"
tone: Sys.netUp ? Theme.accentSoft : Theme.overlay
}
StatusRow {
width: parent.width - parent.leftPadding
uiScale: surface.uiScale
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 - parent.leftPadding
uiScale: surface.uiScale
visible: Notifs.count > 0
label: "Notices"
detail: Notifs.count + (Notifs.count === 1 ? " message" : " messages")
tone: Theme.accentSoft
}
}
RailRow {
Item { width: 1; height: Math.round(Theme.padXL * surface.uiScale) }
// ------------------------------------------------------ the field
Item {
id: authBlock
width: parent.width
glyph: Net.icon
label: Net.label
detail: Sys.netUp ? Sys.netIp : "no link"
tone: Sys.netUp ? Theme.primary : Theme.muted
}
height: field.height + hint.height + hint.anchors.topMargin
RailRow {
width: parent.width
visible: Media.hasPlayer
glyph: Media.playing ? "󰝚" : "󰏤"
label: Media.title
detail: Media.artist
tone: Theme.primary
}
transform: Translate { id: nudge }
RailRow {
width: parent.width
visible: Notifs.count > 0
glyph: "󱅫"
label: Notifs.count + " notification" + (Notifs.count === 1 ? "" : "s")
detail: Notifs.history.length > 0 ? Notifs.history[0].summary : ""
tone: Theme.accent
}
}
Rectangle {
id: field
width: parent.width
height: Math.round(48 * surface.uiScale)
radius: height / 2
color: Theme.alpha(Theme.hull, 0.86)
border.width: 1
border.color: surface.failed
? Theme.alarm
: (input.activeFocus && surface.entry.length > 0 ? Theme.outline : Theme.rim)
// ------------------------------------------------------- auth panel
Item {
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
Behavior on border.color {
ColorAnimation { duration: Theme.durBase }
}
transform: Translate { id: shakeShift }
Icon {
id: lockGlyph
anchors.left: parent.left
anchors.leftMargin: Math.round(Theme.padL * surface.uiScale)
anchors.verticalCenter: parent.verticalCenter
text: "󰌾"
color: surface.failed ? Theme.alarm : Theme.muted
font.pixelSize: Math.round(Theme.fsLarge * surface.uiScale)
}
SplitText {
id: prompt
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
text: surface.failed
? (surface.status || "WRONG").toUpperCase()
: (surface.busy ? "CHECKING…" : "WHO GOES THERE")
color: surface.failed ? Theme.alarm : Theme.text
pixelSize: Theme.fsTitle
letterSpacing: 4
horizontalAlignment: Text.AlignHCenter
split: surface.failed ? 4 : 1.6
}
Text {
anchors.left: lockGlyph.right
anchors.leftMargin: Math.round(Theme.padM * surface.uiScale)
anchors.verticalCenter: parent.verticalCenter
visible: surface.entry.length === 0
text: "PASSWORD"
color: Theme.muted
font.family: Theme.fontDisplay
font.pixelSize: Math.round(Theme.fsSmall * surface.uiScale)
font.letterSpacing: Theme.tracking * 2 * surface.uiScale
renderType: Text.NativeRendering
}
// Password field: one chopped block per character.
P5Panel {
id: field
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: prompt.bottom
anchors.topMargin: Theme.padL
width: Math.min(380, authBlock.width - Theme.padL * 2)
height: 56
lean: 0.12
chop: 16
fill: Theme.mantle
fillOpacity: 0.94
border: surface.failed
? 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
// One dot per character, each resolving in as it is typed.
// They breathe while PAM is checking.
Row {
id: dots
anchors.left: lockGlyph.right
anchors.leftMargin: Math.round(Theme.padM * surface.uiScale)
anchors.right: enter.left
anchors.rightMargin: Math.round(Theme.padM * surface.uiScale)
anchors.verticalCenter: parent.verticalCenter
spacing: Math.round(7 * surface.uiScale)
clip: true
Row {
anchors.centerIn: parent
spacing: 6
visible: surface.entry.length > 0
Repeater {
model: Math.min(surface.entry.length, 28)
Repeater {
model: Math.min(surface.entry.length, 22)
Rectangle {
id: pip
required property int index
anchors.verticalCenter: parent.verticalCenter
width: Math.round(7 * surface.uiScale)
height: width
radius: width / 2
color: surface.failed ? Theme.alarm : Theme.accent
opacity: 0
scale: 0.4
Skew {
required property int index
width: 10
height: 20
color: surface.failed ? Theme.alarm : Theme.primary
Component.onCompleted: arrive.start()
// Each new block snaps in.
Component.onCompleted: pop.start()
NumberAnimation {
id: pop
target: parent
property: "scale"
from: 0.2; to: 1
duration: Theme.durBase
easing.type: Easing.OutBack
ParallelAnimation {
id: arrive
NumberAnimation {
target: pip; property: "opacity"
to: 1; duration: Theme.durBase; easing.type: Easing.OutCubic
}
NumberAnimation {
target: pip; property: "scale"
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: Math.round(8 * surface.uiScale)
anchors.verticalCenter: parent.verticalCenter
width: Math.round(32 * surface.uiScale)
height: width
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: Math.round(Theme.fsLarge * surface.uiScale)
}
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 {
anchors.centerIn: parent
visible: surface.entry.length === 0 && !surface.busy
text: "PASSWORD"
color: Theme.muted
font.family: Theme.fontMono
font.pixelSize: Theme.fsSmall
font.letterSpacing: 5
renderType: Text.NativeRendering
}
// Under the field: who is locked, what is happening, or what went
// wrong — one line, never more.
Row {
id: hint
anchors.top: field.bottom
anchors.topMargin: Math.round(Theme.padS * surface.uiScale)
anchors.left: parent.left
anchors.leftMargin: Math.round(Theme.padL * surface.uiScale)
spacing: Math.round(Theme.padS * surface.uiScale)
// Busy sweep.
Rectangle {
anchors.bottom: parent.bottom
anchors.bottomMargin: 6
width: 60
height: 3
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;
Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: Math.round(4 * surface.uiScale)
height: width
radius: width / 2
color: surface.failed ? Theme.alarm : Theme.overlay
}
onAccepted: surface.submit()
Keys.onEscapePressed: {
input.text = "";
surface.failed = false;
Text {
anchors.verticalCenter: parent.verticalCenter
text: surface.failed
? (surface.status || "Access denied").toUpperCase()
: (surface.busy ? "VERIFYING" : (Quickshell.env("USER") || "user"))
color: surface.failed ? Theme.alarm : Theme.muted
font.family: Theme.fontMono
font.pixelSize: Math.round(Theme.fsMicro * surface.uiScale)
font.letterSpacing: 2 * surface.uiScale
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.
@@ -369,62 +406,60 @@ Item {
surface.entry = "";
surface.failed = false;
} else {
surface.failed = true;
if (surface.status === "") surface.status = "Access denied";
input.text = "";
surface.entry = "";
shake.restart();
surface.failed = true;
if (surface.status === "") surface.status = "Access denied";
refuse.restart();
}
}
onError: err => {
surface.failed = true;
surface.status = "PAM error";
input.text = "";
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 {
id: shake
loops: 2
id: refuse
NumberAnimation {
target: shakeShift; property: "x"
to: -16; duration: 55; easing.type: Easing.OutQuad
target: nudge; property: "x"
to: -7; duration: 90; easing.type: Easing.OutSine
}
NumberAnimation {
target: shakeShift; property: "x"
to: 16; duration: 55; easing.type: Easing.OutQuad
target: nudge; property: "x"
to: 5; duration: 140; easing.type: Easing.InOutSine
}
NumberAnimation {
target: shakeShift; property: "x"
to: 0; duration: 90; easing.type: Easing.OutBack
target: nudge; property: "x"
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()
ParallelAnimation {
SequentialAnimation {
id: intro
// The stripes sweep in under everything, the same move the power menu
// opens with.
NumberAnimation {
target: backdrop; property: "reveal"
from: 0; to: 1; duration: Theme.durLazy; easing.type: Easing.OutExpo
target: wall; property: "opacity"
from: 0; to: 1; duration: Theme.durLazy; easing.type: Easing.OutCubic
}
NumberAnimation {
target: timeBlock; property: "opacity"
from: 0; to: 1; duration: Theme.durLazy; easing.type: Easing.OutExpo
}
NumberAnimation {
target: authBlock; property: "opacity"
from: 0; to: 1; duration: Theme.durLazy; easing.type: Easing.OutExpo
}
NumberAnimation {
target: shakeShift; property: "y"
from: 40; to: 0; duration: Theme.durLazy; easing.type: Easing.OutBack
ParallelAnimation {
NumberAnimation {
target: face; property: "opacity"
from: 0; 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
}
}
}
}
-64
View File
@@ -1,64 +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.italic: true
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
}
}
}
}
+69
View File
@@ -0,0 +1,69 @@
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
// The lock face's size factor. The row is laid out at that size rather
// than scaled, so its native text stays sharp.
property real uiScale: 1
implicitHeight: visible ? Math.round(26 * row.uiScale) : 0
Rectangle {
id: dot
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
width: Math.round(Theme.marker * row.uiScale)
height: width
radius: width / 2
color: row.tone
}
Text {
id: name
anchors.left: dot.right
anchors.leftMargin: Math.round(Theme.padS * row.uiScale)
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: Math.round(Theme.fsSmall * row.uiScale)
font.weight: Theme.weightDisplay
font.letterSpacing: Theme.tracking * row.uiScale
renderType: Text.NativeRendering
}
Rectangle {
anchors.left: name.right
anchors.leftMargin: Math.round(Theme.padS * row.uiScale)
anchors.right: value.left
anchors.rightMargin: Math.round(Theme.padS * row.uiScale)
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: Math.round(Theme.fsMicro * row.uiScale)
renderType: Text.NativeRendering
}
}
+39
View File
@@ -0,0 +1,39 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import "root:/config"
import "root:/services"
// The first stage of idle (services/Idle.qml): the screen dims the way a room
// does when the lights go down, slowly enough that you notice it before the
// lock does. Any input lifts it at once. Input passes straight through.
PanelWindow {
id: root
required property var modelData
screen: modelData
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.namespace: "takemi-dim"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
exclusionMode: ExclusionMode.Ignore
anchors { top: true; bottom: true; left: true; right: true }
color: "transparent"
mask: Region {}
visible: Idle.dimmed || shade.opacity > 0
Rectangle {
id: shade
anchors.fill: parent
color: Theme.ink
opacity: Idle.dimmed ? 0.6 : 0
Behavior on opacity {
NumberAnimation {
duration: Idle.dimmed ? 8000 : Theme.durBase
easing.type: Idle.dimmed ? Easing.InOutSine : Easing.OutCubic
}
}
}
}
+14 -12
View File
@@ -111,20 +111,22 @@ PanelWindow {
slash: true
slashColor: root.tone
slashWidth: 5
lean: 0.09
chop: 14
padding: Theme.padM
// It rises a few pixels into place instead of growing: a scale would
// stretch the native text in the panel while it moves.
opacity: root.shown ? 1 : 0
scale: root.shown ? 1 : 0.9
transform: Translate {
y: root.shown ? 0 : 8
Behavior on y {
NumberAnimation { duration: Theme.durSlow; easing.type: Easing.OutCubic }
}
}
Behavior on opacity {
NumberAnimation { id: fade; duration: Theme.durBase }
}
Behavior on scale {
NumberAnimation { duration: Theme.durSlow; easing.type: Easing.OutBack }
}
Icon {
id: glyphIcon
anchors.left: parent.left
@@ -173,8 +175,8 @@ PanelWindow {
anchors.verticalCenter: parent.verticalCenter
// Natural width, not a fixed one — SplitText offsets its
// ghosts with left/right anchor margins, so pinning the
// width and right-aligning the text would flatten the RGB
// split away. The title's left edge is anchored, so a
// width and right-aligning the text would flatten the
// glitch flicker away. The title's left edge is anchored, so a
// widening readout only moves its own left edge.
text: Math.round(root.level * 100) + "%"
pixelSize: Theme.fsBody
@@ -183,10 +185,10 @@ PanelWindow {
MeterBar {
width: parent.width
height: 10
height: 4
segments: 20
segmentWidth: (width - 19 * 3) / 20
spacing: 3
segmentWidth: (width - 19 * 2) / 20
spacing: 2
value: root.level * 100
activeColor: root.tone
}
+141 -142
View File
@@ -1,12 +1,16 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import Quickshell.Io
import "root:/config"
import "root:/components"
import "root:/services"
// Full-screen power menu. An accent wedge and stripes sweep in behind a stack of leaning
// buttons; Escape or a click on the backdrop dismisses it.
// Full-screen power menu, built like the lock screen: the desktop dims to the
// 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 {
id: root
@@ -32,16 +36,9 @@ PanelWindow {
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 {
root.selectedAction = (root.selectedAction + delta + root.actionModel.length)
% root.actionModel.length;
root.focusSelectedAction();
}
readonly property var actionModel: [
@@ -53,6 +50,11 @@ PanelWindow {
{ label: "Shut Down", icon: "󰐥", danger: true, act: "shutdown" }
]
FileView {
id: hostFile
path: "/etc/hostname"
}
onOpenChanged: {
if (open) {
PopupCoordinator.dismissAll();
@@ -64,134 +66,153 @@ PanelWindow {
}
// ------------------------------------------------------------- backdrop
// The desktop stays faintly there behind the dark, deepest on the left
// where the menu stands.
Rectangle {
id: backdrop
anchors.fill: parent
color: Theme.alpha(Theme.base, 0.9)
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 {
onTapped: Actions.closePowerMenu()
}
}
// Stripes and wedge, shared with the lock screen. The wedge used to lean at
// 0.32 here, which was its own angle and nothing else's; P5Backdrop leans
// it at Theme.skew like every other edge in the shell.
P5Backdrop {
id: ground
anchors.fill: parent
reveal: 0
stripeOpacity: 0.10
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
// ---------------------------------------------------------------- group
Column {
id: title
anchors.left: parent.left
anchors.leftMargin: root.width * 0.12
id: group
x: root.width * 0.14
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: -60
spacing: -14
width: 340
opacity: 0
SplitText {
text: "TAKE"
pixelSize: Theme.fsMega
split: 4
splitOpacity: 0.9
transform: Translate { id: drift }
Porthole {
size: 26
}
SplitText {
text: "YOUR"
pixelSize: Theme.fsMega
color: Theme.accent
split: 4
splitOpacity: 0.5
}
Item { width: 1; height: Theme.padM }
SplitText {
text: "TIME."
pixelSize: Theme.fsMega
split: 4
splitOpacity: 0.9
GlowText {
text: "Take your time."
pixelSize: Theme.fsHuge
letterSpacing: 1
active: root.open
}
Text {
text: "— TAE TAKEMI"
color: Theme.primary
topPadding: Theme.padS
leftPadding: 2
text: [(Quickshell.env("USER") || "user") + "@" + hostFile.text().trim(),
Sys.uptime ? "UP " + Sys.uptime : ""].filter(t => t !== "").join(" · ")
color: Theme.muted
font.family: Theme.fontMono
font.pixelSize: Theme.fsSmall
font.letterSpacing: 5
topPadding: 24
font.pixelSize: Theme.fsMicro
font.letterSpacing: 2
renderType: Text.NativeRendering
}
}
// -------------------------------------------------------------- actions
Column {
id: actions
anchors.right: parent.right
anchors.rightMargin: root.width * 0.12
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.padM
// The widest slab in the stack. Fixed rather than derived so the ragged
// x offsets below cannot feed back into the Column's own width.
readonly property int stackWidth: 312
width: stackWidth
Item { width: 1; height: Theme.padXL }
// ------------------------------------------------------------ menu
Repeater {
id: buttonRepeater
model: root.actionModel
// A carrier item owns the staggered slide so the button keeps its
// own hover transform.
Item {
id: entry
required property var modelData
required property int index
readonly property int slabWidth: actions.stackWidth
width: entry.slabWidth
height: 52
readonly property bool selected: root.selectedAction === entry.index
readonly property color tone: entry.modelData.danger ? Theme.alarm : Theme.accent
width: group.width
height: 44
opacity: 0
transform: [
Translate { id: slide; x: 90 },
Rotation {
id: tilt
angle: -3
origin.x: entry.slabWidth / 2
origin.y: 26
}
]
P5Button {
id: actionButton
anchors.fill: parent
text: entry.modelData.label
icon: entry.modelData.icon
destructive: entry.modelData.danger
selected: root.selectedAction === entry.index
accent: entry.modelData.danger ? Theme.alarm : Theme.primary
lean: Theme.skew
lunge: -10
onClicked: root.invoke(entry.modelData.act)
onHoveredChanged: if (hovered) root.selectedAction = entry.index
onActiveFocusChanged: if (actionButton.activeFocus) root.selectedAction = entry.index
Keys.forwardTo: [keyController]
transform: Translate { id: rise; y: 6 }
// The divider after the third row: what pauses the session
// above, what ends it below.
Rectangle {
visible: entry.index === 3
anchors.top: parent.top
x: Theme.padL
width: parent.width - Theme.padL * 2
height: 1
color: Theme.alpha(Theme.outline, 0.35)
}
// 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 {
target: root
function onOpenChanged() {
@@ -202,48 +223,31 @@ PanelWindow {
SequentialAnimation {
id: entryAnim
PauseAnimation { duration: 40 + entry.index * 55 }
PauseAnimation { duration: 120 + entry.index * 40 }
ParallelAnimation {
NumberAnimation {
target: entry; property: "opacity"
to: 1; duration: Theme.durBase
to: 1; duration: Theme.durSlow; easing.type: Easing.OutCubic
}
NumberAnimation {
target: slide; property: "x"
from: 90; to: 0
duration: Theme.durSlow
easing.type: Easing.OutBack
}
// 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.OutBack
easing.overshoot: 2.2
target: rise; property: "y"
from: 6; to: 0; duration: Theme.durSlow; easing.type: Easing.OutCubic
}
}
}
}
}
}
// Hint line.
Text {
anchors.bottom: parent.bottom
anchors.bottomMargin: 40
anchors.horizontalCenter: parent.horizontalCenter
text: "↑ ↓ SELECT ENTER CONFIRM ESC CANCEL"
color: Theme.muted
font.family: Theme.fontMono
font.pixelSize: Theme.fsMicro
font.letterSpacing: 4
opacity: backdrop.opacity
renderType: Text.NativeRendering
Text {
topPadding: Theme.padXL
leftPadding: Theme.padL
text: "↑ ↓ SELECT ENTER CONFIRM ESC CANCEL"
color: Theme.overlay
font.family: Theme.fontMono
font.pixelSize: Theme.fsMicro
font.letterSpacing: 2
renderType: Text.NativeRendering
}
}
function invoke(what: string) {
@@ -286,16 +290,15 @@ PanelWindow {
id: openAnim
NumberAnimation {
target: backdrop; property: "opacity"
to: 1; duration: Theme.durBase; easing.type: Easing.OutQuad
}
// 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
to: 1; duration: Theme.durSlow; easing.type: Easing.OutCubic
}
NumberAnimation {
target: title; property: "opacity"
to: 1; duration: Theme.durSlow
target: group; property: "opacity"
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
}
}
@@ -303,14 +306,10 @@ PanelWindow {
id: closeAnim
NumberAnimation {
target: backdrop; property: "opacity"
to: 0; duration: Theme.durFast
to: 0; duration: Theme.durBase; easing.type: Easing.InCubic
}
NumberAnimation {
target: ground; property: "reveal"
to: 0; duration: Theme.durFast
}
NumberAnimation {
target: title; property: "opacity"
target: group; property: "opacity"
to: 0; duration: Theme.durFast
}
}
+158 -102
View File
@@ -1,4 +1,5 @@
import QtQuick
import QtQuick.Shapes
import Quickshell
import Quickshell.Wayland
import Quickshell.Services.Notifications
@@ -6,7 +7,14 @@ import "root:/config"
import "root:/components"
import "root:/services"
// Floating notification stack under the bar, top right. Replaces dunst.
// Notification drawer, docked in the top-right corner of the opening.
// Replaces dunst.
//
// Not a stack of floating cards: one hull surface lowered out of the bar and
// out of the rail at once, the same construction as a corner-docked popout,
// with each notification an entry in it divided by its own header hairline.
// The drawer grows and shrinks with the entries and slides back behind the
// frame when the last one goes.
PanelWindow {
id: root
@@ -15,23 +23,47 @@ PanelWindow {
// A toast window exists on every screen, but each notification lands only
// on the monitor that had focus when it arrived. Latched per toast rather
// than live, so a card does not hop screens while it is being read.
// than live, so an entry does not hop screens while it is being read.
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.namespace: "takemi-toasts"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
exclusionMode: ExclusionMode.Ignore
// One pixel over the bar and the rail, so the drawer covers the rim the
// hull draws along both edges instead of sitting inside it.
anchors { top: true; right: true }
margins {
top: Theme.barHeight + Theme.barMargin * 2
right: Theme.barMargin
top: Theme.barHeight - 1
right: Theme.railWidth - 1
}
implicitWidth: 400
implicitHeight: Math.max(1, stack.implicitHeight)
readonly property real fillet: Theme.frameRadius
readonly property real corner: Theme.frameRadius
readonly property real seam: 1
readonly property real drawerWidth: 400
// Height of the entries, eased, so the drawer grows rather than jumps.
property real bodyHeight: stack.implicitHeight
Behavior on bodyHeight {
NumberAnimation { duration: Theme.durBase; easing.type: Easing.OutCubic }
}
// 0 = behind the frame, 1 = out. Follows whether anything is live.
property real reveal: root.live.length > 0 ? 1 : 0
Behavior on reveal {
NumberAnimation {
duration: Theme.durSlow
easing.type: root.live.length > 0 ? Easing.OutCubic : Easing.InCubic
}
}
implicitWidth: root.drawerWidth + root.fillet + root.seam
implicitHeight: Math.max(1, root.bodyHeight + root.fillet + root.seam)
color: "transparent"
exclusionMode: ExclusionMode.Ignore
visible: root.live.length > 0
visible: root.live.length > 0 || root.reveal > 0
mask: Region { item: stack }
// Toasts currently on screen, newest first.
property list<Notification> live: []
@@ -53,106 +85,130 @@ PanelWindow {
root.live = next;
}
Column {
id: stack
anchors.right: parent.right
anchors.top: parent.top
width: parent.width
spacing: Theme.padS
Item {
anchors.fill: parent
clip: true
Repeater {
model: root.live
Item {
id: sheet
width: parent.width
height: parent.height
y: -height * (1 - root.reveal)
Item {
id: toast
required property var modelData
// Off the bar through the left fillet, round the one free corner,
// onto the rail through the bottom fillet — Popout's "topRight".
Shape {
id: silhouette
anchors.fill: parent
preferredRendererType: Shape.CurveRenderer
width: stack.width
height: card.implicitHeight
readonly property real w: width
readonly property real h: height
readonly property real f: root.fillet
readonly property real r: Math.min(root.corner, Math.max(0, (h - f) / 2))
readonly property string edge:
`M 0 0 A ${f} ${f} 0 0 1 ${f} ${f}`
+ ` V ${h - f - r} A ${r} ${r} 0 0 0 ${f + r} ${h - f}`
+ ` H ${w - f} A ${f} ${f} 0 0 1 ${w} ${h}`
NotifCard {
id: card
width: parent.width
notif: toast.modelData
onDismissed: {
Notifs.dismiss(toast.modelData);
root.retire(toast.modelData);
ShapePath {
fillColor: Theme.hull
strokeColor: "transparent"
PathSvg { path: silhouette.edge + ` L ${silhouette.w} 0 Z` }
}
ShapePath {
fillColor: "transparent"
strokeColor: Theme.rim
strokeWidth: 1
PathSvg { path: silhouette.edge }
}
}
Column {
id: stack
x: root.fillet
y: root.seam
width: root.drawerWidth
bottomPadding: Theme.padS
Repeater {
model: root.live
Item {
id: toast
required property var modelData
width: stack.width
height: card.implicitHeight
NotifCard {
id: card
width: parent.width
notif: toast.modelData
onDismissed: {
Notifs.dismiss(toast.modelData);
root.retire(toast.modelData);
}
}
// Countdown: a hairline draining along the entry's
// foot, inset to the text column.
Rectangle {
id: fuse
anchors.bottom: parent.bottom
x: Theme.padM + Theme.marker + Theme.padS
height: 1
width: 0
color: Theme.alpha(card.tone, 0.5)
visible: life.running
}
readonly property real fuseSpan:
width - Theme.padM * 2 - Theme.marker - Theme.padS
NumberAnimation {
id: life
target: fuse
property: "width"
from: toast.fuseSpan
to: 0
running: false
duration: Notifs.timeoutFor(toast.modelData)
easing.type: Easing.Linear
onFinished: root.retire(toast.modelData)
}
// A new entry resolves in place: a fade, no shove.
opacity: 0
Component.onCompleted: {
enter.start();
const ms = Notifs.timeoutFor(toast.modelData);
if (ms > 0) life.start();
}
NumberAnimation {
id: enter
target: toast; property: "opacity"
to: 1; duration: Theme.durSlow; easing.type: Easing.OutCubic
}
// Hovering an entry pauses its fuse.
HoverHandler {
onHoveredChanged: {
if (!life.duration) return;
if (hovered) life.pause();
else if (life.paused) life.resume();
}
}
TapHandler {
acceptedButtons: Qt.LeftButton
onTapped: root.retire(toast.modelData)
}
}
}
// Countdown rule that drains along the bottom edge.
//
// It used to be a full-bleed rectangle across the whole toast,
// which meant it ran straight through the card's chopped
// bottom-left corner and out the other side — a stray bar
// hanging off the card at the one place the silhouette is
// supposed to be cut. It lives inside the cut now, and ends
// where the leaning right edge does.
readonly property real fuseSpan: Math.max(0,
width - card.chop - card.implicitHeight * card.lean)
Skew {
id: fuse
anchors.bottom: parent.bottom
anchors.bottomMargin: 1
x: card.chop
height: 2
width: toast.fuseSpan
lean: card.lean
color: Theme.alpha(card.tone, 0.7)
visible: life.running
}
NumberAnimation {
id: life
target: fuse
property: "width"
from: toast.fuseSpan
to: 0
running: false
duration: Notifs.timeoutFor(toast.modelData)
easing.type: Easing.Linear
onFinished: root.retire(toast.modelData)
}
// Slide in from the right, Persona-style.
opacity: 0
transform: Translate { id: shove; x: 60 }
Component.onCompleted: {
enter.start();
const ms = Notifs.timeoutFor(toast.modelData);
if (ms > 0) life.start();
}
ParallelAnimation {
id: enter
NumberAnimation {
target: toast; property: "opacity"
to: 1; duration: Theme.durBase
}
NumberAnimation {
target: shove; property: "x"
from: 60; to: 0
duration: Theme.durSlow
easing.type: Easing.OutBack
}
}
// Hovering a toast pauses its fuse.
HoverHandler {
id: toastHover
onHoveredChanged: {
if (!life.duration) return;
if (hovered) life.pause();
else if (life.paused) life.resume();
}
}
TapHandler {
acceptedButtons: Qt.LeftButton
onTapped: root.retire(toast.modelData)
}
}
}
}
-1
View File
@@ -36,7 +36,6 @@ Item {
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsSmall
font.weight: Font.DemiBold
font.italic: true
renderType: Text.NativeRendering
}
-2
View File
@@ -16,7 +16,6 @@ Item {
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsHuge
font.weight: Theme.weightDisplay
font.italic: true
renderType: Text.NativeRendering
}
@@ -45,7 +44,6 @@ Item {
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsSmall
font.weight: Font.DemiBold
font.italic: true
renderType: Text.NativeRendering
}
-1
View File
@@ -163,7 +163,6 @@ Popout {
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsSmall
font.weight: Font.DemiBold
font.italic: true
renderType: Text.NativeRendering
}
+3 -3
View File
@@ -99,10 +99,10 @@ Popout {
anchors.topMargin: Theme.padM
anchors.left: parent.left
anchors.right: parent.right
height: 12
height: 4
segments: 20
segmentWidth: (width - 19 * 3) / 20
spacing: 3
segmentWidth: (width - 19 * 2) / 20
spacing: 2
value: Battery.percent
activeColor: Battery.tint
}
+3 -3
View File
@@ -130,7 +130,8 @@ Popout {
Skew {
anchors.centerIn: parent
width: 26
height: 24
height: 26
radius: 13
visible: cell.modelData.today
color: Theme.accent
}
@@ -145,8 +146,7 @@ Popout {
}
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsSmall
font.italic: true
font.weight: cell.modelData.today ? Font.Black : Font.Medium
font.weight: cell.modelData.today ? Font.DemiBold : Font.Medium
renderType: Text.NativeRendering
}
}
+12 -56
View File
@@ -1,6 +1,7 @@
import QtQuick
import QtQuick.Shapes
import Quickshell
import Quickshell.Widgets
import "root:/config"
import "root:/components"
import "root:/services"
@@ -32,62 +33,31 @@ Popout {
subtitle: Media.identity
}
// Cover art, chopped.
Item {
// Cover art, rounded, in a hairline frame.
ClippingRectangle {
id: artFrame
anchors.top: header.bottom
anchors.topMargin: Theme.padM
anchors.left: parent.left
width: 108
height: 108
Shape {
id: artMask
anchors.fill: parent
visible: false
layer.enabled: true
// Without this the mask texture is point-sampled, so the
// antialiased pixels along the cuts get snapped back to hard
// steps before MultiEffect ever sees them — see MaskedArt.
layer.smooth: true
preferredRendererType: Shape.CurveRenderer
ShapePath {
fillColor: "white"
strokeColor: "transparent"
PathPolyline {
path: Geom.chamfer(Geom.parallelogram(108, 108, 0.1), 18, [1, 3])
}
}
}
radius: Theme.radiusL
color: Theme.surface
border.width: 1
border.color: Theme.overlay
Image {
id: art
anchors.fill: parent
anchors.margins: 1
source: Media.artUrl
fillMode: Image.PreserveAspectCrop
asynchronous: true
cache: true
visible: status === Image.Ready
layer.enabled: true
layer.smooth: true
layer.effect: MaskedArt { maskItem: artMask }
}
// Placeholder when the player exposes no artwork.
Shape {
anchors.fill: parent
visible: art.status !== Image.Ready
preferredRendererType: Shape.CurveRenderer
ShapePath {
fillColor: Theme.surface
strokeColor: Theme.surfaceAlt
strokeWidth: 2
PathPolyline {
path: Geom.chamfer(Geom.parallelogram(108, 108, 0.1), 18, [1, 3])
}
}
}
Icon {
anchors.centerIn: parent
visible: art.status !== Image.Ready
@@ -95,16 +65,6 @@ Popout {
font.pixelSize: 42
color: Theme.muted
}
// Spinning rule around the art while playing.
Rectangle {
anchors.right: parent.right
anchors.bottom: parent.bottom
width: 30
height: 3
color: Theme.accent
visible: Media.playing
}
}
Column {
@@ -123,8 +83,7 @@ Popout {
color: Theme.text
pixelSize: Theme.fsLarge
family: Theme.fontDisplay
weight: Font.Black
italic: true
weight: Font.DemiBold
}
Marquee {
@@ -134,7 +93,6 @@ Popout {
color: Theme.primary
pixelSize: Theme.fsSmall
family: Theme.fontDisplay
italic: true
weight: Font.DemiBold
}
@@ -260,6 +218,7 @@ Popout {
Skew {
anchors.fill: parent
radius: height / 2
color: !chip.enabled
? Theme.treatmentDisabledFill
: (chip.activeFocus
@@ -331,6 +290,7 @@ Popout {
Skew {
anchors.fill: parent
radius: width / 2
color: {
if (!tb.enabled) return "transparent";
if (tb.activeFocus) return Theme.treatmentFocusFill;
@@ -359,16 +319,12 @@ Popout {
color: {
if (!tb.enabled) return Theme.alpha(Theme.muted, 0.5);
if (tb.activeFocus) return Theme.treatmentFocusText;
// Ink on the accent slab. White on light blue was barely 2:1.
// Ink on the accent slab, for contrast.
if (tbHover.hovered && tb.primary) return Theme.ink;
return Theme.text;
}
}
scale: tbHover.hovered && tb.enabled ? 1.12 : 1.0
Behavior on scale {
NumberAnimation { duration: Theme.durBase; easing.type: Easing.OutBack }
}
HoverHandler {
id: tbHover
+3 -9
View File
@@ -56,8 +56,7 @@ Popout {
color: Theme.muted
font.family: Theme.fontDisplay
font.pixelSize: Theme.fsBody
font.italic: true
font.weight: Font.Black
font.weight: Font.DemiBold
font.letterSpacing: 3
renderType: Text.NativeRendering
}
@@ -69,7 +68,8 @@ Popout {
anchors.right: parent.right
anchors.bottom: parent.bottom
clip: true
spacing: Theme.padS
// Entries carry their own header hairline, so they sit flush.
spacing: 0
model: Notifs.history
delegate: NotifCard {
@@ -80,12 +80,6 @@ Popout {
}
add: Transition {
NumberAnimation {
properties: "x"
from: 40
duration: Theme.durSlow
easing.type: Easing.OutBack
}
NumberAnimation {
property: "opacity"
from: 0; to: 1
+12 -12
View File
@@ -2,8 +2,8 @@ import QtQuick
import "root:/config"
import "root:/components"
// Title bar for a popout: glyph, print-shadowed heading, right-aligned status
// text, and a rule with a chopped accent tail.
// Title bar for a popout, in the NieR header pattern: glyph, tracked heading,
// right-aligned status text, and a hairline rule that ends in a dot.
Item {
id: root
@@ -17,7 +17,7 @@ Item {
Row {
anchors.left: parent.left
anchors.top: parent.top
spacing: Theme.padS
spacing: Theme.padM
Icon {
anchors.verticalCenter: parent.verticalCenter
@@ -31,7 +31,6 @@ Item {
anchors.verticalCenter: parent.verticalCenter
text: root.title.toUpperCase()
pixelSize: Theme.fsLarge
letterSpacing: 1.5
}
}
@@ -50,19 +49,20 @@ Item {
Rectangle {
anchors.bottom: parent.bottom
anchors.bottomMargin: (Theme.marker - 1) / 2
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: 22
height: 2
color: Theme.alpha(root.accent, 0.8)
anchors.right: endMark.left
height: 1
color: Theme.alpha(root.accent, 0.6)
}
Skew {
Rectangle {
id: endMark
anchors.bottom: parent.bottom
anchors.right: parent.right
width: 16
height: 2
width: Theme.marker
height: Theme.marker
radius: width / 2
color: Theme.accent
lean: 1.4
}
}
+16 -13
View File
@@ -8,22 +8,25 @@ P5Panel {
property color tone: Theme.primary
property bool textured: false
fill: Theme.mantle
fillOpacity: 0.97
// A neutral keyline. `tone` used to colour the whole border at 75%, and
// since most panels pass the resting text colour that put a pale 2px frame
// around every popout — the loudest thing on screen for no reason. The
// tone lives in the slash now; the frame is structure.
// Inside a Popout the panel is part of the hull, which paints the fill
// and the rim itself; the surface only lays out the content. Elsewhere —
// the launcher, the ask panel — it is a free-standing panel of its own.
readonly property bool attached: parent?.frameAttached ?? false
fill: attached ? "transparent" : Theme.mantle
// Zero, not a transparent fill: P5Panel re-applies this opacity to the
// fill colour, which turned "transparent" into 97% black.
fillOpacity: attached ? 0 : 0.97
// The frame is structure: a neutral hairline. A free-standing panel takes
// the frame's own corner radius, so it reads as a piece of the hull set
// down somewhere else rather than as a hard card.
border: Theme.surfaceAlt
borderWidth: Theme.stroke
borderWidth: attached ? 0 : Theme.stroke
halftone: textured
halftoneColor: tone
halftoneOpacity: 0.06
sheen: true
slash: true
slashColor: tone
slashWidth: 5
lean: 0.06
chop: 16
ticks: false
radius: Theme.frameRadius
tickColor: tone
padding: Theme.padM
}
+3 -4
View File
@@ -29,7 +29,7 @@ PopupWindow {
// The slab's own lean and padding, which the entries sit inside. Sizing the
// window to the entry widths alone left every label short by that inset and
// elided perfectly ordinary items.
readonly property real slabLean: 0.06
readonly property real slabLean: Theme.skew
readonly property real slabPad: Theme.padS
readonly property real contentHeight: list.implicitHeight + slabPad * 2
@@ -114,7 +114,6 @@ PopupWindow {
anchors.fill: parent
tone: Theme.accent
padding: root.slabPad
chop: 12
lean: root.slabLean
TrayMenuList {
@@ -139,12 +138,12 @@ PopupWindow {
}
NumberAnimation {
target: holder; property: "scale"
to: 1.0; duration: Theme.durSlow; easing.type: Easing.OutBack
to: 1.0; duration: Theme.durSlow; easing.type: Easing.OutCubic
}
NumberAnimation {
target: holder; property: "x"
from: root.bleed + 14; to: root.bleed
duration: Theme.durSlow; easing.type: Easing.OutBack
duration: Theme.durSlow; easing.type: Easing.OutCubic
}
}
+5 -19
View File
@@ -84,19 +84,12 @@ Column {
width: root.width
// ------------------------------------------------------ separator
//
// A horizontal rule slants by a fraction of its own width, so at
// menu width the house lean turns a hairline into a 30px diagonal
// gash straight across the entries above and below it. The rise is
// pinned in pixels instead, matching the rail's dividers.
Skew {
Rectangle {
anchors.verticalCenter: parent.verticalCenter
visible: row.entry.isSeparator
width: parent.width - Theme.padM * 2
x: Theme.padM
height: 1
vertical: true
lean: width > 0 ? 5 / width : 0
color: Theme.alpha(Theme.outline, 0.9)
}
@@ -113,6 +106,7 @@ Column {
anchors.fill: parent
anchors.leftMargin: Theme.padS
anchors.rightMargin: Theme.padS
radius: Theme.radiusM
color: row.hovered ? Theme.accent : "transparent"
Behavior on color {
@@ -226,20 +220,12 @@ Column {
NumberAnimation { duration: Theme.durFast; easing.type: Easing.OutExpo }
}
// The guide rule down the side of an expanded submenu.
//
// Same trap the separators hit, from the other direction: the
// house lean is a fraction of *height*, and this rule is as tall
// as the submenu. A Wi-Fi list of thirty networks made it 600px
// tall, which sheared a 1px hairline into a 150px crimson wedge
// lying across every entry in the list. The rise is pinned in
// pixels, and it is declared before the entries so it can never
// paint over them again.
Skew {
// The guide rule down the side of an expanded submenu,
// declared before the entries so it can never paint over them.
Rectangle {
x: Theme.padS
width: 1
height: parent.height
lean: height > 0 ? 5 / height : 0
visible: row.expanded
color: Theme.alpha(Theme.accent, 0.8)
}
+26
View File
@@ -18,6 +18,7 @@ Singleton {
property bool sidebarOpen: false
property bool launcherOpen: false
property bool askOpen: false
property bool dashboardOpen: false
// Seed text for the next launcher open. Lets a keybind or a script hand the
// launcher a query it did not have to type — `qs ipc call shell prompt
@@ -36,6 +37,15 @@ Singleton {
// …and for the ask panel, which also takes the keyboard.
property string askScreen: ""
// …and for the dashboard drawer.
property string dashboardScreen: ""
// When the dashboard was last closed from outside. A click on the bar's
// clock first clears the drawer's focus grab, which closes it, and then
// lands on the clock, which would toggle it straight back open. A toggle
// that arrives within a moment of a grab-close is that same click.
property real dashboardClosedAt: 0
// Seed text for the next ask open. A seeded open asks straight away, so
// `qs ipc call shell askAbout "$(wl-paste)"` is a one-shot question.
property string askSeed: ""
@@ -65,6 +75,19 @@ Singleton {
}
function closeLauncher() { root.launcherOpen = false; }
function toggleDashboard() {
if (!root.dashboardOpen && Date.now() - root.dashboardClosedAt < 250)
return;
if (!root.dashboardOpen)
root.dashboardScreen = root.focusedScreenName();
root.dashboardOpen = !root.dashboardOpen;
}
function closeDashboard() {
if (!root.dashboardOpen) return;
root.dashboardOpen = false;
root.dashboardClosedAt = Date.now();
}
function toggleAsk() {
if (!root.askOpen) {
root.askSeed = "";
@@ -131,6 +154,7 @@ Singleton {
function power(): void { root.togglePowerMenu(); }
function launcher(): void { root.toggleLauncher(); }
function ask(): void { root.toggleAsk(); }
function dashboard(): void { root.toggleDashboard(); }
function askAbout(question: string): void { root.askAbout(question); }
function prompt(seed: string): void { root.promptLauncher(seed); }
function dmenu(inPath: string, outPath: string, prompt: string): void {
@@ -139,5 +163,7 @@ Singleton {
function reloadConfig(): void { root.reloadShell(); }
function toggleDnd(): void { Notifs.toggleDnd(); }
function clearNotifications(): void { Notifs.clearAll(); }
function toggleStayAwake(): void { Idle.toggleStayAwake(); }
function wallpaper(path: string): void { Wallpaper.set(path); }
}
}
+68
View File
@@ -0,0 +1,68 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Hyprland
import Quickshell.Wayland
import "root:/services"
// What happens when nobody is at the machine. There is no hypridle: the shell
// already owns the lock screen, so it owns the clock that leads up to it.
//
// dim the screen slowly darkens, so the lock does not arrive unannounced
// lock the session lock (lock/Lock.qml)
// off the displays power down; any input wakes them
// suspend only on battery, and only after a long while
//
// Every stage respects idle inhibitors, so a fullscreen video or a call that
// asks the compositor to stay awake holds all of them off. `stayAwake` is the
// same thing by hand (`qs ipc call shell toggleStayAwake`).
Singleton {
id: root
// Seconds of no input before each stage.
readonly property int dimAfter: 240
readonly property int lockAfter: 300
readonly property int offAfter: 360
readonly property int suspendAfter: 900
property bool stayAwake: false
readonly property bool dimmed: dimMon.isIdle && !Actions.lockRequested
function toggleStayAwake() { root.stayAwake = !root.stayAwake; }
IdleMonitor {
id: dimMon
enabled: !root.stayAwake
respectInhibitors: true
timeout: root.dimAfter
}
IdleMonitor {
id: lockMon
enabled: !root.stayAwake
respectInhibitors: true
timeout: root.lockAfter
onIsIdleChanged: if (isIdle) Actions.lock()
}
IdleMonitor {
id: offMon
enabled: !root.stayAwake
respectInhibitors: true
timeout: root.offAfter
// Input ends the idle period, so the displays come back on the same
// event that would have woken them anyway.
onIsIdleChanged: Hyprland.dispatch(
"hl.dsp.dpms({ action = \"" + (isIdle ? "off" : "on") + "\" })")
}
IdleMonitor {
id: suspendMon
enabled: !root.stayAwake && Battery.onBattery
respectInhibitors: true
timeout: root.suspendAfter
onIsIdleChanged: if (isIdle) Actions.suspend()
}
}
+17
View File
@@ -10,6 +10,9 @@ Singleton {
id: root
property string preferredId: ""
// The player that was playing most recently, so pausing it keeps it in
// the bar instead of handing the slot to whichever player is listed first.
property string lastPlayingId: ""
readonly property var players: Mpris.players?.values ?? []
readonly property bool hasPlayer: active !== null
@@ -22,9 +25,23 @@ Singleton {
for (const p of players) if (p.dbusName === root.preferredId) return p;
}
for (const p of players) if (p.isPlaying) return p;
if (root.lastPlayingId !== "") {
for (const p of players) if (p.dbusName === root.lastPlayingId) return p;
}
// Browsers register a player for every tab with a media element, most
// of them with no track at all. Prefer one that has something to show.
for (const p of players) if (p.trackTitle) return p;
return players[0];
}
// Remember whichever player was playing last. Bound through the active
// player, so it updates whenever playback starts anywhere.
readonly property string playingId: {
for (const p of players) if (p.isPlaying) return p.dbusName;
return "";
}
onPlayingIdChanged: if (root.playingId !== "") root.lastPlayingId = root.playingId
readonly property string title: active?.trackTitle || ""
readonly property string artist: active?.trackArtist || ""
readonly property string album: active?.trackAlbum || ""
+24 -82
View File
@@ -5,24 +5,17 @@ import Quickshell
import Quickshell.Io
import "root:/config"
// The wallpaper currently on screen, per monitor.
// The wallpaper, owned by the shell. backdrop/Backdrop.qml draws it and the lock
// screen fades in from the same picture, so there is one answer to "what is
// on screen" and nothing to ask a separate daemon (this used to poll
// hyprpaper every ten seconds).
//
// The lock screen used to point at a hardcoded path in Theme.qml, which meant
// it showed whatever picture was correct on the day that line was written —
// takemi_2 while hyprpaper had been switched to takemi_1. Anything that puts
// the wallpaper on screen should ask hyprpaper what it is actually displaying,
// which is what this does.
//
// hyprpaper is the source of truth at runtime; hyprpaper.conf is the fallback
// for the window between the shell starting and hyprpaper answering, and for
// the case where hyprpaper is not running at all.
// The choice lives in ~/.config/quickshell/wallpaper — one line, a path, `~`
// allowed — so it survives restarts and can be edited by hand. Change it live
// with `qs ipc call shell wallpaper ~/.wallpapers/other.jpg`.
Singleton {
id: root
// monitor name -> file path. Monitors hyprpaper set with a blank `monitor =`
// come back under the empty-string key.
property var byMonitor: ({})
// The wallpaper to use when no particular monitor is being asked about.
property string path: ""
@@ -30,18 +23,19 @@ Singleton {
? Qt.resolvedUrl("file://" + root.path)
: Theme.fallbackWallpaper
// Path set from hyprpaper.conf, used until (and unless) hyprctl answers.
property string configPath: ""
signal changed()
// Every monitor shows the same picture. Kept as a per-monitor lookup so
// callers do not have to change if that stops being true.
function forMonitor(name: string): url {
const p = root.byMonitor[name];
return p ? Qt.resolvedUrl("file://" + p) : root.source;
return root.source;
}
function refresh() {
reader.running = true;
function set(p: string) {
const next = root._expand(p);
if (next === "") return;
store.setText("# The wallpaper the shell draws (services/Wallpaper.qml). One path; ~ allowed.\n"
+ next + "\n");
}
function _expand(p: string): string {
@@ -50,71 +44,19 @@ Singleton {
return s;
}
function _adopt(map: var, fallback: string) {
root.byMonitor = map;
// Prefer the focused monitor's wallpaper, then any monitor's, then the
// config. Object.values order is insertion order here, which is the
// order hyprpaper listed them in.
const vals = Object.keys(map).map(k => map[k]).filter(v => v !== "");
const next = vals.length > 0 ? vals[0] : fallback;
if (next !== root.path) {
root.path = next;
root.changed();
}
}
Process {
id: reader
command: ["hyprctl", "hyprpaper", "listactive"]
stdout: StdioCollector {
onStreamFinished: {
const map = {};
for (const line of text.split("\n")) {
// "eDP-1: /home/narl/.wallpapers/takemi_1.jpg". Split on the
// first colon only — the path may contain one.
const at = line.indexOf(":");
if (at === -1) continue;
const mon = line.slice(0, at).trim();
const file = line.slice(at + 1).trim();
if (file === "" || file.startsWith("invalid")) continue;
map[mon] = file;
}
root._adopt(map, root.configPath);
}
}
// hyprpaper down, or hyprctl missing. Fall back to the config file.
onExited: code => {
if (code !== 0 && root.path === "") root._adopt({}, root.configPath);
}
}
// hyprpaper has no change signal to subscribe to, so poll — but slowly. A
// wallpaper changes when a person decides to change it, not continuously.
Timer {
interval: 10000
running: true
repeat: true
triggeredOnStart: true
onTriggered: root.refresh()
}
// Editing the config is the other way the wallpaper changes here, and that
// one we can watch properly.
FileView {
id: conf
path: Quickshell.env("HOME") + "/.config/hypr/hyprpaper.conf"
id: store
path: Quickshell.env("HOME") + "/.config/quickshell/wallpaper"
watchChanges: true
printErrors: false
onFileChanged: reload()
onLoaded: {
const m = /^\s*path\s*=\s*(.+)$/m.exec(text());
if (m) root.configPath = root._expand(m[1]);
// A config edit is usually followed by a hyprpaper reload, so go
// ask what actually took effect rather than trusting the file.
root.refresh();
const line = text().split("\n").map(l => l.trim()).find(l => l !== "" && !l.startsWith("#"));
const next = line ? root._expand(line) : "";
if (next !== root.path) {
root.path = next;
root.changed();
}
}
}
}
Loaded 100 of 114 files, more files were not shown because too many files have changed in this diff. Show more