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}