shell: widen spotlight's launcher schema for phase 6c (modes/sections/search geometry)

[launcher].modes now lists calc/cmd/url alongside apps, sections is
enabled, and two new keys (search_width/search_radius) let an embedded
launcher theme declare its search-state capsule geometry, defaulting to
the idle value when a theme omits them. Also gives spotlight's [bar.slots]
a widget:left_of_stats entry so a Lua widget requesting that placement
(e.g. git-branch-widget.lua) has a home instead of being silently dropped.
This commit is contained in:
Breadway 2026-08-25 11:46:28 +08:00
parent 95e676e909
commit d2c68f18b0
4 changed files with 109 additions and 15 deletions

View file

@ -185,6 +185,8 @@ pub(super) struct RawLauncher {
pub(super) footer: Option<String>,
pub(super) sections: Option<bool>,
pub(super) modes: Option<Vec<String>>,
pub(super) search_width: Option<i64>,
pub(super) search_radius: Option<i64>,
}
#[derive(Debug, serde::Deserialize)]
@ -374,13 +376,15 @@ fn resolve_launcher(theme_id: &str, l: Option<&RawLauncher>) -> anyhow::Result<L
bail!("theme '{theme_id}': launcher.mode = \"{other}\" is not overlay|embedded")
}
};
let width = l.and_then(|l| l.width).unwrap_or(540) as i32;
let radius = l.and_then(|l| l.radius).unwrap_or(20) as i32;
Ok(Launcher {
mode,
width: l.and_then(|l| l.width).unwrap_or(540) as i32,
width,
top: l
.and_then(|l| l.top.clone())
.unwrap_or_else(|| "16%".to_string()),
radius: l.and_then(|l| l.radius).unwrap_or(20) as i32,
radius,
icon_px: l.and_then(|l| l.icon_px).unwrap_or(36) as i32,
row_anim: l
.and_then(|l| l.row_anim.clone())
@ -395,6 +399,11 @@ fn resolve_launcher(theme_id: &str, l: Option<&RawLauncher>) -> anyhow::Result<L
modes: l
.and_then(|l| l.modes.clone())
.unwrap_or_else(|| vec!["apps".to_string()]),
// Default to the idle value — a theme that never sets these gets
// "no change while searching", not a hardcoded widen/shrink it
// never asked for (see the field docs on `Launcher`).
search_width: l.and_then(|l| l.search_width).map(|v| v as i32).unwrap_or(width),
search_radius: l.and_then(|l| l.search_radius).map(|v| v as i32).unwrap_or(radius),
})
}

View file

@ -783,6 +783,56 @@ mod tests {
);
}
#[test]
fn spotlight_launcher_widget_slot_gives_left_of_stats_widgets_a_home() {
let theme = load_named(builtin::SPOTLIGHT_ID).expect("spotlight should resolve");
assert!(
theme
.slots()
.right
.iter()
.any(|s| s == "widget:left_of_stats"),
"a Lua widget requesting WidgetPlacement::LeftOfStats (e.g. \
git-branch-widget.lua) must have a home under spotlight: {:?}",
theme.slots().right
);
}
#[test]
fn spotlight_query_modes_and_sections_are_enabled_phase_6c() {
let theme = load_named(builtin::SPOTLIGHT_ID).expect("spotlight should resolve");
let l = theme.launcher();
assert!(l.sections, "phase 6c: recent/apps headers are implemented");
for mode in ["apps", "calc", "cmd", "url"] {
assert!(
l.modes.iter().any(|m| m == mode),
"spotlight.modes should list \"{mode}\": {:?}",
l.modes
);
}
}
#[test]
fn spotlight_launcher_search_state_geometry_differs_from_idle() {
let theme = load_named(builtin::SPOTLIGHT_ID).expect("spotlight should resolve");
let l = theme.launcher();
assert_eq!(l.width, 480);
assert_eq!(l.search_width, 520);
assert_eq!(l.radius, 22);
assert_eq!(l.search_radius, 20);
}
#[test]
fn launcher_search_geometry_defaults_to_idle_values_when_unset() {
// liquid-motion's own [launcher] table never sets search_width/
// search_radius — a theme that omits them must fall back to "no
// change while searching", not some hardcoded magic number.
let theme = resolve_builtin();
let l = theme.launcher();
assert_eq!(l.search_width, l.width);
assert_eq!(l.search_radius, l.radius);
}
#[test]
fn spotlight_workspaces_are_dots_with_the_demos_own_widths() {
let theme = load_named(builtin::SPOTLIGHT_ID).expect("spotlight should resolve");

View file

@ -178,6 +178,19 @@ pub struct Launcher {
pub footer: String,
pub sections: bool,
pub modes: Vec<String>,
/// `LauncherMode::Embedded` only (theme 04/spotlight, plan §7 phase 6c):
/// the capsule's own width while a search is in progress
/// (`04-spotlight.html`: `.searching .capsule { width: 520px }` vs the
/// idle 480px). Defaults to `width` (no widen) for a theme that omits
/// it, so an `Overlay`-mode theme — which never reads this field at all
/// — and an `Embedded` theme that just doesn't want the widen both fall
/// back to "no change" rather than a hardcoded magic number.
pub search_width: i32,
/// `LauncherMode::Embedded` only: `border-radius` while searching
/// (`04-spotlight.html`: `.searching .capsule { border-radius: 20px }`
/// vs the collapsed 22px `radius`). Same default-to-`radius` fallback
/// reasoning as `search_width`.
pub search_radius: i32,
}
/// A satellite surface, keyed by layer-shell namespace in `[surfaces.*]` —