pushrod/render/widget_cache.rs
1// Pushrod Rendering Library
2// Widget Caching Library
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use std::cell::RefCell;
17
18use crate::render::layout_cache::LayoutContainer;
19use crate::render::texture_cache::TextureCache;
20use crate::render::widget::Widget;
21use crate::render::widget_config::{CONFIG_ORIGIN, CONFIG_SIZE};
22use sdl2::event::Event;
23use sdl2::pixels::Color;
24use sdl2::rect::Rect;
25use sdl2::render::Canvas;
26use sdl2::video::Window;
27
28/// This is a container that stores information about a `Widget` that will be drawn on the screen.
29/// It stores the `Widget` object, the actual point of origin inside the `Window` (as a `Vec<i32>`
30/// of X and Y points), the parent ID of this `Widget`, if it is being added as a child.
31pub struct WidgetContainer {
32 pub widget: RefCell<Box<dyn Widget>>,
33 widget_name: String,
34 pub origin: Vec<i32>,
35 widget_id: i32,
36 parent_id: i32,
37}
38
39/// This is the `WidgetContainer` object that stores a `Widget` and its accompanying information:
40/// its name, point of origin, and parent ID.
41impl WidgetContainer {
42 /// Creates a new `WidgetContainer` storage object.
43 pub fn new(
44 widget: Box<dyn Widget>,
45 widget_name: String,
46 origin: Vec<i32>,
47 widget_id: i32,
48 parent_id: i32,
49 ) -> Self {
50 Self {
51 widget: RefCell::new(widget),
52 widget_name,
53 origin,
54 widget_id,
55 parent_id,
56 }
57 }
58
59 /// Retrieves the name of this `Widget`.
60 pub fn get_widget_name(&self) -> String {
61 self.widget_name.clone()
62 }
63
64 /// Retrieves the numeric ID of this `Widget`.
65 pub fn get_widget_id(&self) -> i32 {
66 self.widget_id
67 }
68
69 /// Retrieves the numeric ID of the parent that this `Widget` refers to. A `0` indicates
70 /// no parent is assigned.
71 pub fn get_parent_id(&self) -> i32 {
72 self.parent_id
73 }
74}
75
76/// This is the `WidgetCache` struct, which contains a list of `Widget`s that are managed by the Pushrod
77/// `Engine`. `Widget` IDs are automatically generated by the `WidgetCache`, which automatically
78/// assigns the `Widget` ID at the time it's added to the cache. Parent IDs must already exist,
79/// otherwise, an error is thrown at the time the `Widget` is attempted to be added. `Widget` IDs
80/// always start at 1.
81pub struct WidgetCache {
82 cache: Vec<WidgetContainer>,
83 texture_cache: TextureCache,
84}
85
86/// This is the `WidgetCache` implementation. This cache object manages the `Widget` list for use by the
87/// Pushrod `Engine`.
88impl WidgetCache {
89 pub fn new() -> Self {
90 Self {
91 cache: Vec::new(),
92 texture_cache: TextureCache::new(),
93 }
94 }
95
96 /// This adds a `Widget` to the render list. It requires that the `Widget` being added is in a `Box`,
97 /// along with a `widget_name`. Returns the ID of the `Widget` that was added. Use this ID if
98 /// you plan on adding further `Widget`s, with this `Widget` as the parent. The point of
99 /// `origin` (extracted from the `Widget`'s position at creation time) is its physical location
100 /// inside the `Window`.
101 pub fn add_widget(&mut self, mut widget: Box<dyn Widget>, widget_name: String) -> i32 {
102 let origin = widget.get_config().get_point(CONFIG_ORIGIN);
103 let widget_id = self.cache.len();
104
105 self.cache.push(WidgetContainer::new(
106 widget,
107 widget_name,
108 origin,
109 widget_id as i32,
110 0,
111 ));
112
113 (self.cache.len() - 1) as i32
114 }
115
116 /// This locates the ID of a `Widget` at a given `x` and `y` coordinate. If a `Widget` could not
117 /// be found, the top-level `Widget` (id 0) is returned. This function returns the top-most
118 /// visible `Widget` id.
119 pub fn find_widget(&mut self, x: i32, y: i32) -> i32 {
120 let mut found_widget_id: i32 = 0;
121
122 for i in 0..self.cache.len() {
123 if !self.is_hidden(i as i32) {
124 let start_x: i32 = self.cache[i]
125 .widget
126 .borrow_mut()
127 .get_config()
128 .get_point(CONFIG_ORIGIN)[0];
129 let start_y: i32 = self.cache[i]
130 .widget
131 .borrow_mut()
132 .get_config()
133 .get_point(CONFIG_ORIGIN)[1];
134 let end_x: i32 = start_x
135 + (self.cache[i]
136 .widget
137 .borrow_mut()
138 .get_config()
139 .get_size(CONFIG_SIZE)[0] as i32);
140 let end_y: i32 = start_y
141 + (self.cache[i]
142 .widget
143 .borrow_mut()
144 .get_config()
145 .get_size(CONFIG_SIZE)[1] as i32);
146
147 if x >= start_x && x <= end_x && y >= start_y && y <= end_y {
148 found_widget_id = i as i32;
149 }
150 }
151 }
152
153 found_widget_id
154 }
155
156 /// Returns a `WidgetContainer` object by its ID. This is the same `Widget` ID that is returned
157 /// when using the `add_widget` function. There are no bounds checks here, so if the ID does not
158 /// exist, it will throw an exception at runtime. Be careful: it's better to use the
159 /// `get_container_by_name` function to avoid this.
160 pub fn get_container_by_id(&mut self, id: i32) -> &mut WidgetContainer {
161 &mut self.cache[id as usize]
162 }
163
164 /// Returns a `WidgetContainer` object by the name of the `Widget`. If the `WidgetContainer`
165 /// cannot find the `Widget` by the `name` specified, the top-level `Widget` is returned for
166 /// safety.
167 pub fn get_container_by_name(&mut self, name: String) -> &mut WidgetContainer {
168 let cache_size = self.cache.len();
169
170 for i in 0..cache_size {
171 if self.cache[i].get_widget_name() == name {
172 return self.get_container_by_id(i as i32);
173 }
174 }
175
176 self.get_container_by_id(0 as i32)
177 }
178
179 /// This function calls the `button_clicked` callback for the `Widget` specified by `widget_id`.
180 /// When state is set to `true`, this indicates that a mouse button down was detected. When set
181 /// to `false`, it indicates that the mouse button was released. When setting the button state
182 /// to `widget_id == -1`, the button click message will be sent to _all_ `Widget`s, so use
183 /// `widget_id == -1` with care.
184 pub fn button_clicked(
185 &mut self,
186 widget_id: i32,
187 button: u8,
188 clicks: u8,
189 state: bool,
190 cache: &[LayoutContainer],
191 ) {
192 if widget_id == -1 {
193 for i in 0..self.cache.len() {
194 if !self.is_hidden(i as i32) && self.is_enabled(i as i32) {
195 self.cache[i as usize].widget.borrow_mut().button_clicked(
196 &self.cache,
197 cache,
198 button,
199 clicks,
200 state,
201 );
202 }
203 }
204 } else if !self.is_hidden(widget_id) && self.is_enabled(widget_id) {
205 self.cache[widget_id as usize]
206 .widget
207 .borrow_mut()
208 .button_clicked(&self.cache, cache, button, clicks, state);
209 }
210 }
211
212 /// This function calls the `mouse_moved` callback for the `Widget` specified by `widget_id`.
213 pub fn mouse_moved(&mut self, widget_id: i32, points: Vec<i32>, cache: &[LayoutContainer]) {
214 if !self.is_hidden(widget_id) && self.is_enabled(widget_id) {
215 self.cache[widget_id as usize]
216 .widget
217 .borrow_mut()
218 .mouse_moved(&self.cache, cache, points);
219 }
220 }
221
222 /// This function calls the `mouse_scrolled` callback for the `Widget` specified by `widget_id`.
223 pub fn mouse_scrolled(&mut self, widget_id: i32, points: Vec<i32>, cache: &[LayoutContainer]) {
224 if !self.is_hidden(widget_id) && self.is_enabled(widget_id) {
225 self.cache[widget_id as usize]
226 .widget
227 .borrow_mut()
228 .mouse_scrolled(&self.cache, cache, points);
229 }
230 }
231
232 /// This function calls the `mouse_exited` callback for the `Widget` specified by `widget_id`.
233 pub fn mouse_exited(&mut self, widget_id: i32, cache: &[LayoutContainer]) {
234 if !self.is_hidden(widget_id) && self.is_enabled(widget_id) {
235 self.cache[widget_id as usize]
236 .widget
237 .borrow_mut()
238 .mouse_exited(&self.cache, cache);
239 }
240 }
241
242 /// This function calls the `mouse_entered` callback for the `Widget` specified by `widget_id`.
243 pub fn mouse_entered(&mut self, widget_id: i32, cache: &[LayoutContainer]) {
244 if !self.is_hidden(widget_id) && self.is_enabled(widget_id) {
245 self.cache[widget_id as usize]
246 .widget
247 .borrow_mut()
248 .mouse_entered(&self.cache, cache);
249 }
250 }
251
252 /// This function calls the `tick` method on all registered `Widget`s in the cache. The purpose
253 /// for the `tick` is to indicate that a drawing loop is about to occur, and the `Widget` can
254 /// update itself as necessary beforehand.
255 pub fn tick(&mut self, _cache: &[LayoutContainer]) {
256 let cache_size = self.cache.len();
257
258 for i in 0..cache_size {
259 if !self.is_hidden(i as i32) {
260 self.cache[i].widget.borrow_mut().tick(&self.cache, _cache);
261 }
262 }
263 }
264
265 /// This function sends all other un-handled events from SDL2 to the currently highlighted
266 /// `Widget`.
267 pub fn other_event(&mut self, widget_id: i32, event: Event, cache: &[LayoutContainer]) {
268 if !self.is_hidden(widget_id) && self.is_enabled(widget_id) {
269 self.cache[widget_id as usize]
270 .widget
271 .borrow_mut()
272 .other_event(&self.cache, cache, event);
273 }
274 }
275
276 /// This function performs the draw loop for all of the `Widget`s stored in the `cache`. Each
277 /// `Widget` receives a mutable reference to the `Canvas` so that the `Widget` can be drawn on
278 /// the screen during the draw loop of the `Engine`. This `draw_loop` function automatically
279 /// clips the screen area so that the `Widget` cannot draw outside of its bounds. Returns `true`
280 /// if the display loop needs to refresh the top-level canvas, `false` otherwise.
281 pub fn draw_loop(&mut self, c: &mut Canvas<Window>) -> bool {
282 let cache_size = self.cache.len();
283
284 for i in 0..cache_size {
285 if self.cache[i].widget.borrow_mut().is_invalidated() {
286 self.draw(0, c);
287
288 return true;
289 }
290 }
291
292 false
293 }
294
295 /// Returns a borrowed slice of the `WidgetContainer` `Vec` object, which can be passed on to
296 /// `Layout` objects so that the layout can be computed and performed.
297 pub fn borrow_cache(&mut self) -> &[WidgetContainer] {
298 &self.cache
299 }
300
301 // Private functions
302
303 fn get_children_of(&mut self, widget_id: i32) -> Vec<i32> {
304 self.cache
305 .iter()
306 .filter(|x| x.parent_id == widget_id)
307 .map(|x| x.widget_id)
308 .collect()
309 }
310
311 fn draw(&mut self, widget_id: i32, c: &mut Canvas<Window>) {
312 let parents_of_widget = self.get_children_of(widget_id);
313
314 if parents_of_widget.is_empty() {
315 return;
316 }
317
318 for paint_id in &parents_of_widget {
319 let paint_widget = &mut self.cache[*paint_id as usize];
320 let is_hidden = paint_widget.widget.borrow_mut().get_config().is_hidden();
321 let is_enabled = paint_widget.widget.borrow_mut().get_config().is_enabled();
322 let widget_x = paint_widget.widget.borrow_mut().get_config().to_x(0);
323 let widget_y = paint_widget.widget.borrow_mut().get_config().to_y(0);
324 let widget_w = paint_widget
325 .widget
326 .borrow_mut()
327 .get_config()
328 .get_size(CONFIG_SIZE)[0];
329 let widget_h = paint_widget
330 .widget
331 .borrow_mut()
332 .get_config()
333 .get_size(CONFIG_SIZE)[1];
334
335 if !is_hidden {
336 match paint_widget
337 .widget
338 .borrow_mut()
339 .draw(c, &mut self.texture_cache)
340 {
341 Some(texture) => {
342 c.copy(
343 texture,
344 None,
345 Rect::new(widget_x, widget_y, widget_w, widget_h),
346 )
347 .unwrap();
348 }
349 None => eprintln!("No texture presented: ID={}", paint_id),
350 };
351
352 paint_widget.widget.borrow_mut().set_invalidated(false);
353 }
354
355 if *paint_id != widget_id {
356 self.draw(*paint_id, c);
357 }
358
359 if !is_enabled {
360 c.set_draw_color(Color::RGBA(0, 0, 0, 128));
361 c.draw_rect(Rect::new(widget_x, widget_y, widget_w, widget_h))
362 .unwrap();
363 }
364 }
365 }
366
367 fn is_hidden(&self, widget_id: i32) -> bool {
368 self.cache[widget_id as usize]
369 .widget
370 .borrow_mut()
371 .get_config()
372 .is_hidden()
373 }
374
375 fn is_enabled(&self, widget_id: i32) -> bool {
376 self.cache[widget_id as usize]
377 .widget
378 .borrow_mut()
379 .get_config()
380 .is_enabled()
381 }
382}
383
384impl Default for WidgetCache {
385 fn default() -> Self {
386 Self::new()
387 }
388}