Add bread_theme::shell manifest system (Phase 1)
Implements the shell theme manifest layer from THEME_SYSTEM_PLAN.md §4-5: ShellTheme/WindowSpec/Slots/Tokens/LayerRule types, TOML discovery (user -> system -> compiled-in builtin), one level of `extends` deep-merge, deny_unknown_fields validation naming the offending key, slot module-name validation, and css() token substitution with an extra.css overlay. load() never fails, falling back to the compiled-in builtin and logging once. Ships exactly one builtin manifest, liquid-motion, describing breadbar/breadbox as they exist today (not the design-doc demo, which disagrees with the code on bar side margin, launcher geometry, and the easing curves). Compositor rules and surface specs are keyed by layer-shell namespace and cover all five breadbar namespaces plus breadbox/breadbar-panel/breadbar-dismiss. watch() is gated behind the existing `gtk` feature (gio::FileMonitor is a gtk4 dependency); the rest of the module is gtk-free so bread and breadcrumbs can validate a theme without linking GTK. No consumer changes — breadbar/breadbox still use their own hardcoded values.
This commit is contained in:
parent
347f356b1d
commit
96aa6a513b
10 changed files with 2097 additions and 0 deletions
104
bread-theme/assets/shell/liquid-motion/liquid-motion.css
Normal file
104
bread-theme/assets/shell/liquid-motion/liquid-motion.css
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/* CSS template for the liquid-motion builtin (bread-theme/src/shell/builtin.rs).
|
||||
*
|
||||
* Curly-brace placeholders like {radius_bar} or {spring} are substituted
|
||||
* from [tokens] by ShellTheme::css / Tokens::substitute; @-prefixed palette
|
||||
* references (@accent, @on-bg, ...) pass through untouched, exactly like
|
||||
* `bread_theme::stylesheet()` — GTK's own @define-color mechanism resolves
|
||||
* them when the CSS provider is attached to a display/output.
|
||||
*
|
||||
* Scope: this is the window/workspace/clock chrome the manifest's own
|
||||
* concepts (bar.window, modules.workspaces, modules.clock) actually model —
|
||||
* not a byte-for-byte copy of breadbar/src/theme.rs::load_css's full ~250
|
||||
* lines (notification cards, wifi popover, control panel, media widget, ...).
|
||||
* Those stay hand-written in breadbar for now; migrating them behind this
|
||||
* same token-substitution mechanism is Phase 2/3 work (plan §6), once
|
||||
* breadbar actually consumes ShellTheme and can verify pixel parity itself
|
||||
* via --screenshot. Phase 1's job is the mechanism, exercised end-to-end
|
||||
* here with a representative slice, not the full port.
|
||||
*/
|
||||
|
||||
@keyframes row-in {
|
||||
from { opacity: 0; margin-top: 8px; }
|
||||
to { opacity: 1; margin-top: 0; }
|
||||
}
|
||||
@keyframes digit-flip {
|
||||
from { opacity: 0; margin-top: 7px; }
|
||||
to { opacity: 1; margin-top: 0; }
|
||||
}
|
||||
|
||||
window.breadbar {
|
||||
background-color: alpha(@bg, {bg_alpha});
|
||||
color: @on-bg;
|
||||
border-radius: {radius_bar}px;
|
||||
border: 1px solid alpha(@on-bg, 0.08);
|
||||
}
|
||||
window.breadbar > centerbox { padding: 0 8px 0 6px; }
|
||||
window.breadbar button { min-height: 0; min-width: 0; }
|
||||
|
||||
.workspace-trail {
|
||||
background-image: linear-gradient(90deg, @{accent_from}, @{accent_to});
|
||||
background-color: @{accent_from};
|
||||
border-radius: {radius_card}px;
|
||||
}
|
||||
.workspace-btn {
|
||||
background: transparent;
|
||||
opacity: 0.36;
|
||||
color: @on-bg;
|
||||
border-radius: {radius_card}px;
|
||||
border: none;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
min-width: 28px;
|
||||
min-height: {chip_height}px;
|
||||
margin: 0;
|
||||
padding: 0 7px;
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
transition: opacity 0.22s {spring_settle}, background-color 0.22s {spring_settle};
|
||||
}
|
||||
.workspace-btn:hover { opacity: 0.85; background: alpha(@on-bg, 0.08); }
|
||||
.workspace-btn.occupied { opacity: 0.78; }
|
||||
.workspace-btn.active { background: transparent; color: @on-accent; opacity: 1; }
|
||||
.workspace-btn.active:hover { background: transparent; }
|
||||
.workspace-btn.ws-in { animation: row-in 0.32s {spring_settle} both; }
|
||||
|
||||
.clock-box { padding: 0 4px; }
|
||||
.clock-label {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 0.04em;
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
margin-top: 3px;
|
||||
}
|
||||
.clock-digit {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 0.04em;
|
||||
min-width: 15px;
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.clock-colon { min-width: 10px; opacity: 0.7; }
|
||||
.clock-digit.flip { animation: digit-flip 0.45s {spring} both; }
|
||||
|
||||
window.breadbar-osd {
|
||||
background-color: alpha(@bg, 0.70);
|
||||
color: @on-bg;
|
||||
border-radius: {radius_pill}px;
|
||||
border: 1px solid alpha(@on-bg, 0.10);
|
||||
}
|
||||
|
||||
window.breadbar-panel {
|
||||
background-color: alpha(@bg, {bg_alpha});
|
||||
color: @on-bg;
|
||||
border-radius: 14px;
|
||||
border: 1px solid alpha(@on-bg, 0.12);
|
||||
}
|
||||
|
||||
window.breadbar-dismiss {
|
||||
background-color: alpha(#000000, 0.02);
|
||||
}
|
||||
|
||||
.bread-widget-slot { margin-right: {pad}px; }
|
||||
154
bread-theme/assets/shell/liquid-motion/theme.toml
Normal file
154
bread-theme/assets/shell/liquid-motion/theme.toml
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
# The compiled-in builtin theme (bread-theme/src/shell/builtin.rs). Describes
|
||||
# breadbar/breadbox AS THEY EXIST TODAY, not the 01-liquid-motion.html demo —
|
||||
# where the two disagree (bar side margin, launcher width/top, the two easing
|
||||
# curves), this file follows the current Rust source, since Phase 2's
|
||||
# acceptance test is pixel-identical rendering against today's bar.
|
||||
#
|
||||
# Sources: breadbar/src/main.rs:16-22 (BAR_* / CHIP_HEIGHT / ICON_PX consts),
|
||||
# breadbar/src/theme.rs (load_css's radius/pad/spring locals and the actual
|
||||
# CSS selectors), breadbar/src/{panel,osd}.rs and
|
||||
# breadbar/src/notifications/{popup,history}.rs (satellite window anchors,
|
||||
# margins, namespaces), breadbox/breadbox/src/main.rs:341-344 (launcher
|
||||
# geometry), ~/.config/hypr/scripts/ui/rules.lua (compositor rules).
|
||||
|
||||
name = "Liquid Motion"
|
||||
id = "liquid-motion"
|
||||
|
||||
[tokens]
|
||||
font_family = "Varela Round, sans-serif"
|
||||
font_fallback = "sans-serif"
|
||||
font_size_base = 14
|
||||
radius_bar = 16
|
||||
radius_card = 12
|
||||
radius_sm = 9
|
||||
# Not in the plan's §4 schema list, but a named local in theme.rs::load_css
|
||||
# alongside radius_bar/radius_card/radius_sm (`radius_pill = "999px"`) — the
|
||||
# OSD pill's corner radius.
|
||||
radius_pill = 999
|
||||
pad = 12
|
||||
bg_alpha = 0.72
|
||||
# Two curves theme.rs actually uses, not one: `spring` is the overshoot/bounce
|
||||
# curve (clock flips, pop-ins, the workspace caret draw); `spring_settle` is
|
||||
# the flatter curve used for hovers and workspace/stat-pair transitions. The
|
||||
# plan's §4 example names only `spring`.
|
||||
spring = "cubic-bezier(0.22, 1.35, 0.36, 1)"
|
||||
spring_settle = "cubic-bezier(0.22, 1.2, 0.36, 1)"
|
||||
# "accent" flows through as @accent (the workspace-trail gradient's start);
|
||||
# these are palette token NAMES, not hex - pywal still drives colour.
|
||||
accent_from = "accent"
|
||||
accent_to = "teal"
|
||||
# Not in the plan's §4 schema list, but breadbar::CHIP_HEIGHT / ::ICON_PX
|
||||
# today.
|
||||
chip_height = 32
|
||||
icon_px = 24
|
||||
|
||||
[bar.window]
|
||||
anchors = ["top", "left", "right"]
|
||||
width = "fill"
|
||||
height = 44
|
||||
margin = { top = 12, left = 16, right = 16 }
|
||||
exclusive = "auto"
|
||||
# breadbar never calls gtk4-layer-shell's set_keyboard_mode today, which
|
||||
# defaults to KeyboardMode::None — spelled out explicitly here rather than
|
||||
# left to omit-means-default, since "the shell must never fail to start
|
||||
# because a theme file is malformed" cuts both ways: an explicit builtin
|
||||
# value can't silently drift if the crate's own Default ever changes.
|
||||
keyboard = "none"
|
||||
layer = "top"
|
||||
|
||||
[bar.slots]
|
||||
left = ["workspaces"]
|
||||
centre = ["media", "clock"]
|
||||
right = ["volume", "wifi", "battery", "control"]
|
||||
drawer = []
|
||||
|
||||
[modules.workspaces]
|
||||
style = "trail"
|
||||
show_empty = true
|
||||
|
||||
[modules.clock]
|
||||
style = "flip"
|
||||
format = "%H:%M"
|
||||
show_date = false
|
||||
|
||||
[launcher]
|
||||
# breadbox/breadbox/src/main.rs:341-344 sets vbox.set_margin_top(120) and
|
||||
# vbox.set_size_request(600, -1) — the demo's "540px overlay, 16% top" is a
|
||||
# design-doc approximation, not what the code does today.
|
||||
mode = "overlay"
|
||||
width = 600
|
||||
top = "120px"
|
||||
radius = 20
|
||||
icon_px = 36
|
||||
row_anim = "flip"
|
||||
rule = "gradient"
|
||||
footer = "count_apps"
|
||||
sections = false
|
||||
modes = ["apps"]
|
||||
|
||||
# Keyed by layer-shell namespace, matching [compositor.*] below, so the two
|
||||
# tables share one keyspace and can be validated against each other.
|
||||
# breadbar-notif's popup toast uses set_default_width(320) (its history
|
||||
# sibling on the same namespace uses 360 — 320 is the live-toast surface, the
|
||||
# one this positions). breadbar-panel sets no window width at all: its
|
||||
# popovers size from their own CSS (.control-panel-inner / .wifi-popover-inner
|
||||
# min-width), which is why its width is "auto" rather than a number.
|
||||
[surfaces."breadbar-notif"]
|
||||
anchor = "top_right"
|
||||
offset = [16, 64]
|
||||
width = 320
|
||||
layer = "overlay"
|
||||
|
||||
[surfaces."breadbar-osd"]
|
||||
anchor = "bottom_centre"
|
||||
offset = 80
|
||||
width = 180
|
||||
layer = "overlay"
|
||||
|
||||
[surfaces."breadbar-panel"]
|
||||
anchor = "top_right"
|
||||
offset = [16, 64]
|
||||
width = "auto"
|
||||
layer = "overlay"
|
||||
|
||||
[surfaces."breadbar-dismiss"]
|
||||
# Anchored to all four edges (a fullscreen click-away scrim) with only a top
|
||||
# margin, so it starts below the bar rather than covering it.
|
||||
anchor = "fill"
|
||||
offset = 56
|
||||
width = "fill"
|
||||
layer = "overlay"
|
||||
|
||||
# Faithful to scripts/ui/rules.lua's five breadbar namespaces + breadbox.
|
||||
[compositor."breadbar"]
|
||||
blur = true
|
||||
ignore_alpha = 0.2
|
||||
blur_popups = true
|
||||
animation = "slide top"
|
||||
|
||||
[compositor."breadbar-osd"]
|
||||
blur = true
|
||||
ignore_alpha = 0.2
|
||||
animation = "slide bottom"
|
||||
|
||||
[compositor."breadbar-notif"]
|
||||
blur = true
|
||||
ignore_alpha = 0.2
|
||||
animation = "slide right"
|
||||
|
||||
[compositor."breadbar-panel"]
|
||||
blur = true
|
||||
ignore_alpha = 0.2
|
||||
animation = "slide right"
|
||||
|
||||
[compositor."breadbar-dismiss"]
|
||||
no_anim = true
|
||||
|
||||
[compositor."breadbox"]
|
||||
blur = true
|
||||
ignore_alpha = 0.2
|
||||
|
||||
# No `css = "..."` overlay: the builtin is compiled in via `include_str!`
|
||||
# and has no on-disk directory to resolve a relative overlay path against.
|
||||
# `extra.css` is for user/system themes, which do have one — see
|
||||
# `resolve_theme` in mod.rs.
|
||||
Loading…
Add table
Add a link
Reference in a new issue