Skip to main content

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}