maycoon_widgets/
text.rs

1use maycoon_core::app::context::AppContext;
2use maycoon_core::app::info::AppInfo;
3use maycoon_core::app::update::Update;
4use maycoon_core::layout::{Dimension, LayoutNode, LayoutStyle, StyleNode};
5use maycoon_core::signal::MaybeSignal;
6use maycoon_core::vgi::{Brush, Scene};
7use maycoon_core::widget::{Widget, WidgetLayoutExt};
8use maycoon_theme::id::WidgetId;
9use maycoon_theme::theme::Theme;
10use nalgebra::Vector2;
11use std::ops::Deref;
12
13/// Displays the given text with optional font, size and hinting.
14///
15/// See the [hello-world](https://github.com/maycoon-ui/maycoon/blob/master/examples/hello-world/src/main.rs) example for how to use it in practice.
16///
17/// ### Theming
18/// You can style the text with the following properties:
19/// - `color` - The color of the text.
20/// - `color_invert` - The color to use when the `invert_color` property is set to `true` in the theme [Globals].
21///
22/// [Globals]: maycoon_theme::globals::Globals
23pub struct Text {
24    style: MaybeSignal<LayoutStyle>,
25    text: MaybeSignal<String>,
26    font: MaybeSignal<Option<String>>,
27    font_size: MaybeSignal<f32>,
28    hinting: MaybeSignal<bool>,
29    line_gap: MaybeSignal<f32>,
30}
31
32impl Text {
33    /// Create a new text widget with the given text.
34    pub fn new(text: impl Into<MaybeSignal<String>>) -> Self {
35        Self {
36            style: LayoutStyle::default().into(),
37            text: text.into(),
38            font: None.into(),
39            font_size: 30.0.into(),
40            hinting: true.into(),
41            line_gap: 7.5.into(),
42        }
43    }
44
45    /// Set the hinting of the text.
46    ///
47    /// Hinting adjusts the display of an outline font so that it lines up with a rasterized grid.
48    /// At low screen resolutions and font size, hinting can produce clearer text.
49    pub fn with_hinting(mut self, hinting: impl Into<MaybeSignal<bool>>) -> Self {
50        self.hinting = hinting.into();
51        self
52    }
53
54    /// Set the font of the text.
55    pub fn with_font(mut self, font: impl Into<MaybeSignal<Option<String>>>) -> Self {
56        self.font = font.into();
57        self
58    }
59
60    /// Set the font size of the text.
61    pub fn with_font_size(mut self, size: impl Into<MaybeSignal<f32>>) -> Self {
62        self.font_size = size.into();
63        self
64    }
65
66    /// Set the line gap of the text.
67    ///
68    /// The line gap is the space between lines of text. Defaults to `7.5`.
69    pub fn with_line_gap(mut self, gap: impl Into<MaybeSignal<f32>>) -> Self {
70        self.line_gap = gap.into();
71        self
72    }
73}
74
75impl WidgetLayoutExt for Text {
76    fn set_layout_style(&mut self, layout_style: impl Into<MaybeSignal<LayoutStyle>>) {
77        self.style = layout_style.into();
78    }
79}
80
81impl Widget for Text {
82    fn render(
83        &mut self,
84        scene: &mut dyn Scene,
85        theme: &mut dyn Theme,
86        layout_node: &LayoutNode,
87        info: &AppInfo,
88        _: AppContext,
89    ) {
90        let font_size = *self.font_size.get();
91        let hinting = *self.hinting.get();
92
93        let font_name = self.font.get();
94
95        let font = if font_name.is_some() {
96            info.font_context
97                .get(font_name.deref().clone().unwrap())
98                .expect("Font not found")
99        } else {
100            info.font_context.default_font().clone()
101        };
102
103        let color = if let Some(style) = theme.of(Self::widget_id(self)) {
104            if theme.globals().invert_text_color {
105                style.get_color("color_invert").unwrap()
106            } else {
107                style.get_color("color").unwrap()
108            }
109        } else {
110            theme.defaults().text().foreground()
111        };
112
113        let line_gap = *self.line_gap.get();
114
115        let text = self.text.get();
116
117        scene.draw_text(
118            &Brush::Solid(color),
119            None,
120            Vector2::new(layout_node.layout.location.x, layout_node.layout.location.y),
121            text.as_str(),
122            hinting,
123            &font,
124            font_size,
125            line_gap,
126        );
127    }
128
129    fn layout_style(&self) -> StyleNode {
130        let text = self.text.get();
131
132        let font_size = *self.font_size.get();
133
134        let style = self.style.get().deref().clone();
135
136        StyleNode {
137            style: LayoutStyle {
138                size: Vector2::new(
139                    Dimension::length(font_size * text.len() as f32),
140                    Dimension::length(font_size),
141                ),
142                ..style
143            },
144            children: Vec::new(),
145        }
146    }
147
148    fn update(&mut self, _: &LayoutNode, _: AppContext, _: &AppInfo) -> Update {
149        Update::empty()
150    }
151
152    fn widget_id(&self) -> WidgetId {
153        WidgetId::new("maycoon-widgets", "Text")
154    }
155}