Skip to main content

pushrod/widgets/
toggle_button_widget.rs

1// Pushrod Widget Library
2// Toggle Button 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    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_toggle` callback is triggered from this
37/// `Widget`.
38pub type OnToggleCallbackType =
39    Option<Box<dyn FnMut(&mut ToggleButtonWidget, &[WidgetContainer], &[LayoutContainer], bool)>>;
40
41/// This is the storage object for the `ToggleButtonWidget`.  It stores the config, properties, callback registry.
42pub struct ToggleButtonWidget {
43    config: WidgetConfig,
44    system_properties: HashMap<i32, String>,
45    callback_registry: CallbackRegistry,
46    texture_store: TextureStore,
47    base_widget: BaseWidget,
48    text_widget: TextWidget,
49    active: bool,
50    selected: bool,
51    in_bounds: bool,
52    originated: bool,
53    on_toggle: OnToggleCallbackType,
54}
55
56/// This is the implementation of the `ToggleButtonWidget` that draws a button on the screen that can be
57/// toggled on or off.
58impl ToggleButtonWidget {
59    /// Creates a new `ToggleButtonWidget` given the `x, y, w, h` coordinates, the `text` to display
60    /// inside the button, `font_size` of the font to display, and the initial `selected` state: `true`
61    /// being selected, `false` otherwise.
62    pub fn new(points: Points, size: Size, text: String, font_size: i32, selected: bool) -> Self {
63        let mut base_widget = BaseWidget::new(points.clone(), size.clone());
64        let mut text_widget = TextWidget::new(
65            String::from("assets/OpenSans-Regular.ttf"),
66            sdl2::ttf::FontStyle::NORMAL,
67            font_size,
68            TextJustify::Center,
69            text,
70            make_points(points[POINT_X] + 2, points[POINT_Y] + 2),
71            make_size(size[SIZE_WIDTH] - 4, size[SIZE_HEIGHT] - 4),
72        );
73
74        let base_color = if selected {
75            Color::RGB(0, 0, 0)
76        } else {
77            Color::RGB(255, 255, 255)
78        };
79        let text_color = if selected {
80            Color::RGB(255, 255, 255)
81        } else {
82            Color::RGB(0, 0, 0)
83        };
84
85        let mut config = WidgetConfig::new(points, size);
86
87        base_widget.set_color(CONFIG_COLOR_BASE, base_color);
88        base_widget.set_color(CONFIG_COLOR_BORDER, Color::RGB(0, 0, 0));
89        base_widget.set_numeric(CONFIG_BORDER_WIDTH, 2);
90
91        text_widget.set_color(CONFIG_COLOR_BASE, base_color);
92        text_widget.set_color(CONFIG_COLOR_TEXT, text_color);
93
94        config.set_toggle(CONFIG_SELECTED_STATE, selected);
95
96        Self {
97            config,
98            system_properties: HashMap::new(),
99            callback_registry: CallbackRegistry::new(),
100            texture_store: TextureStore::default(),
101            base_widget,
102            text_widget,
103            active: false,
104            selected,
105            in_bounds: false,
106            originated: false,
107            on_toggle: None,
108        }
109    }
110
111    /// Sets the selected state of this `Widget`.
112    pub fn set_selected(&mut self, selected: bool) {
113        self.selected = selected;
114        self.get_config().set_invalidated(true);
115    }
116
117    /// Returns the selected state of this `Widget`: `true` indicates selected, `false` otherwise.
118    pub fn is_selected(&self) -> bool {
119        self.selected
120    }
121
122    /// Draws the state when the mouse is over the top of the `Widget`.
123    fn draw_hovered(&mut self) {
124        let base_color = if self.selected {
125            Color::RGB(255, 255, 255)
126        } else {
127            Color::RGB(0, 0, 0)
128        };
129        let text_color = if self.selected {
130            Color::RGB(0, 0, 0)
131        } else {
132            Color::RGB(255, 255, 255)
133        };
134
135        self.base_widget.set_color(CONFIG_COLOR_BASE, base_color);
136        self.text_widget.set_color(CONFIG_COLOR_TEXT, text_color);
137        self.text_widget.set_color(CONFIG_COLOR_BASE, base_color);
138        self.get_config().set_invalidated(true);
139    }
140
141    /// Draws the state when the mouse leaves the scope of the `Widget`.
142    fn draw_unhovered(&mut self) {
143        let base_color = if self.selected {
144            Color::RGB(0, 0, 0)
145        } else {
146            Color::RGB(255, 255, 255)
147        };
148        let text_color = if self.selected {
149            Color::RGB(255, 255, 255)
150        } else {
151            Color::RGB(0, 0, 0)
152        };
153
154        self.base_widget.set_color(CONFIG_COLOR_BASE, base_color);
155        self.text_widget.set_color(CONFIG_COLOR_TEXT, text_color);
156        self.text_widget.set_color(CONFIG_COLOR_BASE, base_color);
157        self.get_config().set_invalidated(true);
158    }
159
160    /// Assigns the callback closure that will be used when the `Widget` toggles state.
161    pub fn on_toggle<F>(&mut self, callback: F)
162    where
163        F: FnMut(&mut ToggleButtonWidget, &[WidgetContainer], &[LayoutContainer], bool) + 'static,
164    {
165        self.on_toggle = Some(Box::new(callback));
166    }
167
168    /// Internal function that triggers the `on_toggle` callback.
169    fn call_toggle_callback(&mut self, widgets: &[WidgetContainer], layouts: &[LayoutContainer]) {
170        if let Some(mut cb) = self.on_toggle.take() {
171            cb(self, widgets, layouts, self.selected);
172            self.on_toggle = Some(cb);
173        }
174    }
175}
176
177/// This is the `Widget` implementation of the `ToggleButtonWidget`.
178impl Widget for ToggleButtonWidget {
179    /// Draws the `ToggleButtonWidget` contents.
180    fn draw(&mut self, c: &mut Canvas<Window>, t: &mut TextureCache) -> Option<&Texture> {
181        if self.get_config().invalidated() {
182            let bounds = self.get_config().get_size(CONFIG_SIZE);
183            let base_color = self.get_color(CONFIG_COLOR_BASE);
184
185            self.texture_store
186                .create_or_resize_texture(c, bounds[0] as u32, bounds[1] as u32);
187
188            // Paint the base widget first.  Forcing a draw() call here will ignore invalidation.
189            // Invalidation is controlled by the top level widget (this box).
190            let base_widget_texture = self.base_widget.draw(c, t).unwrap();
191            let text_widget_texture = self.text_widget.draw(c, t).unwrap();
192
193            c.with_texture_canvas(self.texture_store.get_mut_ref(), |texture| {
194                texture.set_draw_color(base_color);
195                texture.clear();
196
197                texture
198                    .copy(
199                        base_widget_texture,
200                        None,
201                        Rect::new(0, 0, bounds[0], bounds[1]),
202                    )
203                    .unwrap();
204
205                texture
206                    .copy(
207                        text_widget_texture,
208                        None,
209                        Rect::new(2, 2, bounds[0] - 4, bounds[1] - 4),
210                    )
211                    .unwrap();
212            })
213            .unwrap();
214        }
215
216        self.texture_store.get_optional_ref()
217    }
218
219    /// When a mouse enters the bounds of the `Widget`, this function is triggered.
220    fn mouse_entered(&mut self, _widgets: &[WidgetContainer], _layouts: &[LayoutContainer]) {
221        if self.active {
222            self.draw_hovered();
223        }
224
225        self.in_bounds = true;
226        self.mouse_entered_callback(_widgets, _layouts);
227    }
228
229    /// When a mouse exits the bounds of the `Widget`, this function is triggered.
230    fn mouse_exited(&mut self, _widgets: &[WidgetContainer], _layouts: &[LayoutContainer]) {
231        if self.active {
232            self.draw_unhovered();
233        }
234
235        self.in_bounds = false;
236        self.mouse_exited_callback(_widgets, _layouts);
237    }
238
239    /// Overrides the `button_clicked` callback to handle toggling.
240    fn button_clicked(
241        &mut self,
242        _widgets: &[WidgetContainer],
243        _layouts: &[LayoutContainer],
244        _button: u8,
245        _clicks: u8,
246        _state: bool,
247    ) {
248        if _button == 1 {
249            if _state {
250                self.draw_hovered();
251                self.active = true;
252                self.originated = true;
253            } else {
254                self.active = false;
255
256                if self.in_bounds && self.originated {
257                    self.selected = !self.selected;
258                    self.set_toggle(CONFIG_SELECTED_STATE, self.selected);
259                    self.call_toggle_callback(_widgets, _layouts);
260                }
261
262                self.originated = false;
263            }
264        }
265
266        self.button_clicked_callback(_widgets, _layouts, _button, _clicks, _state);
267    }
268
269    default_widget_functions!();
270    default_widget_properties!();
271    default_widget_callbacks!();
272}