use std::panic::Location;
use super::text::h3;
use crate::metrics::MetricsRole;
use crate::style::StyleProfile;
use crate::tokens;
use crate::tree::*;
#[track_caller]
pub fn overlay<I, E>(children: I) -> El
where
I: IntoIterator<Item = E>,
E: Into<El>,
{
El::new(Kind::Overlay)
.at_loc(Location::caller())
.children(children)
.fill_size()
.align(Align::Center)
.justify(Justify::Center)
.axis(Axis::Overlay)
.clip()
}
#[track_caller]
pub fn overlays<I>(main: impl Into<El>, layers: I) -> El
where
I: IntoIterator<Item = Option<El>>,
{
let mut main = main.into();
if matches!(main.width, Size::Hug) {
main.width = Size::Fill(1.0);
}
if matches!(main.height, Size::Hug) {
main.height = Size::Fill(1.0);
}
let mut children: Vec<El> = Vec::new();
children.push(main);
children.extend(layers.into_iter().flatten());
crate::stack(children)
}
#[track_caller]
pub fn scrim(key: impl Into<String>) -> El {
El::new(Kind::Scrim)
.at_loc(Location::caller())
.key(key)
.fill(tokens::OVERLAY_SCRIM)
.fill_size()
}
#[track_caller]
pub fn modal<I, E>(key: impl Into<String>, title: impl Into<String>, body: I) -> El
where
I: IntoIterator<Item = E>,
E: Into<El>,
{
let key = key.into();
overlay([
scrim(format!("{key}:dismiss")),
modal_panel(title, body).block_pointer(),
])
}
#[track_caller]
pub fn modal_panel<I, E>(title: impl Into<String>, body: I) -> El
where
I: IntoIterator<Item = E>,
E: Into<El>,
{
let mut children: Vec<El> = vec![h3(title)];
children.extend(body.into_iter().map(Into::into));
El::new(Kind::Modal)
.at_loc(Location::caller())
.style_profile(StyleProfile::Surface)
.metrics_role(MetricsRole::Panel)
.surface_role(SurfaceRole::Popover)
.children(children)
.fill(tokens::POPOVER)
.stroke(tokens::BORDER)
.default_radius(tokens::RADIUS_LG)
.shadow(tokens::SHADOW_LG)
.default_padding(tokens::SPACE_4)
.default_gap(tokens::SPACE_3)
.width(Size::Fixed(420.0))
.height(Size::Hug)
.axis(Axis::Column)
.align(Align::Stretch)
.clip()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::widgets::button::button;
#[test]
fn overlays_filters_none_layers_in_order() {
let main = button("main").key("main");
let one = button("one").key("one");
let two = button("two").key("two");
let stacked = overlays(main, [None, Some(one), None, Some(two)]);
let keys: Vec<_> = stacked
.children
.iter()
.map(|c| c.key.clone().unwrap_or_default())
.collect();
assert_eq!(keys, vec!["main", "one", "two"]);
}
#[test]
fn overlays_with_no_layers_is_just_main_in_a_stack() {
let stacked = overlays(button("main").key("main"), std::iter::empty::<Option<El>>());
assert_eq!(stacked.children.len(), 1);
assert_eq!(stacked.children[0].key.as_deref(), Some("main"));
}
#[test]
fn overlays_promotes_hug_main_to_fill() {
let main = crate::column(std::iter::empty::<El>());
assert!(matches!(main.width, Size::Hug));
assert!(matches!(main.height, Size::Hug));
let stacked = overlays(main, std::iter::empty::<Option<El>>());
let promoted = &stacked.children[0];
assert!(matches!(promoted.width, Size::Fill(_)));
assert!(matches!(promoted.height, Size::Fill(_)));
}
#[test]
fn overlays_preserves_explicit_main_sizes() {
let main = crate::column(std::iter::empty::<El>())
.width(Size::Fixed(320.0))
.height(Size::Fill(1.0));
let stacked = overlays(main, std::iter::empty::<Option<El>>());
let kept = &stacked.children[0];
assert!(matches!(kept.width, Size::Fixed(v) if (v - 320.0).abs() < 0.01));
assert!(matches!(kept.height, Size::Fill(_)));
}
}