use crate::core::{Align, Axis, BoxStyle, Cx, Element, Role};
use crate::geometry::Insets;
use crate::render::ImageSource;
use crate::style::Theme;
const ICON: f64 = 18.0;
pub fn toolbar(theme: &Theme, items: Vec<Element>) -> Element {
Element::stack(Axis::Horizontal, items)
.fill(theme.palette.surface)
.border(theme.palette.border, 1.0)
.radius(theme.radius)
.padding(Insets::uniform(theme.spacing.xs))
.gap(theme.spacing.xs)
.align(Align::Start, Align::Center)
.role(Role::Toolbar)
}
fn tool_frame(theme: &Theme, children: Vec<Element>, on: bool) -> Element {
let mut el = Element::stack(Axis::Horizontal, children)
.radius(theme.radius / 2.0)
.padding(Insets::symmetric(theme.spacing.sm, theme.spacing.xs))
.gap(theme.spacing.xs)
.align(Align::Center, Align::Center)
.min_height(ICON + 2.0 * theme.spacing.xs)
.min_width(ICON + 2.0 * theme.spacing.sm);
if on {
el = el
.fill(theme.palette.selection)
.border(theme.palette.primary, 1.0);
}
el
}
pub fn tool_button<S>(
cx: &mut Cx<S>,
theme: &Theme,
text: impl Into<String>,
on_tap: impl FnMut(&mut S) + 'static,
) -> Element {
let label = Element::text(text, theme.typography.body, theme.palette.text);
tool_frame(theme, vec![label], false).on_tap(cx, on_tap)
}
pub fn tool_icon_button<S>(
cx: &mut Cx<S>,
theme: &Theme,
icon: impl Into<ImageSource>,
name: impl Into<String>,
on_tap: impl FnMut(&mut S) + 'static,
) -> Element {
let icon = Element::image(icon).width(ICON).height(ICON);
tool_frame(theme, vec![icon], false)
.a11y_label(name)
.on_tap(cx, on_tap)
}
pub fn tool_toggle<S>(
cx: &mut Cx<S>,
theme: &Theme,
text: impl Into<String>,
on: bool,
on_tap: impl FnMut(&mut S) + 'static,
) -> Element {
let label = Element::text(text, theme.typography.body, theme.palette.text);
tool_frame(theme, vec![label], on).on_tap(cx, on_tap)
}
pub fn tool_separator(theme: &Theme) -> Element {
Element::boxed(BoxStyle {
fill: Some(theme.palette.border),
radius: 0.0,
border: None,
})
.width(1.0)
.height(ICON + 2.0 * theme.spacing.xs)
.role(Role::Separator)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::App;
use crate::geometry::Size;
#[derive(Default)]
struct State {
bold: bool,
saved: u32,
}
#[test]
fn toolbar_buttons_run_and_report_their_roles() {
let svg = std::sync::Arc::new(
crate::render::Svg::parse(
br#"<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><rect width="16" height="16"/></svg>"#,
)
.unwrap(),
);
let mut app = App::new(State::default(), move |s: &State, cx: &mut Cx<State>| {
let theme = *cx.theme();
let bar = toolbar(
&theme,
vec![
tool_button(cx, &theme, "Save", |s: &mut State| s.saved += 1),
tool_separator(&theme),
tool_toggle(cx, &theme, "B", s.bold, |s: &mut State| s.bold = !s.bold),
tool_icon_button(cx, &theme, &svg, "Delete", |_: &mut State| {}),
],
);
super::super::column(vec![bar])
})
.logical_size(Size::new(400.0, 60.0));
app.render_once();
let tree = app.accessibility_tree().unwrap();
let bar = tree
.descendants()
.into_iter()
.find(|n| n.role == Role::Toolbar)
.unwrap()
.clone();
let roles: Vec<Role> = bar.children.iter().map(|c| c.role).collect();
assert_eq!(
roles,
[Role::Button, Role::Separator, Role::Button, Role::Button]
);
assert_eq!(bar.children[3].name, "Delete", "the icon button is named");
assert!(app.click_at(bar.children[0].bounds.center()));
assert!(app.click_at(bar.children[2].bounds.center()));
assert_eq!((app.state().saved, app.state().bold), (1, true));
}
}