1use crate::render::callbacks::CallbackRegistry;
17use crate::render::widget::*;
18use crate::render::widget_cache::WidgetContainer;
19use crate::render::widget_config::*;
20use crate::render::{Points, Size, POINT_X, POINT_Y, SIZE_HEIGHT, SIZE_WIDTH};
21
22use sdl2::render::{Canvas, Texture};
23use sdl2::video::Window;
24
25use crate::render::canvas_helper::CanvasHelper;
26use crate::render::layout_cache::LayoutContainer;
27use crate::render::texture_cache::TextureCache;
28use crate::render::texture_store::TextureStore;
29use crate::widgets::slider_widget::SliderOrientation::{SliderHorizontal, SliderVertical};
30use sdl2::pixels::Color;
31use sdl2::rect::{Point, Rect};
32use std::any::Any;
33use std::collections::HashMap;
34
35pub type OnValueChangedCallbackType =
38 Option<Box<dyn FnMut(&mut SliderWidget, &[WidgetContainer], &[LayoutContainer], u32)>>;
39
40#[derive(PartialEq, Clone)]
42pub enum SliderOrientation {
43 SliderHorizontal,
45
46 SliderVertical,
48}
49
50pub struct SliderWidget {
52 config: WidgetConfig,
53 system_properties: HashMap<i32, String>,
54 callback_registry: CallbackRegistry,
55 texture_store: TextureStore,
56 min: u32,
57 max: u32,
58 current: u32,
59 orientation: SliderOrientation,
60 in_bounds: bool,
61 active: bool,
62 originated: bool,
63 on_value_changed: OnValueChangedCallbackType,
64}
65
66impl SliderWidget {
69 pub fn new(
72 points: Points,
73 size: Size,
74 min: u32,
75 max: u32,
76 current: u32,
77 orientation: SliderOrientation,
78 ) -> Self {
79 Self {
80 config: WidgetConfig::new(points, size),
81 system_properties: HashMap::new(),
82 callback_registry: CallbackRegistry::new(),
83 texture_store: TextureStore::default(),
84 min,
85 max,
86 current,
87 orientation,
88 in_bounds: false,
89 active: false,
90 originated: false,
91 on_value_changed: None,
92 }
93 }
94
95 pub fn on_value_changed<F>(&mut self, callback: F)
97 where
98 F: FnMut(&mut SliderWidget, &[WidgetContainer], &[LayoutContainer], u32) + 'static,
99 {
100 self.on_value_changed = Some(Box::new(callback));
101 }
102
103 fn call_value_changed_callback(
105 &mut self,
106 widgets: &[WidgetContainer],
107 layouts: &[LayoutContainer],
108 ) {
109 if let Some(mut cb) = self.on_value_changed.take() {
110 cb(self, widgets, layouts, self.current);
111 self.on_value_changed = Some(cb);
112 }
113 }
114}
115
116impl CanvasHelper for SliderWidget {}
117
118impl Widget for SliderWidget {
120 fn draw(&mut self, c: &mut Canvas<Window>, _t: &mut TextureCache) -> Option<&Texture> {
122 if self.get_config().invalidated() {
123 let bounds = self.get_config().get_size(CONFIG_SIZE);
124
125 self.texture_store
126 .create_or_resize_texture(c, bounds[0] as u32, bounds[1] as u32);
127
128 let half_height = (self.get_config().get_size(CONFIG_SIZE)[SIZE_HEIGHT] / 2) as i32;
130 let half_width = (self.get_config().get_size(CONFIG_SIZE)[SIZE_WIDTH] / 2) as i32;
131 let height = (self.get_config().get_size(CONFIG_SIZE)[SIZE_HEIGHT]) as i32;
132 let width = (self.get_config().get_size(CONFIG_SIZE)[SIZE_WIDTH]) as i32;
133 let base_color = self.get_color(CONFIG_COLOR_BASE);
134 let orientation = self.orientation.clone();
135 let min = self.min;
136 let max = self.max;
137 let current = self.current;
138
139 c.with_texture_canvas(self.texture_store.get_mut_ref(), |texture| {
140 texture.set_draw_color(base_color);
141 texture.clear();
142
143 if orientation == SliderHorizontal {
144 texture.set_draw_color(Color::RGB(192, 192, 192));
145 texture
146 .draw_line(
147 Point::new(10, half_height),
148 Point::new(width - 10, half_height),
149 )
150 .unwrap();
151
152 texture
153 .draw_line(
154 Point::new(10, half_height - 1),
155 Point::new(width - 10, half_height - 1),
156 )
157 .unwrap();
158
159 texture
160 .draw_line(
161 Point::new(10, half_height + 1),
162 Point::new(width - 10, half_height + 1),
163 )
164 .unwrap();
165
166 let full_range = max - min;
168 let slider_center =
169 ((width as f64 / full_range as f64) * (current - min) as f64) as u32;
170 let slider_start = if slider_center >= width as u32 - 15 {
171 width as u32 - 30
172 } else if slider_center <= 15 {
173 0
174 } else {
175 slider_center - 15
176 };
177
178 texture.set_draw_color(base_color);
179 texture
180 .fill_rect(Rect::new(slider_start as i32, 0, 30, bounds[SIZE_HEIGHT]))
181 .unwrap();
182
183 texture.set_draw_color(Color::RGB(0, 0, 0));
184 texture
185 .draw_rect(Rect::new(slider_start as i32, 0, 30, bounds[SIZE_HEIGHT]))
186 .unwrap();
187 } else if orientation == SliderVertical {
188 texture.set_draw_color(Color::RGB(192, 192, 192));
191 texture
192 .draw_line(
193 Point::new(half_width, 10),
194 Point::new(half_width, height - 10),
195 )
196 .unwrap();
197
198 texture
199 .draw_line(
200 Point::new(half_width - 1, 10),
201 Point::new(half_width - 1, height - 10),
202 )
203 .unwrap();
204
205 texture
206 .draw_line(
207 Point::new(half_width + 1, 10),
208 Point::new(half_width + 1, height - 10),
209 )
210 .unwrap();
211
212 let full_range = max - min;
214 let slider_center =
215 ((height as f64 / full_range as f64) * (current - min) as f64) as u32;
216 let slider_start = if slider_center >= height as u32 - 15 {
217 height as u32 - 30
218 } else if slider_center <= 15 {
219 0
220 } else {
221 slider_center - 15
222 };
223
224 texture.set_draw_color(base_color);
225 texture
226 .fill_rect(Rect::new(0, slider_start as i32, bounds[SIZE_WIDTH], 30))
227 .unwrap();
228
229 texture.set_draw_color(Color::RGB(0, 0, 0));
230 texture
231 .draw_rect(Rect::new(0, slider_start as i32, bounds[SIZE_WIDTH], 30))
232 .unwrap();
233 }
234 })
235 .unwrap();
236 }
237
238 self.texture_store.get_optional_ref()
239 }
240
241 fn mouse_entered(&mut self, _widgets: &[WidgetContainer], _layouts: &[LayoutContainer]) {
243 self.in_bounds = true;
244 }
245
246 fn mouse_exited(&mut self, _widgets: &[WidgetContainer], _layouts: &[LayoutContainer]) {
248 self.in_bounds = false;
249 }
250
251 fn mouse_moved(
253 &mut self,
254 _widgets: &[WidgetContainer],
255 _layouts: &[LayoutContainer],
256 points: Points,
257 ) {
258 if self.in_bounds && self.active && self.originated {
259 if self.orientation == SliderHorizontal {
260 let width = (self.get_config().get_size(CONFIG_SIZE)[SIZE_WIDTH]) as i32;
261 let position_x =
262 points[POINT_X] - self.get_config().get_point(CONFIG_ORIGIN)[POINT_X] as i32;
263 let percentage = position_x as f64 / width as f64;
264 let full_range = self.max - self.min;
265 let actual = (percentage * full_range as f64) as u32;
266
267 self.current = self.min + actual;
268
269 self.get_config().set_invalidated(true);
270 self.call_value_changed_callback(_widgets, _layouts);
271 } else if self.orientation == SliderVertical {
272 let height = (self.get_config().get_size(CONFIG_SIZE)[SIZE_HEIGHT]) as i32;
273 let position_y =
274 points[POINT_Y] - self.get_config().get_point(CONFIG_ORIGIN)[POINT_Y] as i32;
275 let percentage = position_y as f64 / height as f64;
276 let full_range = self.max - self.min;
277 let actual = (percentage * full_range as f64) as u32;
278
279 self.current = self.min + actual;
280
281 self.get_config().set_invalidated(true);
282 self.call_value_changed_callback(_widgets, _layouts);
283 }
284 }
285 }
286
287 fn mouse_scrolled(
289 &mut self,
290 _widgets: &[WidgetContainer],
291 _layouts: &[LayoutContainer],
292 points: Points,
293 ) {
294 let mut current_i32 = self.current as i32;
295
296 if self.orientation == SliderHorizontal {
297 current_i32 += points[POINT_X];
298 } else if self.orientation == SliderVertical {
299 current_i32 += -points[POINT_Y];
300 }
301
302 if current_i32 >= self.max as i32 {
303 current_i32 = self.max as i32;
304 } else if current_i32 <= self.min as i32 {
305 current_i32 = self.min as i32;
306 }
307
308 self.current = current_i32 as u32;
309
310 self.get_config().set_invalidated(true);
311 self.call_value_changed_callback(_widgets, _layouts);
312 }
313
314 fn button_clicked(
316 &mut self,
317 _widgets: &[WidgetContainer],
318 _layouts: &[LayoutContainer],
319 _button: u8,
320 _clicks: u8,
321 _state: bool,
322 ) {
323 if _button == 1 {
324 if _state {
325 self.active = true;
326 self.originated = true;
327 } else {
328 self.active = false;
329 self.originated = false;
330 }
331
332 self.get_config().set_invalidated(true);
333 }
334
335 self.button_clicked_callback(_widgets, _layouts, _button, _clicks, _state);
336 }
337
338 default_widget_functions!();
339 default_widget_properties!();
340 default_widget_callbacks!();
341}