1use crate::geom::{Bounds, Point};
2use crate::gfx::{DrawingContext, draw_centered_text};
3use crate::view::Flex::{Intrinsic, Resize};
4use crate::view::{View, ViewId};
5use crate::{Action, DrawEvent, EventType, GuiEvent, LayoutEvent};
6use alloc::boxed::Box;
7use alloc::string::{String, ToString};
8use alloc::vec::Vec;
9use core::any::Any;
10use core::option::Option::Some;
11use hashbrown::Equivalent;
12
13pub fn make_toggle_group(name: &ViewId, data: Vec<&str>, selected: usize) -> View {
14 View {
15 name: *name,
16 title: name.as_str().into(),
17 bounds: Bounds::new(0, 0, (data.len() * 60) as i32, 30),
18 state: Some(SelectOneOfState::new_with(data, selected)),
19 input: Some(input_toggle_group),
20 layout: Some(layout_toggle_group),
21 draw: Some(draw_toggle_group),
22 visible: true,
23 h_flex: Resize,
24 v_flex: Intrinsic,
25 ..Default::default()
26 }
27}
28
29pub struct SelectOneOfState {
30 pub items: Vec<String>,
31 pub selected: usize,
32}
33
34impl SelectOneOfState {
35 pub fn new_with(items: Vec<&str>, selected: usize) -> Box<dyn Any> {
36 Box::new(SelectOneOfState {
37 items: items.iter().map(|s| s.to_string()).collect(),
38 selected,
39 })
40 }
41}
42
43pub fn input_toggle_group(e: &mut GuiEvent) -> Option<Action> {
44 match &e.event_type {
45 EventType::Tap(pt) => {
46 e.scene.mark_dirty_view(e.target);
47 e.scene.set_focused(e.target);
48 if let Some(view) = e.scene.get_view_mut(e.target) {
49 let bounds = view.bounds;
50 if let Some(state) = view.get_state::<SelectOneOfState>() {
51 let cell_width = bounds.size.w / (state.items.len() as i32);
52 let x = pt.x - bounds.x();
53 let n = x / cell_width;
54 if n >= 0 && n < state.items.len() as i32 {
55 state.selected = n as usize;
56 return Some(Action::Command(state.items[state.selected].clone()));
57 }
58 }
59 }
60 }
61 _ => {}
62 }
63 None
64}
65
66fn draw_toggle_group(e: &mut DrawEvent) {
67 let bounds = e.view.bounds;
68 e.ctx.fill_rect(&e.view.bounds, &e.theme.bg);
69 let name = e.view.name.clone();
70 if let Some(state) = e.view.get_state::<SelectOneOfState>() {
71 let cell_width = bounds.size.w / (state.items.len() as i32);
72 for (i, item) in state.items.iter().enumerate() {
73 let (bg, fg) = if i == state.selected {
74 (&e.theme.selected_bg, &e.theme.selected_fg)
75 } else {
76 (&e.theme.bg, &e.theme.fg)
77 };
78 let bds = Bounds::new(
79 bounds.position.x + (i as i32) * cell_width + 1,
80 bounds.y(),
81 cell_width - 1,
82 bounds.h(),
83 );
84 if i == state.selected {
86 e.ctx.fill_rect(&bds, bg);
87 if let Some(focused) = e.focused {
88 if focused == &name {
89 e.ctx.stroke_rect(&bds.contract(2), fg);
90 }
91 }
92 }
93
94 draw_centered_text(e.ctx, item, &bds, &e.theme.font, fg);
96
97 if i != 0 {
99 let x = bounds.position.x + (i as i32) * cell_width;
100 e.ctx.line(
101 &Point::new(x, bounds.y()),
102 &Point::new(x, bounds.position.y + bounds.size.h - 1),
103 &e.theme.fg,
104 );
105 }
106 }
107 }
108 e.ctx.stroke_rect(&e.view.bounds, &e.theme.fg);
109}
110
111pub fn layout_toggle_group(pass: &mut LayoutEvent) {
112 if let Some(view) = pass.scene.get_view_mut(pass.target) {
113 let char_size = pass.theme.font.character_size;
114 let mut height = char_size.height + (char_size.height / 2) * 2; if view.h_flex == Resize {
116 view.bounds.size.w = pass.space.w;
117 }
118 if view.h_flex == Intrinsic {
119 if let Some(state) = view.get_state::<SelectOneOfState>() {
120 let mut width = 0;
121 for item in &state.items {
122 width += char_size.width as i32;
123 width += item.len() as i32 * char_size.width as i32;
124 width += char_size.width as i32;
125 }
126 view.bounds.size.w = width;
127 }
128 }
129 if view.v_flex == Resize {
130 view.bounds.size.h = pass.space.h;
131 }
132 if view.v_flex == Intrinsic {
133 view.bounds.size.h = height as i32;
134 }
135 }
136 pass.layout_all_children(&pass.target.clone(), pass.space);
137}
138mod tests {
139 use crate::geom::{Bounds, Point};
140 use crate::scene::{Scene, click_at, draw_scene, layout_scene};
141 use crate::test::MockDrawingContext;
142 use crate::toggle_group::{SelectOneOfState, make_toggle_group};
143 use crate::view::ViewId;
144 use alloc::vec;
145
146 #[test]
147 fn test_toggle_group() {
148 let theme = MockDrawingContext::make_mock_theme();
149 let mut scene: Scene = Scene::new_with_bounds(Bounds::new(0, 0, 100, 240));
150 {
151 let group = make_toggle_group(&ViewId::new("group"), vec!["A", "BB", "CCC"], 0);
152 scene.add_view_to_root(group);
153 }
154 layout_scene(&mut scene, &theme);
155
156 {
157 let mut group = scene.get_view_mut(&ViewId::new("group")).unwrap();
158 assert_eq!(group.name.as_str(), "group");
159 assert_eq!(group.bounds, Bounds::new(0, 0, 100, 13 + 7));
160 let state = &mut group.get_state::<SelectOneOfState>().unwrap();
161 assert_eq!(state.items, vec!["A", "BB", "CCC"]);
162 assert_eq!(state.selected, 0);
163 }
164
165 click_at(&mut scene, &vec![], Point::new(50, 10));
166
167 {
168 let state = &mut scene
169 .get_view_state::<SelectOneOfState>(&ViewId::new("group"))
170 .unwrap();
171 assert_eq!(state.items, vec!["A", "BB", "CCC"]);
172 assert_eq!(state.selected, 1);
173 }
174
175 let mut ctx = MockDrawingContext::new(&scene);
176 assert_eq!(scene.dirty, true);
177 draw_scene(&mut scene, &mut ctx, &theme);
178 assert_eq!(scene.dirty, false);
179 }
180}