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, SIZE_HEIGHT, SIZE_WIDTH};
21
22use sdl2::render::{Canvas, Texture, TextureQuery};
23use sdl2::video::Window;
24
25use crate::render::layout_cache::LayoutContainer;
26use crate::render::texture_cache::TextureCache;
27use crate::render::texture_store::TextureStore;
28use sdl2::pixels::Color;
29use sdl2::rect::{Point, Rect};
30use std::any::Any;
31use std::collections::HashMap;
32use std::path::Path;
33
34pub type OnTabSelectedCallbackType =
37 Option<Box<dyn FnMut(&mut TabBarWidget, &[WidgetContainer], &[LayoutContainer], u16)>>;
38
39pub struct TabBarWidget {
42 config: WidgetConfig,
43 system_properties: HashMap<i32, String>,
44 callback_registry: CallbackRegistry,
45 texture_store: TextureStore,
46 tab_items: Vec<String>,
47 tab_widths: Vec<u32>,
48 on_tab_selected: OnTabSelectedCallbackType,
49 selected_item: i16,
50 hovered_item: i16,
51 in_bounds: bool,
52 calculated: bool,
53}
54
55impl TabBarWidget {
59 pub fn new(points: Points, size: Size, tab_items: Vec<String>) -> Self {
62 Self {
63 config: WidgetConfig::new(points, size),
64 system_properties: HashMap::new(),
65 callback_registry: CallbackRegistry::new(),
66 texture_store: TextureStore::default(),
67 on_tab_selected: None,
68 tab_items,
69 tab_widths: vec![0],
70 selected_item: -1,
71 hovered_item: -1,
72 in_bounds: false,
73 calculated: false,
74 }
75 }
76
77 pub fn on_tab_selected<F>(&mut self, callback: F)
79 where
80 F: FnMut(&mut TabBarWidget, &[WidgetContainer], &[LayoutContainer], u16) + 'static,
81 {
82 self.on_tab_selected = Some(Box::new(callback));
83 }
84
85 fn call_tab_selected_callback(
87 &mut self,
88 widgets: &[WidgetContainer],
89 layouts: &[LayoutContainer],
90 tab: u16,
91 ) {
92 if let Some(mut cb) = self.on_tab_selected.take() {
93 cb(self, widgets, layouts, tab);
94 self.on_tab_selected = Some(cb);
95 }
96 }
97
98 fn adjust_widgets(&mut self, c: &mut Canvas<Window>, t: &mut TextureCache) {
100 let ttf_context = t.get_ttf_context();
101 let texture_creator = c.texture_creator();
102 let num_tabs = self.tab_items.len();
103 let mut font = ttf_context
104 .load_font(Path::new(&String::from("assets/OpenSans-Regular.ttf")), 10)
105 .unwrap();
106 let mut tab_widths = Vec::new();
107 let bounds = self.get_config().get_size(CONFIG_SIZE);
108
109 font.set_style(sdl2::ttf::FontStyle::NORMAL);
110
111 for i in 0..num_tabs {
112 let surface = font
113 .render(&self.tab_items[i])
114 .blended_wrapped(Color::RGB(0, 0, 0), bounds[SIZE_WIDTH])
115 .map_err(|e| e.to_string())
116 .unwrap();
117 let font_texture = texture_creator
118 .create_texture_from_surface(&surface)
119 .map_err(|e| e.to_string())
120 .unwrap();
121
122 let TextureQuery { width, .. } = font_texture.query();
123
124 tab_widths.push((width + 20) as u32);
125 }
126
127 self.tab_widths = tab_widths;
128 self.calculated = true;
129 }
130
131 fn find_hovered_item(&self, x: i32) -> i16 {
134 let mut selected_item = -1;
135 let mut start_x: i32 = 20;
136
137 for i in 0..self.tab_widths.len() {
138 if x >= start_x && x <= (start_x + self.tab_widths[i] as i32 + 30) {
139 selected_item = i as i16;
140 break;
141 }
142
143 start_x += self.tab_widths[i] as i32 + 31;
144 }
145
146 selected_item
147 }
148}
149
150impl Widget for TabBarWidget {
152 fn draw(&mut self, c: &mut Canvas<Window>, t: &mut TextureCache) -> Option<&Texture> {
153 if !self.calculated {
154 self.adjust_widgets(c, t);
155 }
156
157 if self.get_config().invalidated() {
158 let bounds = self.get_config().get_size(CONFIG_SIZE);
159 let base_color = self.get_color(CONFIG_COLOR_BASE);
160
161 self.texture_store
162 .create_or_resize_texture(c, bounds[0] as u32, bounds[1] as u32);
163
164 let tab_widths = self.tab_widths.clone();
165 let tab_items = self.tab_items.clone();
166 let selected_tab = self.selected_item;
167 let hovered_tab = self.hovered_item;
168
169 c.with_texture_canvas(self.texture_store.get_mut_ref(), |texture| {
170 texture.set_draw_color(base_color);
171 texture.clear();
172
173 let mut start_x: u32 = 20;
174
175 for i in 0..tab_widths.len() {
176 let mut font_color = Color::RGB(0, 0, 0);
177
178 if selected_tab == i as i16 {
179 texture.set_draw_color(Color::RGB(128, 128, 128));
180 font_color = Color::RGB(255, 255, 255);
181 } else if hovered_tab == i as i16 {
182 texture.set_draw_color(Color::RGB(192, 192, 192));
183 } else {
184 texture.set_draw_color(Color::RGB(224, 224, 224));
185 }
186
187 texture
188 .fill_rect(Rect::new(
189 start_x as i32,
190 0,
191 tab_widths[i] + 30,
192 bounds[SIZE_HEIGHT],
193 ))
194 .unwrap();
195
196 let (font_texture, font_width, font_height) = t.render_text(
197 texture,
198 String::from("assets/OpenSans-Regular.ttf"),
199 14,
200 sdl2::ttf::FontStyle::NORMAL,
201 tab_items[i].clone(),
202 font_color,
203 bounds[SIZE_WIDTH],
204 );
205
206 texture
207 .copy(
208 &font_texture,
209 None,
210 Rect::new(
211 start_x as i32 + 10,
212 (bounds[SIZE_HEIGHT] / 2 - 10) as i32,
213 font_width,
214 font_height,
215 ),
216 )
217 .unwrap();
218
219 start_x += tab_widths[i] + 30 + 1;
220 }
221
222 texture.set_draw_color(Color::RGB(0, 0, 0));
223 texture
224 .draw_line(
225 Point::new(0, bounds[SIZE_HEIGHT] as i32 - 1),
226 Point::new(bounds[SIZE_WIDTH] as i32, bounds[SIZE_HEIGHT] as i32 - 1),
227 )
228 .unwrap();
229 })
230 .unwrap();
231 }
232
233 self.texture_store.get_optional_ref()
234 }
235
236 fn mouse_entered(&mut self, _widgets: &[WidgetContainer], _layouts: &[LayoutContainer]) {
239 self.in_bounds = true;
240 }
241
242 fn mouse_exited(&mut self, _widgets: &[WidgetContainer], _layouts: &[LayoutContainer]) {
245 self.in_bounds = false;
246 self.hovered_item = -1;
247 self.set_invalidated(true);
248 }
249
250 fn mouse_moved(
253 &mut self,
254 _widgets: &[WidgetContainer],
255 _layouts: &[LayoutContainer],
256 points: Points,
257 ) {
258 if self.calculated {
259 let origin = self.get_config().get_point(CONFIG_ORIGIN);
260 let true_x = points[POINT_X] - origin[POINT_X];
261 let previous_hovered_item = self.hovered_item;
262 let hovered_item = self.find_hovered_item(true_x);
263
264 self.hovered_item = hovered_item;
265
266 if previous_hovered_item != hovered_item {
267 self.set_invalidated(true);
268 }
269 }
270 }
271
272 fn button_clicked(
275 &mut self,
276 _widgets: &[WidgetContainer],
277 _layouts: &[LayoutContainer],
278 button: u8,
279 _clicks: u8,
280 state: bool,
281 ) {
282 if button == 1 && self.in_bounds && self.calculated && state && self.hovered_item != -1 {
283 self.selected_item = self.hovered_item;
284 self.set_invalidated(true);
285
286 if self.selected_item > -1 {
287 self.call_tab_selected_callback(_widgets, _layouts, self.selected_item as u16);
288 }
289 }
290 }
291
292 default_widget_functions!();
293 default_widget_properties!();
294 default_widget_callbacks!();
295}