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:
parent
8d80a05d9b
commit
df6b568cc1
3 changed files with 213 additions and 28 deletions
|
|
@ -18,6 +18,19 @@ pub fn current() -> String {
|
||||||
format!("{} {}", date(), time())
|
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>) {
|
pub fn spawn_ticker(sender: ComponentSender<App>) {
|
||||||
relm4::spawn(async move {
|
relm4::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
|
|
|
||||||
143
src/main.rs
143
src/main.rs
|
|
@ -13,7 +13,7 @@ mod surface;
|
||||||
mod theme;
|
mod theme;
|
||||||
mod widgets;
|
mod widgets;
|
||||||
|
|
||||||
use bread_theme::shell::{Exclusive, Keyboard};
|
use bread_theme::shell::{ClockStyle, Exclusive, Keyboard, WorkspaceStyle};
|
||||||
use gtk4::prelude::*;
|
use gtk4::prelude::*;
|
||||||
use gtk4_layer_shell::{Edge, KeyboardMode, Layer, LayerShell};
|
use gtk4_layer_shell::{Edge, KeyboardMode, Layer, LayerShell};
|
||||||
use hyprland::data::Workspace;
|
use hyprland::data::Workspace;
|
||||||
|
|
@ -45,6 +45,12 @@ pub struct App {
|
||||||
time_str: String,
|
time_str: String,
|
||||||
clock_digits: Vec<gtk4::Label>,
|
clock_digits: Vec<gtk4::Label>,
|
||||||
date_lbl: gtk4::Label,
|
date_lbl: gtk4::Label,
|
||||||
|
// `modules.clock.style = "plain"` (glass-workbench, Phase 5): a plain
|
||||||
|
// "HH:MM" label with no per-digit flip. Built alongside `clock_digits`
|
||||||
|
// regardless of the active theme's style so switching styles needs no
|
||||||
|
// recompile; only one of the two ever lands in a `[bar.slots]` module
|
||||||
|
// registration (see the "Assemble" section).
|
||||||
|
clock_plain_lbl: gtk4::Label,
|
||||||
|
|
||||||
// ── Stats bar ─────────────────────────────────────────────────────────
|
// ── Stats bar ─────────────────────────────────────────────────────────
|
||||||
// Island chrome matches the Liquid Motion demo: volume / wifi / battery
|
// Island chrome matches the Liquid Motion demo: volume / wifi / battery
|
||||||
|
|
@ -58,6 +64,14 @@ pub struct App {
|
||||||
cpu_lbl: gtk4::Label,
|
cpu_lbl: gtk4::Label,
|
||||||
mem_lbl: gtk4::Label,
|
mem_lbl: gtk4::Label,
|
||||||
pwr_lbl: gtk4::Label,
|
pwr_lbl: gtk4::Label,
|
||||||
|
// `[bar.slots].right = [..., "cpu", "ram", ...]` (glass-workbench, Phase
|
||||||
|
// 5): separate instances from `cpu_pair`/`mem_pair` above, which stay
|
||||||
|
// parented in the control panel's sys-grid — a GTK widget can only have
|
||||||
|
// one parent, so reusing those here would mean reparenting them out of
|
||||||
|
// the panel, changing panel behaviour no theme asked to change. Fed by
|
||||||
|
// the same `AppInput::StatsUpdate` data.
|
||||||
|
bar_cpu_lbl: gtk4::Label,
|
||||||
|
bar_ram_lbl: gtk4::Label,
|
||||||
gpu_lbl: gtk4::Label,
|
gpu_lbl: gtk4::Label,
|
||||||
vol_lbl: gtk4::Label,
|
vol_lbl: gtk4::Label,
|
||||||
bat_lbl: gtk4::Label,
|
bat_lbl: gtk4::Label,
|
||||||
|
|
@ -388,6 +402,22 @@ impl SimpleComponent for App {
|
||||||
date_lbl.add_css_class("date-label");
|
date_lbl.add_css_class("date-label");
|
||||||
date_lbl.set_visible(false);
|
date_lbl.set_visible(false);
|
||||||
|
|
||||||
|
// `modules.clock.style = "plain"` (glass-workbench): date_lbl above
|
||||||
|
// plus one plain "HH:MM" label, no per-digit flip markup at all.
|
||||||
|
// Built unconditionally alongside the flip clock so a theme's style
|
||||||
|
// choice is just which of the two gets registered into the "clock"
|
||||||
|
// slot below — see "Assemble".
|
||||||
|
let clock_plain_lbl = gtk4::Label::new(Some(&bar::clock::time()));
|
||||||
|
clock_plain_lbl.add_css_class("clock-plain-time");
|
||||||
|
clock_plain_lbl.set_valign(gtk4::Align::Center);
|
||||||
|
clock_plain_lbl.set_vexpand(false);
|
||||||
|
let clock_plain_box = gtk4::Box::new(gtk4::Orientation::Horizontal, 8);
|
||||||
|
clock_plain_box.add_css_class("clock-plain");
|
||||||
|
clock_plain_box.set_valign(gtk4::Align::Center);
|
||||||
|
clock_plain_box.set_vexpand(false);
|
||||||
|
clock_plain_box.append(&date_lbl);
|
||||||
|
clock_plain_box.append(&clock_plain_lbl);
|
||||||
|
|
||||||
// Center area: [media_widget · widgets · clock · widgets]
|
// Center area: [media_widget · widgets · clock · widgets]
|
||||||
let center_area = gtk4::Box::new(gtk4::Orientation::Horizontal, 12);
|
let center_area = gtk4::Box::new(gtk4::Orientation::Horizontal, 12);
|
||||||
center_area.add_css_class("center-area");
|
center_area.add_css_class("center-area");
|
||||||
|
|
@ -420,6 +450,18 @@ impl SimpleComponent for App {
|
||||||
pair.set_hexpand(true);
|
pair.set_hexpand(true);
|
||||||
}
|
}
|
||||||
gpu_pair.set_visible(false);
|
gpu_pair.set_visible(false);
|
||||||
|
|
||||||
|
// `[bar.slots].right = [..., "cpu", "ram", ...]` (glass-workbench,
|
||||||
|
// Phase 5): separate chip instances from `cpu_pair`/`mem_pair`
|
||||||
|
// above, which stay parented in the control panel's sys-grid below
|
||||||
|
// — reusing them here would mean reparenting them out of the panel.
|
||||||
|
// Same icons, same `.stat-pair` chip styling every other bar chip
|
||||||
|
// (volume/battery) already uses; fed by the same `StatsUpdate` data.
|
||||||
|
let bar_cpu_lbl = stat_label();
|
||||||
|
let bar_ram_lbl = stat_label();
|
||||||
|
let bar_cpu_pair = stat_pair(asset!("CPU.svg"), &bar_cpu_lbl);
|
||||||
|
let bar_ram_pair = stat_pair(asset!("RAM Usage.svg"), &bar_ram_lbl);
|
||||||
|
|
||||||
let system_stats_box = gtk4::Box::new(gtk4::Orientation::Vertical, 4);
|
let system_stats_box = gtk4::Box::new(gtk4::Orientation::Vertical, 4);
|
||||||
system_stats_box.add_css_class("sys-grid");
|
system_stats_box.add_css_class("sys-grid");
|
||||||
let sys_row1 = gtk4::Box::new(gtk4::Orientation::Horizontal, 8);
|
let sys_row1 = gtk4::Box::new(gtk4::Orientation::Horizontal, 8);
|
||||||
|
|
@ -721,11 +763,32 @@ impl SimpleComponent for App {
|
||||||
let mut bar_modules = bar::slots::ModuleRegistry::new();
|
let mut bar_modules = bar::slots::ModuleRegistry::new();
|
||||||
bar_modules.register("workspaces", &workspace_trail.overlay);
|
bar_modules.register("workspaces", &workspace_trail.overlay);
|
||||||
bar_modules.register("media", &media_widget);
|
bar_modules.register("media", &media_widget);
|
||||||
bar_modules.register("clock", &clock_box);
|
// `modules.clock.style`: "flip" (default, liquid-motion) registers
|
||||||
|
// the existing per-digit clock_box unchanged; "plain" (glass-
|
||||||
|
// workbench) registers clock_plain_box instead and reveals date_lbl
|
||||||
|
// per `show_date` — clock_box/clock_digits are still fully built in
|
||||||
|
// that case, just never placed in any slot. "none" (Phase 6+,
|
||||||
|
// unused today) registers neither.
|
||||||
|
match bar_shell_theme.modules().clock.style {
|
||||||
|
ClockStyle::Plain => {
|
||||||
|
date_lbl.set_visible(bar_shell_theme.modules().clock.show_date);
|
||||||
|
bar_modules.register("clock", &clock_plain_box);
|
||||||
|
}
|
||||||
|
ClockStyle::Flip => bar_modules.register("clock", &clock_box),
|
||||||
|
ClockStyle::None => {}
|
||||||
|
}
|
||||||
bar_modules.register("volume", &vol_box);
|
bar_modules.register("volume", &vol_box);
|
||||||
bar_modules.register("wifi", &connectivity_pair);
|
bar_modules.register("wifi", &connectivity_pair);
|
||||||
bar_modules.register("battery", &bat_box);
|
bar_modules.register("battery", &bat_box);
|
||||||
bar_modules.register("control", &hamburger_btn);
|
bar_modules.register("control", &hamburger_btn);
|
||||||
|
// `[bar.slots].right = [..., "cpu", "ram", ...]` (glass-workbench):
|
||||||
|
// registered unconditionally, same as every other module — a theme
|
||||||
|
// that never names "cpu"/"ram" in a slot (liquid-motion) just never
|
||||||
|
// walks these entries in `for_each_in_slot` below, so they stay
|
||||||
|
// built but unparented, exactly like `media_widget` does for
|
||||||
|
// glass-workbench (which omits "media" entirely).
|
||||||
|
bar_modules.register("cpu", &bar_cpu_pair);
|
||||||
|
bar_modules.register("ram", &bar_ram_pair);
|
||||||
|
|
||||||
// `tray` never appears in a bar slot — it stays inside the
|
// `tray` never appears in a bar slot — it stays inside the
|
||||||
// control-panel popover (built above, next to the SNI tray) — but
|
// control-panel popover (built above, next to the SNI tray) — but
|
||||||
|
|
@ -791,6 +854,7 @@ impl SimpleComponent for App {
|
||||||
time_str: bar::clock::current(),
|
time_str: bar::clock::current(),
|
||||||
clock_digits,
|
clock_digits,
|
||||||
date_lbl,
|
date_lbl,
|
||||||
|
clock_plain_lbl,
|
||||||
system_stats_box,
|
system_stats_box,
|
||||||
system_sep,
|
system_sep,
|
||||||
cpu_pair,
|
cpu_pair,
|
||||||
|
|
@ -800,6 +864,8 @@ impl SimpleComponent for App {
|
||||||
cpu_lbl,
|
cpu_lbl,
|
||||||
mem_lbl,
|
mem_lbl,
|
||||||
pwr_lbl,
|
pwr_lbl,
|
||||||
|
bar_cpu_lbl,
|
||||||
|
bar_ram_lbl,
|
||||||
gpu_lbl,
|
gpu_lbl,
|
||||||
vol_lbl,
|
vol_lbl,
|
||||||
bat_lbl,
|
bat_lbl,
|
||||||
|
|
@ -938,7 +1004,14 @@ impl SimpleComponent for App {
|
||||||
self.active_ws = new_active;
|
self.active_ws = new_active;
|
||||||
if let Some(btn) = self.button_map.get(&self.active_ws).cloned() {
|
if let Some(btn) = self.button_map.get(&self.active_ws).cloned() {
|
||||||
btn.add_css_class("active");
|
btn.add_css_class("active");
|
||||||
self.workspace_trail.stretch(from.as_ref(), &btn);
|
// Trail style only: pill/dots never call place()/
|
||||||
|
// stretch() at all — the "active" CSS class above is
|
||||||
|
// the whole of their active-workspace treatment
|
||||||
|
// (solid accent fill, no trail overlay).
|
||||||
|
if theme::shell_theme().modules().workspaces.style == WorkspaceStyle::Trail
|
||||||
|
{
|
||||||
|
self.workspace_trail.stretch(from.as_ref(), &btn);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -958,8 +1031,25 @@ impl SimpleComponent for App {
|
||||||
}
|
}
|
||||||
AppInput::ClockTick => {
|
AppInput::ClockTick => {
|
||||||
self.time_str = bar::clock::current();
|
self.time_str = bar::clock::current();
|
||||||
flip_clock_digits(&self.clock_digits, &bar::clock::time());
|
|
||||||
self.date_lbl.set_label(&bar::clock::date());
|
self.date_lbl.set_label(&bar::clock::date());
|
||||||
|
let clock_module = theme::shell_theme().modules().clock.clone();
|
||||||
|
match clock_module.style {
|
||||||
|
// Plain (glass-workbench): one label, no flip animation
|
||||||
|
// — `flip_clock_digits` would just be wasted work (and
|
||||||
|
// a pointless 450ms `play_once` timer) on digits that
|
||||||
|
// are never on screen.
|
||||||
|
ClockStyle::Plain => {
|
||||||
|
self.clock_plain_lbl
|
||||||
|
.set_label(&bar::clock::formatted(&clock_module.format));
|
||||||
|
}
|
||||||
|
// Flip (default, liquid-motion) and None both keep
|
||||||
|
// exactly today's per-digit-flip update — None has no
|
||||||
|
// module in a slot to display it, but there's no reason
|
||||||
|
// to special-case skipping the (cheap, idempotent) work.
|
||||||
|
ClockStyle::Flip | ClockStyle::None => {
|
||||||
|
flip_clock_digits(&self.clock_digits, &bar::clock::time());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AppInput::StatsUpdate(stats) => {
|
AppInput::StatsUpdate(stats) => {
|
||||||
let cpu = match stats.cpu_temp {
|
let cpu = match stats.cpu_temp {
|
||||||
|
|
@ -969,6 +1059,11 @@ impl SimpleComponent for App {
|
||||||
self.cpu_lbl.set_label(&cpu);
|
self.cpu_lbl.set_label(&cpu);
|
||||||
self.mem_lbl.set_label(&stats.mem);
|
self.mem_lbl.set_label(&stats.mem);
|
||||||
self.pwr_lbl.set_label(&stats.power);
|
self.pwr_lbl.set_label(&stats.power);
|
||||||
|
// `[bar.slots].right = [..., "cpu", "ram", ...]` (glass-
|
||||||
|
// workbench): same formatted text, separate chip instances
|
||||||
|
// (see the App struct field docs for why).
|
||||||
|
self.bar_cpu_lbl.set_label(&cpu);
|
||||||
|
self.bar_ram_lbl.set_label(&stats.mem);
|
||||||
match stats.gpu_usage {
|
match stats.gpu_usage {
|
||||||
Some(g) => {
|
Some(g) => {
|
||||||
let gpu = match stats.gpu_temp {
|
let gpu = match stats.gpu_temp {
|
||||||
|
|
@ -1306,14 +1401,33 @@ impl App {
|
||||||
self.workspace_box.remove(&child);
|
self.workspace_box.remove(&child);
|
||||||
}
|
}
|
||||||
self.button_map.clear();
|
self.button_map.clear();
|
||||||
|
let modules = theme::shell_theme().modules().clone();
|
||||||
|
let ws_style = modules.workspaces.style;
|
||||||
|
let show_empty = modules.workspaces.show_empty;
|
||||||
for ws in &self.workspaces {
|
for ws in &self.workspaces {
|
||||||
if ws.monitor != self.monitor {
|
if ws.monitor != self.monitor {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Persistent empty Hyprland workspaces stay off the bar unless
|
let empty = ws.windows == 0 && ws.id != self.active_ws;
|
||||||
// this output is actually looking at them.
|
if empty {
|
||||||
if ws.windows == 0 && ws.id != self.active_ws {
|
match ws_style {
|
||||||
continue;
|
// Trail (default, liquid-motion): unconditionally off
|
||||||
|
// the bar, exactly as before this change — regardless
|
||||||
|
// of `show_empty`, which liquid-motion's own manifest
|
||||||
|
// declares `true` but this style has never consumed.
|
||||||
|
// Changing that now would be a real, undesired
|
||||||
|
// liquid-motion regression, not a Phase 5 fix.
|
||||||
|
WorkspaceStyle::Trail => continue,
|
||||||
|
// Pill/Dots: honour `show_empty` for real — demo 02's
|
||||||
|
// pills render an unoccupied, non-active workspace at
|
||||||
|
// reduced opacity via the `.workspace-btn:not(.occupied)
|
||||||
|
// :not(.active)` CSS rule rather than hiding it.
|
||||||
|
_ => {
|
||||||
|
if !show_empty {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let btn = bar::workspaces::make_button(ws.id, &ws.name, self.active_ws, ws.windows > 0);
|
let btn = bar::workspaces::make_button(ws.id, &ws.name, self.active_ws, ws.windows > 0);
|
||||||
if !prev.contains(&ws.id) {
|
if !prev.contains(&ws.id) {
|
||||||
|
|
@ -1322,10 +1436,15 @@ impl App {
|
||||||
self.workspace_box.append(&btn);
|
self.workspace_box.append(&btn);
|
||||||
self.button_map.insert(ws.id, btn);
|
self.button_map.insert(ws.id, btn);
|
||||||
}
|
}
|
||||||
match self.button_map.get(&self.active_ws).cloned() {
|
// Trail style only: pill/dots never call place()/stretch()/clear()
|
||||||
Some(btn) if animate => self.workspace_trail.stretch(None, &btn),
|
// at all — the "active" CSS class `make_button` already applies is
|
||||||
Some(btn) => self.workspace_trail.place(&btn),
|
// the whole of their active-workspace treatment.
|
||||||
None => self.workspace_trail.clear(),
|
if ws_style == WorkspaceStyle::Trail {
|
||||||
|
match self.button_map.get(&self.active_ws).cloned() {
|
||||||
|
Some(btn) if animate => self.workspace_trail.stretch(None, &btn),
|
||||||
|
Some(btn) => self.workspace_trail.place(&btn),
|
||||||
|
None => self.workspace_trail.clear(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
85
src/theme.rs
85
src/theme.rs
|
|
@ -69,6 +69,66 @@ fn load_css() -> String {
|
||||||
let spring = tokens.spring();
|
let spring = tokens.spring();
|
||||||
let spring_settle = tokens.spring_settle();
|
let spring_settle = tokens.spring_settle();
|
||||||
|
|
||||||
|
// `tokens.bar_border()` (plan §11 Phase 5): "full" (default, liquid-
|
||||||
|
// motion's floating island) draws a border on all four edges; "bottom"
|
||||||
|
// (glass-workbench's flush edge-to-edge bar) draws only the hairline
|
||||||
|
// the demo's `.bar { border-bottom: 1px solid #ffffff12 }` calls for —
|
||||||
|
// a full border on a bar flush against the screen's top/left/right
|
||||||
|
// edges would otherwise show as a stray line along those edges an
|
||||||
|
// island never has to worry about. Reused below for the centerbox's
|
||||||
|
// horizontal padding too: the flush bar's demo padding (`0 12px`,
|
||||||
|
// symmetric) differs from the island's own asymmetric `0 8px 0 6px`.
|
||||||
|
let flush = tokens.bar_border() == "bottom";
|
||||||
|
let window_border = if flush {
|
||||||
|
"border: none; border-bottom: 1px solid alpha(@on-bg, 0.07);".to_string()
|
||||||
|
} else {
|
||||||
|
"border: 1px solid alpha(@on-bg, 0.08);".to_string()
|
||||||
|
};
|
||||||
|
let centerbox_padding = if flush { "0 12px" } else { "0 8px 0 6px" };
|
||||||
|
|
||||||
|
// `modules.workspaces.style` (plan §11 Phase 5): "trail" (default,
|
||||||
|
// liquid-motion) is exactly today's CSS, unchanged byte-for-byte —
|
||||||
|
// dimmed/translucent buttons with the gradient trail overlay supplying
|
||||||
|
// the active fill. "pill"/"dots" (glass-workbench, Phase 6+) render the
|
||||||
|
// active state as a solid accent fill on the button itself instead,
|
||||||
|
// since neither style ever calls `WorkspaceTrail::place`/`stretch`
|
||||||
|
// (see `App::rebuild_buttons`) — the trail's own `.workspace-trail`
|
||||||
|
// pill CSS is therefore irrelevant for them (it's never made visible).
|
||||||
|
let workspace_css = match theme.modules().workspaces.style {
|
||||||
|
bread_theme::shell::WorkspaceStyle::Trail => format!(
|
||||||
|
".workspace-trail {{ background-image: linear-gradient(90deg, @accent, @teal);\
|
||||||
|
background-color: @accent; border-radius: 12px; }}\
|
||||||
|
.workspace-btn {{ background: transparent; opacity: 0.36; color: @on-bg;\
|
||||||
|
border-radius: 12px; border: none; outline: none; box-shadow: none;\
|
||||||
|
min-width: 28px; min-height: 28px; 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; }}",
|
||||||
|
),
|
||||||
|
_ => {
|
||||||
|
let accent = theme.tokens().accent_from();
|
||||||
|
format!(
|
||||||
|
".workspace-btn {{ background: transparent; opacity: 1; color: alpha(@on-bg, 0.4);\
|
||||||
|
border-radius: {radius_sm}; border: none; outline: none; box-shadow: none;\
|
||||||
|
min-width: 22px; min-height: 20px; margin: 0; padding: 0 6px;\
|
||||||
|
font-size: 12px; font-weight: 600;\
|
||||||
|
transition: background-color 0.22s {spring_settle},\
|
||||||
|
color 0.22s {spring_settle}, opacity 0.22s {spring_settle}; }}\
|
||||||
|
.workspace-btn:hover {{ background: alpha(@on-bg, 0.08); }}\
|
||||||
|
.workspace-btn.occupied {{ color: alpha(@on-bg, 0.8); }}\
|
||||||
|
.workspace-btn:not(.occupied):not(.active) {{ opacity: 0.35; }}\
|
||||||
|
.workspace-btn.active {{ background: @{accent}; color: @on-accent; opacity: 1; }}\
|
||||||
|
.workspace-btn.active:hover {{ background: @{accent}; }}\
|
||||||
|
.workspace-btn.ws-in {{ animation: row-in 0.32s {spring_settle} both; }}",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
"@keyframes notif-in {{ from {{ opacity: 0; margin-right: -16px; }} }}\
|
"@keyframes notif-in {{ from {{ opacity: 0; margin-right: -16px; }} }}\
|
||||||
@keyframes osd-in {{ from {{ opacity: 0; margin-bottom: -8px; }} }}\
|
@keyframes osd-in {{ from {{ opacity: 0; margin-bottom: -8px; }} }}\
|
||||||
|
|
@ -79,22 +139,10 @@ fn load_css() -> String {
|
||||||
@keyframes digit-flip {{ from {{ opacity: 0; margin-top: 7px; }} to {{ opacity: 1; margin-top: 0; }} }}\
|
@keyframes digit-flip {{ from {{ opacity: 0; margin-top: 7px; }} to {{ opacity: 1; margin-top: 0; }} }}\
|
||||||
@keyframes caret-draw {{ from {{ margin-right: 200px; opacity: 0.2; }} to {{ margin-right: 4px; opacity: 1; }} }}\
|
@keyframes caret-draw {{ from {{ margin-right: 200px; opacity: 0.2; }} to {{ margin-right: 4px; opacity: 1; }} }}\
|
||||||
window.breadbar {{ background-color: alpha(@bg, 0.72); color: @on-bg;\
|
window.breadbar {{ background-color: alpha(@bg, 0.72); color: @on-bg;\
|
||||||
border-radius: {radius_bar}; border: 1px solid alpha(@on-bg, 0.08); }}\
|
border-radius: {radius_bar}; {window_border} }}\
|
||||||
window.breadbar > centerbox {{ padding: 0 8px 0 6px; }}\
|
window.breadbar > centerbox {{ padding: {centerbox_padding}; }}\
|
||||||
window.breadbar button {{ min-height: 0; min-width: 0; }}\
|
window.breadbar button {{ min-height: 0; min-width: 0; }}\
|
||||||
.workspace-trail {{ background-image: linear-gradient(90deg, @accent, @teal);\
|
{workspace_css}\
|
||||||
background-color: @accent; border-radius: 12px; }}\
|
|
||||||
.workspace-btn {{ background: transparent; opacity: 0.36; color: @on-bg;\
|
|
||||||
border-radius: 12px; border: none; outline: none; box-shadow: none;\
|
|
||||||
min-width: 28px; min-height: 28px; 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-box {{ padding: 0 4px; }}\
|
||||||
.clock-label {{ font-size: 24px; font-weight: bold; letter-spacing: 0.04em;\
|
.clock-label {{ font-size: 24px; font-weight: bold; letter-spacing: 0.04em;\
|
||||||
min-height: 0; padding: 0; margin-top: 3px; }}\
|
min-height: 0; padding: 0; margin-top: 3px; }}\
|
||||||
|
|
@ -102,7 +150,9 @@ fn load_css() -> String {
|
||||||
min-width: 15px; min-height: 0; padding: 0; margin: 0; }}\
|
min-width: 15px; min-height: 0; padding: 0; margin: 0; }}\
|
||||||
.clock-colon {{ min-width: 10px; opacity: 0.7; }}\
|
.clock-colon {{ min-width: 10px; opacity: 0.7; }}\
|
||||||
.clock-digit.flip {{ animation: digit-flip 0.45s {spring} both; }}\
|
.clock-digit.flip {{ animation: digit-flip 0.45s {spring} both; }}\
|
||||||
.date-label {{ font-size: 14px; opacity: 0.52; letter-spacing: 0.04em; }}\
|
.clock-plain {{ padding: 0 4px; }}\
|
||||||
|
.clock-plain-time {{ font-size: 15px; font-weight: 600; letter-spacing: 0.04em; }}\
|
||||||
|
.date-label {{ font-size: 12px; opacity: 0.48; letter-spacing: 0.04em; }}\
|
||||||
.stat-label {{ font-size: 14px; letter-spacing: 0.02em; opacity: 0.92; }}\
|
.stat-label {{ font-size: 14px; letter-spacing: 0.02em; opacity: 0.92; }}\
|
||||||
.stat-label.tick {{ animation: digit-flip 0.35s {spring} both; }}\
|
.stat-label.tick {{ animation: digit-flip 0.35s {spring} both; }}\
|
||||||
.stats-box {{ margin-right: 0; }}\
|
.stats-box {{ margin-right: 0; }}\
|
||||||
|
|
@ -321,6 +371,9 @@ fn load_css() -> String {
|
||||||
pad = pad,
|
pad = pad,
|
||||||
spring = spring,
|
spring = spring,
|
||||||
spring_settle = spring_settle,
|
spring_settle = spring_settle,
|
||||||
|
window_border = window_border,
|
||||||
|
centerbox_padding = centerbox_padding,
|
||||||
|
workspace_css = workspace_css,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue