iris_ui/
toggle_button.rs

1use crate::geom::Insets;
2use crate::gfx::draw_centered_text;
3use crate::view::{View, ViewId};
4use crate::{Action, DrawEvent, GuiEvent, LayoutEvent, util};
5use alloc::boxed::Box;
6
7pub fn make_toggle_button(name: &ViewId, title: &str) -> View {
8    View {
9        name: *name,
10        title: title.into(),
11        state: Some(Box::new(SelectedState::new())),
12        draw: Some(draw_toggle_button),
13        layout: Some(layout_toggle_button),
14        input: Some(input_toggle_button),
15        ..Default::default()
16    }
17}
18
19pub struct SelectedState {
20    pub selected: bool,
21}
22
23impl SelectedState {
24    pub fn new() -> SelectedState {
25        SelectedState { selected: false }
26    }
27}
28
29fn draw_toggle_button(e: &mut DrawEvent) {
30    let (bg, fg) = if let Some(state) = e.view.get_state::<SelectedState>() {
31        if state.selected {
32            (&e.theme.selected_bg, &e.theme.selected_fg)
33        } else {
34            (&e.theme.bg, &e.theme.fg)
35        }
36    } else {
37        (&e.theme.bg, &e.theme.fg)
38    };
39
40    e.ctx.fill_rect(&e.view.bounds, bg);
41    e.ctx.stroke_rect(&e.view.bounds, &e.theme.fg);
42    if let Some(focused) = e.focused {
43        let focus_insets = Insets::new_same(2);
44        if focused == &e.view.name {
45            e.ctx.stroke_rect(&((*&e.view.bounds) - focus_insets), fg);
46        }
47    }
48
49    draw_centered_text(e.ctx, &e.view.title, &e.view.bounds, &e.theme.font, fg);
50}
51
52fn input_toggle_button(event: &mut GuiEvent) -> Option<Action> {
53    if let Some(state) = event.scene.get_view_state::<SelectedState>(event.target) {
54        state.selected = !state.selected;
55        event.scene.set_focused(event.target);
56        event.scene.mark_dirty_view(event.target);
57        return Some(Action::Generic);
58    }
59    None
60}
61
62fn layout_toggle_button(event: &mut LayoutEvent) {
63    if let Some(view) = event.scene.get_view_mut(event.target) {
64        view.bounds = util::calc_bounds(view.bounds, event.theme.font, &view.title);
65    }
66}
67
68mod tests {
69    use crate::geom::{Bounds, Point};
70    use crate::scene::{Scene, click_at, draw_scene, layout_scene};
71    use crate::test::MockDrawingContext;
72    use crate::toggle_button::{SelectedState, make_toggle_button};
73    use crate::view::ViewId;
74    use alloc::vec;
75
76    #[test]
77    fn test_toggle_button() {
78        let theme = MockDrawingContext::make_mock_theme();
79        let mut scene = Scene::new_with_bounds(Bounds::new(0, 0, 320, 240));
80        {
81            let button = make_toggle_button(&ViewId::new("toggle"), "Toggle");
82            scene.add_view_to_root(button);
83        }
84        layout_scene(&mut scene, &theme);
85
86        {
87            let mut button = scene.get_view_mut(&ViewId::new("toggle")).unwrap();
88            assert_eq!(button.name, ViewId::new("toggle"));
89            let ch_size = &theme.font.character_size;
90            assert_eq!(
91                button.bounds,
92                Bounds::new(
93                    0,
94                    0,
95                    (("toggle".len() as u32) * ch_size.width + (ch_size.width) * 2) as i32,
96                    (ch_size.height + (ch_size.height / 2) * 2) as i32
97                )
98            );
99            let state = &mut button.get_state::<SelectedState>().unwrap();
100            assert_eq!(state.selected, false);
101        }
102
103        click_at(&mut scene, &vec![], Point::new(10, 10));
104
105        {
106            let state = scene
107                .get_view_state::<SelectedState>(&ViewId::new("toggle"))
108                .unwrap();
109            assert_eq!(state.selected, true);
110        }
111
112        let mut ctx: MockDrawingContext = MockDrawingContext::new(&scene);
113
114        assert_eq!(scene.dirty, true);
115        draw_scene(&mut scene, &mut ctx, &theme);
116        assert_eq!(scene.dirty, false);
117    }
118}