pushrod/widgets/
tile_widget.rs

1// Pushrod Widget Library
2// Tile Widget
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 crate::render::callbacks::CallbackRegistry;
17use crate::render::widget::*;
18use crate::render::widget_cache::WidgetContainer;
19use crate::render::widget_config::*;
20use crate::render::{
21    inverse_color, make_points, make_size, Points, Size, POINT_X, POINT_Y, SIZE_HEIGHT, SIZE_WIDTH,
22};
23
24use sdl2::render::{Canvas, Texture};
25use sdl2::video::Window;
26
27use crate::render::layout_cache::LayoutContainer;
28use crate::render::texture_cache::TextureCache;
29use crate::render::texture_store::TextureStore;
30use crate::widgets::text_widget::{TextJustify, TextWidget};
31use sdl2::pixels::Color;
32use sdl2::rect::Rect;
33use std::any::Any;
34use std::collections::HashMap;
35
36/// This is the callback type that is used when an `on_click` callback is triggered from this
37/// `Widget`.  Returns a flag indicating the selected state - toggled on or off.
38pub type OnClickedCallbackType =
39    Option<Box<dyn FnMut(&mut TileWidget, &[WidgetContainer], &[LayoutContainer], bool)>>;
40
41/// This is the storage object for the `TileWidget`.  It stores the config, properties, callback registry.
42pub struct TileWidget {
43    config: WidgetConfig,
44    system_properties: HashMap<i32, String>,
45    callback_registry: CallbackRegistry,
46    texture_store: TextureStore,
47    on_click: OnClickedCallbackType,
48    base_widget: BaseWidget,
49    text_widget: TextWidget,
50    image_name: String,
51    selected: bool,
52    hovered: bool,
53    originated: bool,
54}
55
56/// This is the implementation of the `TileWidget`, which displays an image next to some text.
57impl TileWidget {
58    /// Creates a new `TileWidget`, given the `x, y, w, h` coordinates, the `image_name` to load and
59    /// display, and the text to show in the tile.
60    pub fn new(points: Points, size: Size, image_name: String, tile_text: String) -> Self {
61        let mut base_widget = BaseWidget::new(points.clone(), size.clone());
62        let mut text_widget = TextWidget::new(
63            String::from("assets/OpenSans-Regular.ttf"),
64            sdl2::ttf::FontStyle::NORMAL,
65            14,
66            TextJustify::Center,
67            tile_text,
68            make_points(
69                points[POINT_X] + 1,
70                points[POINT_Y] + size[SIZE_HEIGHT] as i32 - 19,
71            ),
72            make_size(size[SIZE_WIDTH] - 2, 18),
73        );
74
75        base_widget.set_color(CONFIG_COLOR_BORDER, Color::RGB(0, 0, 0));
76        base_widget.set_numeric(CONFIG_BORDER_WIDTH, 1);
77        base_widget.set_color(CONFIG_COLOR_BASE, Color::RGB(255, 255, 255));
78        text_widget.set_color(CONFIG_COLOR_BASE, Color::RGBA(255, 255, 255, 255));
79        text_widget.set_color(CONFIG_COLOR_TEXT, Color::RGB(0, 0, 0));
80
81        Self {
82            config: WidgetConfig::new(points, size),
83            system_properties: HashMap::new(),
84            callback_registry: CallbackRegistry::new(),
85            texture_store: TextureStore::default(),
86            on_click: None,
87            base_widget,
88            text_widget,
89            image_name,
90            selected: false,
91            hovered: false,
92            originated: false,
93        }
94    }
95
96    /// Assigns the callback closure that will be used when a button click is triggered.
97    pub fn on_click<F>(&mut self, callback: F)
98    where
99        F: FnMut(&mut TileWidget, &[WidgetContainer], &[LayoutContainer], bool) + 'static,
100    {
101        self.on_click = Some(Box::new(callback));
102    }
103
104    /// Internal function that triggers the `on_click` callback.
105    fn call_click_callback(
106        &mut self,
107        widgets: &[WidgetContainer],
108        layouts: &[LayoutContainer],
109        state: bool,
110    ) {
111        if let Some(mut cb) = self.on_click.take() {
112            cb(self, widgets, layouts, state);
113            self.on_click = Some(cb);
114        }
115    }
116
117    fn adjust_widgets(&mut self) {
118        if self.selected {
119            let selected_color = self.get_color(CONFIG_COLOR_SELECTED);
120            self.base_widget
121                .set_color(CONFIG_COLOR_BASE, selected_color);
122            self.text_widget
123                .set_color(CONFIG_COLOR_BASE, selected_color);
124            self.text_widget
125                .set_color(CONFIG_COLOR_TEXT, inverse_color(selected_color));
126        } else if self.hovered {
127            let hover_color = self.get_color(CONFIG_COLOR_HOVER);
128            self.base_widget.set_color(CONFIG_COLOR_BASE, hover_color);
129            self.text_widget.set_color(CONFIG_COLOR_BASE, hover_color);
130            self.text_widget
131                .set_color(CONFIG_COLOR_TEXT, inverse_color(hover_color));
132        } else {
133            self.base_widget
134                .set_color(CONFIG_COLOR_BASE, Color::RGB(255, 255, 255));
135            self.text_widget
136                .set_color(CONFIG_COLOR_BASE, Color::RGB(255, 255, 255));
137            self.text_widget
138                .set_color(CONFIG_COLOR_TEXT, Color::RGB(0, 0, 0));
139        }
140    }
141}
142
143/// This is the `Widget` implementation of the `TileWidget`.
144impl Widget for TileWidget {
145    fn draw(&mut self, c: &mut Canvas<Window>, t: &mut TextureCache) -> Option<&Texture> {
146        if self.get_config().invalidated() {
147            let bounds = self.get_config().get_size(CONFIG_SIZE);
148            let base_color = self.get_color(CONFIG_COLOR_BASE);
149
150            self.texture_store
151                .create_or_resize_texture(c, bounds[0] as u32, bounds[1] as u32);
152
153            self.adjust_widgets();
154
155            let base_widget_texture = self.base_widget.draw(c, t).unwrap();
156            let text_widget_texture = self.text_widget.draw(c, t).unwrap();
157            let image_texture = t.get_image(c, self.image_name.clone());
158
159            c.with_texture_canvas(self.texture_store.get_mut_ref(), |texture| {
160                texture.set_draw_color(base_color);
161                texture.clear();
162
163                let size_center = bounds[SIZE_WIDTH] / 2;
164
165                texture
166                    .copy(
167                        base_widget_texture,
168                        None,
169                        Rect::new(0, 0, bounds[0], bounds[1]),
170                    )
171                    .unwrap();
172
173                texture
174                    .copy(
175                        text_widget_texture,
176                        None,
177                        Rect::new(
178                            1,
179                            bounds[SIZE_HEIGHT] as i32 - 20,
180                            bounds[SIZE_WIDTH] - 2,
181                            18,
182                        ),
183                    )
184                    .unwrap();
185
186                texture
187                    .copy(
188                        image_texture,
189                        None,
190                        Rect::new(
191                            (size_center - 16) as i32,
192                            (bounds[SIZE_HEIGHT] / 2 - 32) as i32,
193                            32,
194                            32,
195                        ),
196                    )
197                    .unwrap();
198            })
199            .unwrap();
200        }
201
202        self.texture_store.get_optional_ref()
203    }
204
205    /// When a mouse enters the bounds of the `Widget`, this function is triggered.  This function
206    /// implementation is **optional**.
207    fn mouse_entered(&mut self, _widgets: &[WidgetContainer], _layouts: &[LayoutContainer]) {
208        self.hovered = true;
209        self.get_config().set_invalidated(true);
210    }
211
212    /// When a mouse exits the bounds of the `Widget`, this function is triggered.  This function
213    /// implementation is **optional**.
214    fn mouse_exited(&mut self, _widgets: &[WidgetContainer], _layouts: &[LayoutContainer]) {
215        self.hovered = false;
216        self.get_config().set_invalidated(true);
217    }
218
219    fn button_clicked(
220        &mut self,
221        _widgets: &[WidgetContainer],
222        _layouts: &[LayoutContainer],
223        _button: u8,
224        _clicks: u8,
225        _state: bool,
226    ) {
227        if _button == 1 {
228            if _state {
229                self.originated = true;
230            } else {
231                if self.originated && self.hovered {
232                    self.selected = !self.selected;
233                    self.call_click_callback(_widgets, _layouts, self.selected);
234                }
235
236                self.originated = false;
237            }
238        }
239    }
240
241    default_widget_functions!();
242    default_widget_properties!();
243    default_widget_callbacks!();
244}