1use crate::render::callbacks::CallbackRegistry;
17use crate::render::widget::*;
18use crate::render::widget_cache::WidgetContainer;
19use crate::render::widget_config::{
20 WidgetConfig, CONFIG_BORDER_WIDTH, CONFIG_COLOR_BASE, CONFIG_COLOR_BORDER, CONFIG_COLOR_TEXT,
21 CONFIG_SIZE,
22};
23use crate::render::{
24 make_points, make_size, Points, Size, POINT_X, POINT_Y, SIZE_HEIGHT, SIZE_WIDTH,
25};
26
27use sdl2::render::{Canvas, Texture};
28use sdl2::video::Window;
29
30use crate::render::layout_cache::LayoutContainer;
31use crate::render::texture_cache::TextureCache;
32use crate::render::texture_store::TextureStore;
33use crate::widgets::text_widget::{TextJustify, TextWidget};
34use sdl2::pixels::Color;
35use sdl2::rect::Rect;
36use std::any::Any;
37use std::collections::HashMap;
38
39pub type OnClickCallbackType =
42 Option<Box<dyn FnMut(&mut PushButtonWidget, &[WidgetContainer], &[LayoutContainer])>>;
43
44pub struct PushButtonWidget {
46 config: WidgetConfig,
47 system_properties: HashMap<i32, String>,
48 callback_registry: CallbackRegistry,
49 texture_store: TextureStore,
50 base_widget: BaseWidget,
51 text_widget: TextWidget,
52 active: bool,
53 in_bounds: bool,
54 originated: bool,
55 on_click: OnClickCallbackType,
56}
57
58impl PushButtonWidget {
62 pub fn new(points: Points, size: Size, text: String, font_size: i32) -> Self {
65 let mut base_widget = BaseWidget::new(points.clone(), size.clone());
66 let mut text_widget = TextWidget::new(
67 String::from("assets/OpenSans-Regular.ttf"),
68 sdl2::ttf::FontStyle::NORMAL,
69 font_size,
70 TextJustify::Center,
71 text,
72 make_points(points[POINT_X] + 2, points[POINT_Y] + 2),
73 make_size(size[SIZE_WIDTH] - 4, size[SIZE_HEIGHT] - 4),
74 );
75
76 base_widget.set_color(CONFIG_COLOR_BASE, Color::RGB(255, 255, 255));
77 base_widget.set_color(CONFIG_COLOR_BORDER, Color::RGB(0, 0, 0));
78 base_widget.set_numeric(CONFIG_BORDER_WIDTH, 2);
79
80 text_widget.set_color(CONFIG_COLOR_TEXT, Color::RGB(0, 0, 0));
81
82 Self {
83 config: WidgetConfig::new(points, size),
84 system_properties: HashMap::new(),
85 callback_registry: CallbackRegistry::new(),
86 texture_store: TextureStore::default(),
87 base_widget,
88 text_widget,
89 active: false,
90 in_bounds: false,
91 originated: false,
92 on_click: None,
93 }
94 }
95
96 fn draw_hovered(&mut self) {
97 self.base_widget
98 .set_color(CONFIG_COLOR_BASE, Color::RGB(0, 0, 0));
99 self.text_widget
100 .set_color(CONFIG_COLOR_TEXT, Color::RGB(255, 255, 255));
101 self.text_widget
102 .set_color(CONFIG_COLOR_BASE, Color::RGB(0, 0, 0));
103 self.get_config().set_invalidated(true);
104 }
105
106 fn draw_unhovered(&mut self) {
107 self.base_widget
108 .set_color(CONFIG_COLOR_BASE, Color::RGB(255, 255, 255));
109 self.text_widget
110 .set_color(CONFIG_COLOR_TEXT, Color::RGB(0, 0, 0));
111 self.text_widget
112 .set_color(CONFIG_COLOR_BASE, Color::RGB(255, 255, 255));
113 self.get_config().set_invalidated(true);
114 }
115
116 pub fn on_click<F>(&mut self, callback: F)
118 where
119 F: FnMut(&mut PushButtonWidget, &[WidgetContainer], &[LayoutContainer]) + 'static,
120 {
121 self.on_click = Some(Box::new(callback));
122 }
123
124 fn call_click_callback(&mut self, widgets: &[WidgetContainer], layouts: &[LayoutContainer]) {
126 if let Some(mut cb) = self.on_click.take() {
127 cb(self, widgets, layouts);
128 self.on_click = Some(cb);
129 }
130 }
131}
132
133impl Widget for PushButtonWidget {
135 fn draw(&mut self, c: &mut Canvas<Window>, t: &mut TextureCache) -> Option<&Texture> {
136 if self.get_config().invalidated() {
137 let bounds = self.get_config().get_size(CONFIG_SIZE);
138 let base_color = self.get_color(CONFIG_COLOR_BASE);
139
140 self.texture_store
141 .create_or_resize_texture(c, bounds[0] as u32, bounds[1] as u32);
142
143 let base_widget_texture = self.base_widget.draw(c, t).unwrap();
146 let text_widget_texture = self.text_widget.draw(c, t).unwrap();
147
148 c.with_texture_canvas(self.texture_store.get_mut_ref(), |texture| {
149 texture.set_draw_color(base_color);
150 texture.clear();
151
152 texture
153 .copy(
154 base_widget_texture,
155 None,
156 Rect::new(0, 0, bounds[0], bounds[1]),
157 )
158 .unwrap();
159
160 texture
161 .copy(
162 text_widget_texture,
163 None,
164 Rect::new(2, 2, bounds[0] - 4, bounds[1] - 4),
165 )
166 .unwrap();
167 })
168 .unwrap();
169 }
170
171 self.texture_store.get_optional_ref()
172 }
173
174 fn mouse_entered(&mut self, _widgets: &[WidgetContainer], _layouts: &[LayoutContainer]) {
177 if self.active {
178 self.draw_hovered();
179 }
180
181 self.in_bounds = true;
182 self.mouse_entered_callback(_widgets, _layouts);
183 }
184
185 fn mouse_exited(&mut self, _widgets: &[WidgetContainer], _layouts: &[LayoutContainer]) {
188 if self.active {
189 self.draw_unhovered();
190 }
191
192 self.in_bounds = false;
193 self.mouse_exited_callback(_widgets, _layouts);
194 }
195
196 fn button_clicked(
205 &mut self,
206 _widgets: &[WidgetContainer],
207 _layouts: &[LayoutContainer],
208 _button: u8,
209 _clicks: u8,
210 _state: bool,
211 ) {
212 if _button == 1 {
213 if _state {
214 self.draw_hovered();
215 self.active = true;
216 self.originated = true;
217 } else {
218 let had_bounds = self.active;
219
220 self.draw_unhovered();
221 self.active = false;
222
223 if self.in_bounds && had_bounds && self.originated {
224 self.call_click_callback(_widgets, _layouts);
226 }
227
228 self.originated = false;
229 }
230 }
231
232 self.button_clicked_callback(_widgets, _layouts, _button, _clicks, _state);
233 }
234
235 default_widget_functions!();
236 default_widget_properties!();
237 default_widget_callbacks!();
238}