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}