pushrod/widgets/
list_widget.rs1use crate::render::callbacks::CallbackRegistry;
17use crate::render::widget::*;
18use crate::render::widget_cache::WidgetContainer;
19use crate::render::widget_config::*;
20
21use sdl2::render::{Canvas, Texture, TextureQuery};
22use sdl2::video::Window;
23
24use crate::render::canvas_helper::CanvasHelper;
25use crate::render::layout_cache::LayoutContainer;
26use crate::render::texture_cache::TextureCache;
27use crate::render::texture_store::TextureStore;
28use crate::render::{Points, Size, POINT_Y, SIZE_WIDTH};
29use sdl2::pixels::Color;
30use sdl2::rect::Rect;
31use std::any::Any;
32use std::collections::HashMap;
33use std::path::Path;
34
35pub type OnSelectedCallbackType =
38 Option<Box<dyn FnMut(&mut ListWidget, &[WidgetContainer], &[LayoutContainer], i32)>>;
39
40pub struct ListWidget {
42 config: WidgetConfig,
43 system_properties: HashMap<i32, String>,
44 callback_registry: CallbackRegistry,
45 texture_store: TextureStore,
46 list_items: Vec<String>,
47 highlighted_item: i32,
48 selected_item: i32,
49 in_bounds: bool,
50 on_selected: OnSelectedCallbackType,
51}
52
53impl ListWidget {
56 pub fn new(points: Points, size: Size) -> Self {
58 Self {
59 config: WidgetConfig::new(points, size),
60 system_properties: HashMap::new(),
61 callback_registry: CallbackRegistry::new(),
62 texture_store: TextureStore::default(),
63 list_items: vec![],
64 highlighted_item: -1,
65 selected_item: -1,
66 in_bounds: false,
67 on_selected: None,
68 }
69 }
70
71 pub fn add_item(&mut self, item: String) -> usize {
73 let item_size = self.list_items.len() + 1;
74
75 self.list_items.push(item);
76
77 item_size
78 }
79
80 pub fn on_selected<F>(&mut self, callback: F)
83 where
84 F: FnMut(&mut ListWidget, &[WidgetContainer], &[LayoutContainer], i32) + 'static,
85 {
86 self.on_selected = Some(Box::new(callback));
87 }
88
89 fn call_selected_callback(&mut self, widgets: &[WidgetContainer], layouts: &[LayoutContainer]) {
93 if let Some(mut cb) = self.on_selected.take() {
94 cb(self, widgets, layouts, self.selected_item);
95 self.on_selected = Some(cb);
96 }
97 }
98}
99
100impl CanvasHelper for ListWidget {}
101
102impl Widget for ListWidget {
104 fn draw(&mut self, c: &mut Canvas<Window>, t: &mut TextureCache) -> Option<&Texture> {
106 if self.get_config().invalidated() {
107 let bounds = self.get_config().get_size(CONFIG_SIZE);
108
109 self.texture_store
110 .create_or_resize_texture(c, bounds[0] as u32, bounds[1] as u32);
111
112 let base_color = self.get_color(CONFIG_COLOR_BASE);
113 let hover_color = self.get_color(CONFIG_COLOR_HOVER);
114 let border_color = self.get_config().get_color(CONFIG_COLOR_BORDER);
115 let list_size = self.list_items.len();
116 let highlighted_item = self.highlighted_item;
117 let selected_item = self.selected_item;
118 let list_items = self.list_items.clone();
119
120 let ttf_context = t.get_ttf_context();
121 let texture_creator = c.texture_creator();
122 let mut font = ttf_context
123 .load_font(Path::new(&String::from("assets/OpenSans-Regular.ttf")), 16)
124 .unwrap();
125
126 font.set_style(sdl2::ttf::FontStyle::NORMAL);
127
128 c.with_texture_canvas(self.texture_store.get_mut_ref(), |texture| {
129 texture.set_draw_color(base_color);
130 texture.clear();
131
132 let list_height: u32 = 30;
133
134 for i in 0..list_size {
135 let mut text_color = Color::RGB(0, 0, 0);
136 let mut color = if highlighted_item == i as i32 {
137 hover_color
138 } else {
139 Color::RGB(255, 255, 255)
140 };
141
142 if selected_item == i as i32 {
143 color = Color::RGB(0, 0, 0);
144 text_color = Color::RGB(255, 255, 255);
145 }
146
147 texture.set_draw_color(color);
148 texture
149 .fill_rect(Rect::new(
150 0,
151 (list_height * i as u32) as i32,
152 bounds[SIZE_WIDTH],
153 30,
154 ))
155 .unwrap();
156
157 let surface = font
158 .render(&list_items[i].clone())
159 .blended_wrapped(text_color, bounds[SIZE_WIDTH])
160 .map_err(|e| e.to_string())
161 .unwrap();
162 let font_texture = texture_creator
163 .create_texture_from_surface(&surface)
164 .map_err(|e| e.to_string())
165 .unwrap();
166
167 let TextureQuery { width, height, .. } = font_texture.query();
168 let texture_y = (list_height * i as u32) as i32 + 3;
169 let texture_x = 10;
170
171 texture
172 .copy(
173 &font_texture,
174 None,
175 Rect::new(texture_x, texture_y, width, height),
176 )
177 .unwrap();
178 }
179
180 texture.set_draw_color(border_color);
181 texture
182 .draw_rect(Rect::new(0, 0, bounds[0], bounds[1]))
183 .unwrap();
184 })
185 .unwrap();
186 }
187
188 self.texture_store.get_optional_ref()
189 }
190
191 fn mouse_entered(&mut self, _widgets: &[WidgetContainer], _layouts: &[LayoutContainer]) {
193 self.in_bounds = true;
194 }
195
196 fn mouse_exited(&mut self, _widgets: &[WidgetContainer], _layouts: &[LayoutContainer]) {
198 self.in_bounds = false;
199 self.highlighted_item = -1;
200 self.get_config().set_invalidated(true);
201 }
202
203 fn mouse_moved(
205 &mut self,
206 _widgets: &[WidgetContainer],
207 _layouts: &[LayoutContainer],
208 points: Points,
209 ) {
210 if self.in_bounds {
211 let position_y =
212 points[POINT_Y] - self.get_config().get_point(CONFIG_ORIGIN)[POINT_Y] as i32;
213 let previous_highlighted_item = self.highlighted_item;
214
215 self.highlighted_item = position_y / 30;
216
217 if self.highlighted_item >= self.list_items.len() as i32 {
218 self.highlighted_item = -1;
219 }
220
221 if self.highlighted_item != previous_highlighted_item {
222 self.get_config().set_invalidated(true);
223 }
224 }
225 }
226
227 fn button_clicked(
229 &mut self,
230 _widgets: &[WidgetContainer],
231 _layouts: &[LayoutContainer],
232 button: u8,
233 _clicks: u8,
234 state: bool,
235 ) {
236 if button == 1 && state {
237 self.selected_item = self.highlighted_item;
238 self.get_config().set_invalidated(true);
239
240 self.call_selected_callback(_widgets, _layouts);
241 }
242 }
243
244 default_widget_functions!();
245 default_widget_properties!();
246 default_widget_callbacks!();
247}