Skip to main content

pushrod/render/
widget.rs

1// Pushrod Rendering Library
2// Extensible Widget 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 sdl2::rect::Rect;
17use sdl2::render::{Canvas, Texture};
18use sdl2::video::Window;
19
20use crate::render::callbacks::*;
21use crate::render::layout_cache::LayoutContainer;
22use crate::render::texture_cache::TextureCache;
23use crate::render::texture_store::TextureStore;
24use crate::render::widget_cache::WidgetContainer;
25use crate::render::widget_config::*;
26use crate::render::{Points, Size};
27use sdl2::event::Event;
28use sdl2::pixels::Color;
29use std::any::Any;
30use std::collections::HashMap;
31
32/// This trait is shared by all `Widget` objects that have a presence on the screen.  Functions that
33/// must be implemented are documented in the trait.
34///
35/// ## Implementation Notes
36/// If no custom `get_config` function is defined, and no custom `get_system_properties` function
37/// is defined, you can omit the definition of both, and use the `default_widget_properties!()`
38/// macro to auto-generate this code in your `impl` of this `trait`.  Keep in mind, however, that
39/// these automatically generated implementation details could change in future releases of this
40/// library, so it is best to use the default implementation if possible.
41pub trait Widget {
42    /// Retrieves this `Widget` as an `Any` object so that it can be downcast using `downcast_ref`
43    /// to a `struct` that implements the `Widget` trait.
44    fn as_any(&mut self) -> &mut dyn Any;
45
46    /// Draws the widget.  If you wish to modify the canvas object, you must declare it as `mut` in
47    /// your implementation (ie `fn draw(&mut self, mut canvas: Canvas<Window>)`).  The `_canvas`
48    /// is the currently active drawing canvas at the time this function is called.  This called
49    /// during the draw loop of the `Engine`.  This returns a reference to the stored `Texture` object
50    /// within the `Widget`.  It is then copied to the canvas, and displayed in the display loop.
51    /// In this function, you can just return a reference to the `Texture` if no invalidation state
52    /// was set, otherwise, the draw can be re-performed, and the `Texture` returned.  If the drawing
53    /// function returns no texture, return a `None`, and it will not be rendered during the display
54    /// loop, but it will still be called.  A `TextureCache` is provided in case your `Widget` needs
55    /// to cache an image or a font store.
56    ///
57    /// So, why not just call `draw` each time, if the `Engine` already handles the calling of the
58    /// draw for you when an object needs invalidation?  This is to avoid excess CPU usage.  You
59    /// **can** call the draw method each time: all it will do is return the reference to the already
60    /// drawn `Texture` if you do this.  It's only at the time the contents needs to be redrawn will
61    /// the logic for the draw take place (so long the `invalidated` state is obeyed)
62    fn draw(&mut self, _c: &mut Canvas<Window>, _t: &mut TextureCache) -> Option<&Texture> {
63        None
64    }
65
66    /// Retrieves the `WidgetConfig` object for this `Widget`.
67    fn get_config(&mut self) -> &mut WidgetConfig;
68
69    /// Retrieves a `HashMap` containing system properties used by the `Pushrod` event engine.
70    fn get_system_properties(&mut self) -> &mut HashMap<i32, String>;
71
72    /// Retrieves a `Callback` registry for this `Widget`.
73    fn get_callbacks(&mut self) -> &mut CallbackRegistry;
74
75    /// When a mouse enters the bounds of the `Widget`, this function is triggered.  This function
76    /// implementation is **optional**.
77    fn mouse_entered(&mut self, _widgets: &[WidgetContainer], _layouts: &[LayoutContainer]) {
78        self.mouse_entered_callback(_widgets, _layouts);
79    }
80
81    /// When a mouse exits the bounds of the `Widget`, this function is triggered.  This function
82    /// implementation is **optional**.
83    fn mouse_exited(&mut self, _widgets: &[WidgetContainer], _layouts: &[LayoutContainer]) {
84        self.mouse_exited_callback(_widgets, _layouts);
85    }
86
87    /// When a mouse moves within the bounds of the `Widget`, this function is triggered.  It
88    /// contains the `X` and `Y` coordinates relative to the bounds of the `Widget`.  The
89    /// points start at `0x0`.  This function implementation is **optional**.
90    fn mouse_moved(
91        &mut self,
92        _widgets: &[WidgetContainer],
93        _layouts: &[LayoutContainer],
94        _points: Points,
95    ) {
96        self.mouse_moved_callback(_widgets, _layouts, _points);
97    }
98
99    /// When a mouse scroll is triggered within the bounds of the `Widget`, this function is
100    /// triggered.  Movement along the X axis indicate horizontal movement, where the Y axis
101    /// indicates vertical movement.  Positive movement means to the right or down, respectively.
102    /// Negative movement means to the left or up, respectively.  This function implementation
103    /// is **optional**.
104    fn mouse_scrolled(
105        &mut self,
106        _widgets: &[WidgetContainer],
107        _layouts: &[LayoutContainer],
108        _points: Points,
109    ) {
110        self.mouse_scrolled_callback(_widgets, _layouts, _points);
111    }
112
113    /// When a mouse button is clicked within (or outside of) the bounds of the `Widget`, this
114    /// function is called.  If a mouse button is clicked, and the mouse leaves the bounds of the
115    /// `Widget`, the mouse release event will still be triggered for the last `Widget` which
116    /// received the mouse down state.  This prevents `Widget`s from becoming confused.  This
117    /// behavior is tracked by the main loop, not by the `Widget` code.  Therefore, when a mouse
118    /// button is released outside of the bounds of _this_ `Widget`, you must adjust your state
119    /// accordingly, if you pay attention to the `button_clicked` function.  This function
120    /// implementation is **optional**.
121    fn button_clicked(
122        &mut self,
123        _widgets: &[WidgetContainer],
124        _layouts: &[LayoutContainer],
125        _button: u8,
126        _clicks: u8,
127        _state: bool,
128    ) {
129        self.button_clicked_callback(_widgets, _layouts, _button, _clicks, _state);
130    }
131
132    /// When a timer tick goes by (ie. a frame is displayed on the screen), this function is
133    /// called.  This function implementation is **optional**.
134    fn tick(&mut self, _widgets: &[WidgetContainer], _layouts: &[LayoutContainer]) {
135        self.tick_callback(_widgets, _layouts);
136    }
137
138    /// When an `Event` is sent to the application that is not handled by the `Engine::run` loop, this
139    /// method is called, sending the unhandled `Event` to the currently active `Widget`.  **This behavior
140    /// is subject to change** as the `Engine::run` loop is modified to handle more `Event`s.
141    fn other_event(
142        &mut self,
143        _widgets: &[WidgetContainer],
144        _layouts: &[LayoutContainer],
145        _event: Event,
146    ) {
147        eprintln!("Other event: {:?}", _event);
148    }
149
150    /// This calls the `on_tick` callback.  This is implemented by the `default_widget_callbacks!` macro,
151    /// so you do not need to implement it.  However, you need to call this function if you wish
152    /// to honor an `on_tick` callback.
153    fn tick_callback(&mut self, _widgets: &[WidgetContainer], _layouts: &[LayoutContainer]) {}
154
155    /// This calls the `on_mouse_entered` callback.  This is implemented by the `default_widget_callbacks!` macro,
156    /// so you do not need to implement it.  However, you need to call this function if you wish
157    /// to honor an `on_mouse_entered` callback.
158    fn mouse_entered_callback(
159        &mut self,
160        _widgets: &[WidgetContainer],
161        _layouts: &[LayoutContainer],
162    ) {
163    }
164
165    /// This calls the `on_mouse_exited` callback.  This is implemented by the `default_widget_callbacks!` macro,
166    /// so you do not need to implement it.  However, you need to call this function if you wish
167    /// to honor an `on_mouse_exited` callback.
168    fn mouse_exited_callback(
169        &mut self,
170        _widgets: &[WidgetContainer],
171        _layouts: &[LayoutContainer],
172    ) {
173    }
174
175    /// This calls the `on_mouse_moved` callback.  This is implemented by the `default_widget_callbacks!` macro,
176    /// so you do not need to implement it.  However, you need to call this function if you wish
177    /// to honor an `on_mouse_moved` callback.
178    fn mouse_moved_callback(
179        &mut self,
180        _widgets: &[WidgetContainer],
181        _layouts: &[LayoutContainer],
182        _points: Points,
183    ) {
184    }
185
186    /// This calls the `on_mouse_scrolled` callback.  This is implemented by the `default_widget_callbacks!` macro,
187    /// so you do not need to implement it.  However, you need to call this function if you wish
188    /// to honor an `on_mouse_scrolled` callback.
189    fn mouse_scrolled_callback(
190        &mut self,
191        _widgets: &[WidgetContainer],
192        _layouts: &[LayoutContainer],
193        _points: Points,
194    ) {
195    }
196
197    /// This calls the `on_button_clicked` callback.  This is implemented by the `default_widget_callbacks!` macro,
198    /// so you do not need to implement it.  However, you need to call this function if you wish
199    /// to honor an `on_button_clicked` callback.
200    fn button_clicked_callback(
201        &mut self,
202        _widgets: &[WidgetContainer],
203        _layouts: &[LayoutContainer],
204        _button: u8,
205        _clicks: u8,
206        _state: bool,
207    ) {
208    }
209
210    /// This callback is called when a setter is used to configure a value.  It is _not_ called when a
211    /// call to `get_config()` using the setter is called, so it is best to use the top-level setters
212    /// and getters for the configuration values - at least, until the `get_config()` call can be made
213    /// private.
214    fn on_config_changed(&mut self, _k: u8, _v: Config) {}
215
216    /// Sets a point for a configuration key.
217    fn set_point(&mut self, config: u8, x: i32, y: i32) {
218        self.get_config().set_point(config, x, y);
219        self.on_config_changed(config, Config::Points(vec![x, y]));
220    }
221
222    /// Sets a color for a configuration key.
223    fn set_color(&mut self, config: u8, color: Color) {
224        self.get_config().set_color(config, color);
225        self.on_config_changed(config, Config::Color(color));
226    }
227
228    /// Sets a numeric value for a configuration key.
229    fn set_numeric(&mut self, config: u8, value: i32) {
230        self.get_config().set_numeric(config, value);
231        self.on_config_changed(config, Config::Numeric(value));
232    }
233
234    /// Sets a text value for a configuration key.
235    fn set_text(&mut self, config: u8, text: String) {
236        self.get_config().set_text(config, text.clone());
237        self.on_config_changed(config, Config::Text(text));
238    }
239
240    /// Sets a toggle for a configuration key.
241    fn set_toggle(&mut self, config: u8, flag: bool) {
242        self.get_config().set_toggle(config, flag);
243        self.on_config_changed(config, Config::Toggle(flag));
244    }
245
246    /// Sets a compass position for a configuration key.
247    fn set_compass(&mut self, config: u8, value: CompassPosition) {
248        self.get_config().set_compass(config, value);
249        self.on_config_changed(config, Config::CompassPosition(value));
250    }
251
252    /// Retrieves a `Points` for a configuration key.  Returns `Points::default` if not set.
253    fn get_point(&mut self, k: u8) -> Points {
254        self.get_config().get_point(k)
255    }
256
257    /// Retrieves a `Size` for a configuration key.  Returns a `Size::default` if not set.
258    fn get_size(&mut self, k: u8) -> Size {
259        self.get_config().get_size(k)
260    }
261
262    /// Retrieves a `Color` for a configuration key.  Returns white if not set.
263    fn get_color(&mut self, k: u8) -> Color {
264        self.get_config().get_color(k)
265    }
266
267    /// Retrieves a numeric value for a configuration key.  Returns 0 if not set.
268    fn get_numeric(&mut self, k: u8) -> i32 {
269        self.get_config().get_numeric(k)
270    }
271
272    /// Retrieves text for a configuration key.  Returns a blank string if not set.
273    fn get_text(&mut self, k: u8) -> String {
274        self.get_config().get_text(k)
275    }
276
277    /// Retrieves a boolean toggle for a configuration key.  Returns `false` if not set.
278    fn get_toggle(&mut self, k: u8) -> bool {
279        self.get_config().get_toggle(k)
280    }
281
282    /// Retrieves a `CompassPosition` toggle for a configuration key.  Returns `CompassPosition::W` if not set.
283    fn get_compass(&mut self, k: u8) -> CompassPosition {
284        self.get_config().get_compass(k)
285    }
286
287    /// Sets the origin of the `Widget`, adjusting the X and Y coordinates.  Automatically sets the
288    /// `invalidate` flag to `true` when adjusted, but only if the new origin is not the same as
289    /// the previous origin.
290    fn set_origin(&mut self, _origin: Points) {
291        let old_origin = self.get_config().get_point(CONFIG_ORIGIN);
292
293        if _origin[0] != old_origin[0] || _origin[1] != old_origin[1] {
294            self.get_config()
295                .set_point(CONFIG_ORIGIN, _origin[0], _origin[1]);
296            self.get_config().set_invalidated(true);
297        }
298    }
299
300    /// Sets the size of the `Widget`, adjusting the width and height.  Automatically
301    /// sets the `invalidate` flag to `true` when adjusted, but only if the new size is not the
302    /// same as the previous size.
303    fn set_size(&mut self, _size: Vec<u32>) {
304        let old_size = self.get_config().get_size(CONFIG_SIZE);
305
306        if _size[0] != old_size[0] || _size[1] != old_size[1] {
307            self.get_config().set_size(CONFIG_SIZE, _size[0], _size[1]);
308            self.get_config().set_invalidated(true);
309        }
310    }
311
312    /// Returns a `Rect` object containing the drawing bounds of this `Widget`.
313    fn get_drawing_area(&mut self) -> Rect {
314        Rect::new(
315            self.get_config().to_x(0),
316            self.get_config().to_y(0),
317            self.get_config().get_size(CONFIG_SIZE)[0],
318            self.get_config().get_size(CONFIG_SIZE)[1],
319        )
320    }
321
322    /// Returns whether or not a `Widget` is invalidated state.
323    fn is_invalidated(&mut self) -> bool {
324        self.get_config().invalidated()
325    }
326
327    /// Sets invalidation state for the current `Widget`.
328    fn set_invalidated(&mut self, flag: bool) {
329        self.get_config().set_invalidated(flag);
330    }
331}
332
333/// This is an example top-level `Widget` object that is used to draw a background and a border
334/// of specified colors.  `COLOR_BASE` determines the background fill color, and the `COLOR_BORDER`
335/// determines the color of the border.  The width of the border is controlled by the
336/// `get_config().border_width` property.
337pub struct BaseWidget {
338    config: WidgetConfig,
339    system_properties: HashMap<i32, String>,
340    callback_registry: CallbackRegistry,
341    texture_store: TextureStore,
342}
343
344/// Base top-level implementation of the `BaseWidget`, which other classes can extend.
345impl BaseWidget {
346    /// Constructs a new base widget, given the points of origin and size.
347    pub fn new(points: Points, size: Size) -> Self {
348        Self {
349            config: WidgetConfig::new(points, size),
350            system_properties: HashMap::new(),
351            callback_registry: CallbackRegistry::new(),
352            texture_store: TextureStore::default(),
353        }
354    }
355}
356
357/// Implementation for drawing a `BaseWidget`, with the `Widget` trait objects applied.
358/// This code can be used as a base implementation, or an example of how to create a `Widget` in
359/// `Pushrod`.  The base set of `Widget`s show off a multitude of different uses for handling events,
360/// display contents, and so on.  Look through the code in the `pushrod::widgets` module to get
361/// more of an idea of what is possible.
362impl Widget for BaseWidget {
363    fn draw(&mut self, c: &mut Canvas<Window>, _t: &mut TextureCache) -> Option<&Texture> {
364        // You _can_ remove this `if` statement here, and just let the code run each time.  It will
365        // eventually make your application less efficient if this is constantly called.
366        if self.get_config().invalidated() {
367            let bounds = self.get_config().get_size(CONFIG_SIZE);
368
369            self.texture_store
370                .create_or_resize_texture(c, bounds[0] as u32, bounds[1] as u32);
371
372            let base_color = self.get_config().get_color(CONFIG_COLOR_BASE);
373            let border_color = self.get_config().get_color(CONFIG_COLOR_BORDER);
374
375            c.with_texture_canvas(self.texture_store.get_mut_ref(), |texture| {
376                texture.set_draw_color(base_color);
377                texture.clear();
378
379                texture.set_draw_color(border_color);
380                texture
381                    .draw_rect(Rect::new(0, 0, bounds[0], bounds[1]))
382                    .unwrap();
383            })
384            .unwrap();
385        }
386
387        self.texture_store.get_optional_ref()
388    }
389
390    default_widget_functions!();
391    default_widget_properties!();
392    default_widget_callbacks!();
393}