pushrod/widgets/
text_widget.rs

1// Pushrod Widget Library
2// Text 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::layout_cache::LayoutContainer;
18use crate::render::widget::*;
19use crate::render::widget_cache::WidgetContainer;
20use crate::render::widget_config::*;
21use crate::render::{Points, Size};
22
23use sdl2::render::{Canvas, Texture, TextureQuery};
24use sdl2::ttf::FontStyle;
25use sdl2::video::Window;
26
27use crate::render::texture_cache::TextureCache;
28use crate::render::texture_store::TextureStore;
29use sdl2::rect::Rect;
30use std::any::Any;
31use std::collections::HashMap;
32use std::path::Path;
33
34/// This enum is used by the `TextWidget`, which controls the justification of the text being
35/// rendered within the bounds of the `Widget`.
36pub enum TextJustify {
37    /// Left-justified text.
38    Left,
39
40    /// Center-justified text: `(total width - text width) / 2`
41    Center,
42
43    /// Right-justified text: `(total width - text width)`
44    Right,
45}
46
47/// This is the storage object for the `TextWidget`.  It stores the config, properties, callback registry,
48/// the font name, style, size, justification, and text message.
49pub struct TextWidget {
50    config: WidgetConfig,
51    system_properties: HashMap<i32, String>,
52    callback_registry: CallbackRegistry,
53    texture_store: TextureStore,
54    font_name: String,
55    font_style: FontStyle,
56    font_size: i32,
57    justification: TextJustify,
58    msg: String,
59}
60
61/// Creates a new `TextWidget`, which draws a unit of text on the screen, given the specified font,
62/// size, justification, and layout coordinates.
63impl TextWidget {
64    /// Creates a new `TextWidget` object.  Requires the name of the font (the path to the font file),
65    /// the style of font (`sdl2::ttf::FontStyle`), the size in pixels of the font, the `TextJustify`
66    /// layout of the font, the message to display, and the x, y, w, h coordinates of the text.
67    pub fn new(
68        font_name: String,
69        font_style: FontStyle,
70        font_size: i32,
71        justification: TextJustify,
72        msg: String,
73        points: Points,
74        size: Size,
75    ) -> Self {
76        Self {
77            config: WidgetConfig::new(points, size),
78            system_properties: HashMap::new(),
79            callback_registry: CallbackRegistry::new(),
80            texture_store: TextureStore::default(),
81            font_name,
82            font_style,
83            font_size,
84            justification,
85            msg,
86        }
87    }
88
89    /// Changes the text displayed in the body of the `Widget`.
90    pub fn set_text(&mut self, msg: String) {
91        self.msg = msg;
92        self.get_config().set_invalidated(true);
93    }
94
95    /// Retrieves the text currently being displayed in the `TextWidget`.
96    pub fn get_text(&self) -> String {
97        self.msg.clone()
98    }
99}
100
101/// This is the `Widget` implementation of the `TextWidget`.  Text is rendered onto a 3D texture, then
102/// copied to the canvas after rendering.  It uses blended mode texture mapping, which may be slow (as
103/// described by the SDL2 documentation), so this might change later to use 8 bit color mapping.
104impl Widget for TextWidget {
105    fn draw(&mut self, c: &mut Canvas<Window>, t: &mut TextureCache) -> Option<&Texture> {
106        if self.get_config().invalidated() {
107            let bounds = self.get_config().get_size(CONFIG_SIZE);
108
109            self.texture_store
110                .create_or_resize_texture(c, bounds[0] as u32, bounds[1] as u32);
111
112            let base_color = self.get_color(CONFIG_COLOR_BASE);
113            let text_max_width = self.get_size(CONFIG_SIZE)[0]
114                - ((self.get_numeric(CONFIG_BORDER_WIDTH) * 2) as u32);
115
116            let ttf_context = t.get_ttf_context();
117            let texture_creator = c.texture_creator();
118            let mut font = ttf_context
119                .load_font(Path::new(&self.font_name), self.font_size as u16)
120                .unwrap();
121            let font_color = self.get_color(CONFIG_COLOR_TEXT);
122
123            font.set_style(self.font_style);
124
125            let surface = font
126                .render(&self.msg)
127                .blended_wrapped(font_color, text_max_width)
128                .map_err(|e| e.to_string())
129                .unwrap();
130            let font_texture = texture_creator
131                .create_texture_from_surface(&surface)
132                .map_err(|e| e.to_string())
133                .unwrap();
134
135            let TextureQuery { width, height, .. } = font_texture.query();
136            let texture_y = 0;
137            let widget_w = self.get_size(CONFIG_SIZE)[0] as i32;
138            let texture_x = match self.justification {
139                TextJustify::Left => 0,
140                TextJustify::Right => widget_w - width as i32,
141                TextJustify::Center => (widget_w - width as i32) / 2,
142            };
143
144            c.with_texture_canvas(self.texture_store.get_mut_ref(), |texture| {
145                texture.set_draw_color(base_color);
146                texture.clear();
147
148                texture
149                    .copy(
150                        &font_texture,
151                        None,
152                        Rect::new(texture_x, texture_y, width, height),
153                    )
154                    .unwrap();
155            })
156            .unwrap();
157        }
158
159        self.texture_store.get_optional_ref()
160    }
161
162    /// Monitors for changes in the text, color changes, or font sizes.
163    fn on_config_changed(&mut self, _k: u8, _v: Config) {
164        match _k {
165            CONFIG_COLOR_TEXT => self.get_config().set_invalidated(true),
166            CONFIG_COLOR_BASE => self.get_config().set_invalidated(true),
167            CONFIG_FONT_SIZE => {
168                if let Config::Numeric(size) = _v {
169                    self.font_size = size;
170                    self.get_config().set_invalidated(true);
171                }
172            }
173            CONFIG_TEXT => {
174                if let Config::Text(text) = _v {
175                    self.msg = text;
176                    self.get_config().set_invalidated(true);
177                }
178            }
179
180            _ => (),
181        };
182    }
183
184    default_widget_functions!();
185    default_widget_properties!();
186    default_widget_callbacks!();
187}