bar: implement spotlight's embedded launcher capsule (Phase 6b)

- New bread-launcher dep (gtk feature), pinned tag v0.7.4 with a dev-only
  [patch] entry alongside bread-theme's.
- Wire the drawer slot (plan §2/§7): root becomes a vbox whose first row is
  the existing CenterBox and whose second is drawer_box, appended via the
  same ModuleRegistry::for_each_in_slot pattern left/centre/right already
  use. Empty for liquid-motion/glass-workbench (zero children, zero size);
  theme.rs's window.breadbar > centerbox selector becomes > box > centerbox
  to follow the new nesting, byte-identical CSS for both existing themes.
- modules.workspaces.style = dots: bar::workspaces::make_dot_button, width
  from modules.workspaces.dot_widths, wired as a third rebuild_buttons arm.
- New launcher_entry (plain GtkEntry) and launcher_results
  (bread_launcher::gtk::ResultsList, sharing breadbox's cache/history via
  bread_launcher::LAUNCHER_APP) modules, built unconditionally and placed
  only when a theme's [bar.slots] names them (spotlight today).
- Capsule expand/collapse: typing/focus opens the drawer and animates its
  height via bread_theme::anim::spring_to; Up/Down move the selection,
  Enter launches (do_launch + record_launch) and collapses, Escape collapses
  and releases keyboard focus (on_demand ties to GTK's own focus-widget
  state, so releasing GTK focus hands the compositor keyboard back).
- window.width now wires Width::Px into set_default_width (previously
  unconsumed — Fill-anchored themes never needed it, the capsule does).
- Two new --screenshot views: capsule-collapsed, capsule-expanded (the
  latter focuses launcher_entry, types a query, and captures bar height +
  the drawer's actual settled height rather than a guessed constant).

cargo check/clippy: no new warnings (same two pre-existing: dead_code on
system_stats_box/cpu_pair/mem_pair/pwr_pair, from_* on workspaces.rs).
cargo test: 18/18 unchanged.
This commit is contained in:
Breadway 2026-08-25 00:29:38 +08:00
parent df6b568cc1
commit 1fb10a10cd
6 changed files with 512 additions and 10 deletions

View file

@ -155,6 +155,45 @@ pub fn make_button(
btn
}
/// `style = "dots"` (theme 04/spotlight): a label-less pill whose WIDTH
/// encodes `windows` (0/1/2/3-or-more open) via `dot_widths` — see
/// `bread_theme::shell::WorkspacesModule::dot_widths`. Distinct from
/// [`make_button`] (Trail/Pill) rather than a variant of it because dots
/// carry no text at all (`04-spotlight.html`'s `.dots button` has no label);
/// reusing `Button::with_label("")` would still measure/lay out an empty
/// label box that a genuinely childless button doesn't. Width is a hard
/// `set_size_request` snap, not animated — GTK CSS min-width transitions
/// don't participate in a directly-set size request the way an opacity/
/// background-color transition does, and the plan only calls out the
/// capsule's own expand/collapse as worth the `anim::spring_to` treatment.
pub fn make_dot_button(
id: WorkspaceId,
active: WorkspaceId,
windows: i32,
dot_widths: bread_theme::shell::DotWidths,
) -> gtk4::Button {
let btn = gtk4::Button::new();
btn.add_css_class("workspace-dot");
if windows > 0 {
btn.add_css_class("occupied");
}
if id == active {
btn.add_css_class("active");
}
btn.set_valign(gtk4::Align::Center);
btn.set_halign(gtk4::Align::Center);
btn.set_vexpand(false);
btn.set_hexpand(false);
let idx = (windows.max(0) as usize).min(3);
btn.set_size_request(dot_widths[idx], 6);
btn.connect_clicked(move |_| {
relm4::spawn(async move {
switch_workspace(id).await;
});
});
btn
}
#[derive(Clone, Copy)]
struct Geom {
x: f64,