bar: implement glass-workbench's three module variants (Phase 5)

Wires modules.workspaces.style/modules.clock.style into the bar for real
(Phase 3 shipped the schema but only ever consumed trail/flip), and adds
cpu/ram as bar modules, so glass-workbench (bread-theme) renders correctly
while liquid-motion's default path is untouched:

- workspaces: "pill" never calls WorkspaceTrail::place/stretch (the trail
  overlay stays invisible) and honours modules.workspaces.show_empty for
  real, rendering unoccupied non-active workspaces dimmed via CSS instead
  of filtering them out of the row. "trail" keeps the exact pre-existing
  filter/place/stretch behaviour regardless of show_empty.
- clock: "plain" registers a plain date+time label (date_lbl, built but
  never parented until now, plus a new clock_plain_lbl) instead of the
  per-digit flip box. "flip" is untouched.
- cpu/ram: two new bar chips (bar_cpu_pair/bar_ram_pair, bar_cpu_lbl/
  bar_ram_lbl) fed by the same AppInput::StatsUpdate data as the control
  panel's cpu_pair/mem_pair. Separate instances rather than reparenting
  the panel's own pair — a GTK widget can only have one parent, and
  reparenting would pull them out of the control panel's sys-grid, which
  no theme asked to change.

theme.rs::load_css() now branches on tokens.bar_border() (full-border
island vs. flush bar's bottom-only hairline) and modules.workspaces.style
(dimmed/translucent trail-fill buttons vs. solid-accent-fill pills), and
unconditionally gains .clock-plain/.clock-plain-time rules. The trail/flip
branches reproduce today's CSS byte-for-byte.

Verification (bread-capture, isolated headless-Sway harness):
- Noise floor: same baseline binary against itself varies from AE=0 (same
  capture minute) up to ~1900px (an in-flight digit-flip/clock-tick or a
  live Wi-Fi scan straddling two captures) and ~39000px on control-panel
  (live hardware sensors) — all pre-existing, not introduced by this change.
- Regression: this binary vs. the pre-Phase-5 baseline, captured in the
  same clock-minute to remove the dominant noise source, is AE=0 (bit-for-
  bit identical) on the bar view under the default liquid-motion theme.
- glass-workbench (BREAD_SHELL_THEME=glass-workbench): bar view is
  1920x36 (flush, no margin) vs. liquid-motion's 1920x56 (12px margin +
  44px island), square corners vs. liquid-motion's rounded island, plain
  "Mon 24/08 23:24" clock, and cpu/ram/wifi/battery/control chips with no
  media widget — matches demo 02. The isolated Sway capture harness has no
  live Hyprland IPC, so it reports zero workspaces under both themes
  (pre-existing, unrelated to this change) — the pill fill/dim CSS itself
  is exercised by code review and the trail-untouched regression result,
  not by a captured pixel with visible buttons.

cargo test: 18/18 passing (breadbar), no new clippy/cargo-check warnings
beyond the two pre-existing ones this task named up front.
This commit is contained in:
Breadway 2026-08-24 23:26:34 +08:00
parent 8d80a05d9b
commit df6b568cc1
3 changed files with 213 additions and 28 deletions

View file

@ -18,6 +18,19 @@ pub fn current() -> String {
format!("{} {}", date(), time())
}
/// `modules.clock.format` rendered against GLib's own `DateTime::format`
/// (a strftime subset — `%H`, `%M`, `%a`, `%d`, `%m`, ... all work). Falls
/// back to [`time`]'s hardcoded "HH:MM" on a malformed format string rather
/// than propagating an error — a broken theme's clock format must degrade,
/// not crash the bar, same as every other "malformed theme" fallback in
/// this system.
pub fn formatted(format: &str) -> String {
now()
.format(format)
.map(|s| s.to_string())
.unwrap_or_else(|_| time())
}
pub fn spawn_ticker(sender: ComponentSender<App>) {
relm4::spawn(async move {
loop {