shell: add spotlight builtin (theme 04), dots/placeholder-clock schema, and anim::spring_to
- bread-theme:🐚 WorkspacesModule.dot_widths and ClockModule.placeholder_clock,
resolved/validated in manifest.rs and modeled in types.rs, so a theme can
actually configure dot-pill widths and an entry-as-clock placeholder
instead of those staying schema-only.
- New compiled-in builtin "spotlight" (assets/shell/spotlight/), registered
in builtin.rs so list() returns three themes: centred capsule window spec
(anchors=[top] only, width=480, exclusive=none, keyboard=on_demand),
workspaces.style=dots, clock.style=none+placeholder_clock, launcher.mode=
embedded, drawer=[launcher_results]. Colours are palette token names
(pink), never hex.
- bread-theme::anim::spring_to: a reusable TickCallback size-interpolation
helper (GTK4 has no CSS width/height transition on a widget or layer-shell
surface), generalized from WorkspaceTrail's existing ease/tick pattern, for
breadbar's capsule-drawer expand/collapse.
- bread_launcher::LAUNCHER_APP: the launcher's one shared on-disk identity
(cache/config/history), so breadbar's embedded capsule and breadbox's
overlay window read and write the SAME cache and launch history rather
than forking into two rankings of a user's apps.
Tests: +6 spotlight builtin tests (loads/in list/capsule window shape/
embedded mode/dots widths/flat-pink-not-hex), bread-theme --lib 63->69,
bread-launcher --lib unchanged at 18.
This commit is contained in:
parent
6ffd689a4d
commit
8c5e42230a
9 changed files with 505 additions and 2 deletions
68
bread-theme/src/anim.rs
Normal file
68
bread-theme/src/anim.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
//! `anim::spring_to` — THEME_SYSTEM_PLAN.md §7/§8: GTK4 has no CSS
|
||||
//! width/height transition on a widget or a layer-shell surface (unlike the
|
||||
//! demos' `transition: width .45s var(--spring)` /
|
||||
//! `transition: max-height .4s var(--spring)`), so a size change that wants
|
||||
//! to animate has to interpolate a plain integer over the frame clock
|
||||
//! instead and re-apply it (`set_size_request`, `set_default_width`, ...)
|
||||
//! every frame.
|
||||
//!
|
||||
//! This is the same house technique `breadbar::bar::workspaces::WorkspaceTrail`
|
||||
//! already uses for the workspace trail's stretch/snap (a local, un-exported
|
||||
//! `ease`/`ease_overshoot` pair driven by `add_tick_callback` and
|
||||
//! `Instant::elapsed`) — lifted here, generalized to a plain `i32 -> i32`
|
||||
//! interpolation with a caller-supplied frame callback, so theme 04's
|
||||
//! capsule-drawer expand/collapse (and any future popover that wants the
|
||||
//! same effect) doesn't have to reimplement it.
|
||||
|
||||
use gtk4::glib::ControlFlow;
|
||||
use gtk4::prelude::*;
|
||||
|
||||
/// Approximates `cubic-bezier(0.22, 1.35, 0.36, 1)` — the overshoot/"spring"
|
||||
/// curve every builtin theme's `tokens.spring` names (`Tokens::spring`'s own
|
||||
/// default). Not a literal bezier solve (same approximation
|
||||
/// `bar::workspaces::ease_overshoot` uses) — exact enough that the eye can't
|
||||
/// tell it apart from the real curve at animation speeds.
|
||||
fn spring_ease(t: f64) -> f64 {
|
||||
let t = t.clamp(0.0, 1.0);
|
||||
let c = 1.35;
|
||||
let t1 = t - 1.0;
|
||||
1.0 + t1 * t1 * ((c + 1.0) * t1 + c)
|
||||
}
|
||||
|
||||
/// Interpolates from `from` to `to` over `duration_ms`, calling `on_frame`
|
||||
/// with each intermediate value (and, on the final tick, the exact `to` —
|
||||
/// never an off-by-rounding near-miss) via `widget`'s frame clock.
|
||||
///
|
||||
/// Returns the [`gtk4::TickCallbackId`] so a caller that might need to
|
||||
/// interrupt an in-flight run (e.g. the drawer re-opening before its close
|
||||
/// animation finished) can `.remove()` it early; a run left to finish on its
|
||||
/// own needs no cleanup — the callback self-terminates by returning
|
||||
/// [`ControlFlow::Break`] once `duration_ms` has elapsed, same as
|
||||
/// `WorkspaceTrail`'s own tick callbacks.
|
||||
pub fn spring_to(
|
||||
widget: &impl IsA<gtk4::Widget>,
|
||||
from: i32,
|
||||
to: i32,
|
||||
duration_ms: f64,
|
||||
on_frame: impl FnMut(i32) + 'static,
|
||||
) -> gtk4::TickCallbackId {
|
||||
let started = std::time::Instant::now();
|
||||
// `add_tick_callback` requires `Fn`, not `FnMut` — the caller's frame
|
||||
// closure almost always needs to mutate captured state (a widget's size
|
||||
// request, an `Rc<Cell<..>>` flag), so it's boxed behind a `RefCell`
|
||||
// here rather than pushing `Cell`/`RefCell` plumbing onto every call
|
||||
// site (every current and future caller wants `FnMut`, none want `Fn`).
|
||||
let on_frame = std::cell::RefCell::new(on_frame);
|
||||
widget.add_tick_callback(move |_, _| {
|
||||
let elapsed = started.elapsed().as_secs_f64() * 1000.0;
|
||||
let mut on_frame = on_frame.borrow_mut();
|
||||
if elapsed >= duration_ms {
|
||||
on_frame(to);
|
||||
return ControlFlow::Break;
|
||||
}
|
||||
let t = spring_ease(elapsed / duration_ms);
|
||||
let value = from as f64 + (to - from) as f64 * t;
|
||||
on_frame(value.round() as i32);
|
||||
ControlFlow::Continue
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue