iced_native/widget/
text.rs

1//! Write some text for your users to read.
2use crate::alignment;
3use crate::layout;
4use crate::renderer;
5use crate::text;
6use crate::widget::Tree;
7use crate::{Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget};
8
9use std::borrow::Cow;
10
11pub use iced_style::text::{Appearance, StyleSheet};
12
13/// A paragraph of text.
14///
15/// # Example
16///
17/// ```
18/// # use iced_native::Color;
19/// #
20/// # type Text<'a> = iced_native::widget::Text<'a, iced_native::renderer::Null>;
21/// #
22/// Text::new("I <3 iced!")
23///     .size(40)
24///     .style(Color::from([0.0, 0.0, 1.0]));
25/// ```
26///
27/// ![Text drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text.png?raw=true)
28#[allow(missing_debug_implementations)]
29pub struct Text<'a, Renderer>
30where
31    Renderer: text::Renderer,
32    Renderer::Theme: StyleSheet,
33{
34    content: Cow<'a, str>,
35    size: Option<f32>,
36    width: Length,
37    height: Length,
38    horizontal_alignment: alignment::Horizontal,
39    vertical_alignment: alignment::Vertical,
40    font: Renderer::Font,
41    style: <Renderer::Theme as StyleSheet>::Style,
42}
43
44impl<'a, Renderer> Text<'a, Renderer>
45where
46    Renderer: text::Renderer,
47    Renderer::Theme: StyleSheet,
48{
49    /// Create a new fragment of [`Text`] with the given contents.
50    pub fn new(content: impl Into<Cow<'a, str>>) -> Self {
51        Text {
52            content: content.into(),
53            size: None,
54            font: Default::default(),
55            width: Length::Shrink,
56            height: Length::Shrink,
57            horizontal_alignment: alignment::Horizontal::Left,
58            vertical_alignment: alignment::Vertical::Top,
59            style: Default::default(),
60        }
61    }
62
63    /// Sets the size of the [`Text`].
64    pub fn size(mut self, size: impl Into<Pixels>) -> Self {
65        self.size = Some(size.into().0);
66        self
67    }
68
69    /// Sets the [`Font`] of the [`Text`].
70    ///
71    /// [`Font`]: crate::text::Renderer::Font
72    pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
73        self.font = font.into();
74        self
75    }
76
77    /// Sets the style of the [`Text`].
78    pub fn style(
79        mut self,
80        style: impl Into<<Renderer::Theme as StyleSheet>::Style>,
81    ) -> Self {
82        self.style = style.into();
83        self
84    }
85
86    /// Sets the width of the [`Text`] boundaries.
87    pub fn width(mut self, width: impl Into<Length>) -> Self {
88        self.width = width.into();
89        self
90    }
91
92    /// Sets the height of the [`Text`] boundaries.
93    pub fn height(mut self, height: impl Into<Length>) -> Self {
94        self.height = height.into();
95        self
96    }
97
98    /// Sets the [`alignment::Horizontal`] of the [`Text`].
99    pub fn horizontal_alignment(
100        mut self,
101        alignment: alignment::Horizontal,
102    ) -> Self {
103        self.horizontal_alignment = alignment;
104        self
105    }
106
107    /// Sets the [`alignment::Vertical`] of the [`Text`].
108    pub fn vertical_alignment(
109        mut self,
110        alignment: alignment::Vertical,
111    ) -> Self {
112        self.vertical_alignment = alignment;
113        self
114    }
115}
116
117impl<'a, Message, Renderer> Widget<Message, Renderer> for Text<'a, Renderer>
118where
119    Renderer: text::Renderer,
120    Renderer::Theme: StyleSheet,
121{
122    fn width(&self) -> Length {
123        self.width
124    }
125
126    fn height(&self) -> Length {
127        self.height
128    }
129
130    fn layout(
131        &self,
132        renderer: &Renderer,
133        limits: &layout::Limits,
134    ) -> layout::Node {
135        let limits = limits.width(self.width).height(self.height);
136
137        let size = self.size.unwrap_or_else(|| renderer.default_size());
138
139        let bounds = limits.max();
140
141        let (width, height) =
142            renderer.measure(&self.content, size, self.font.clone(), bounds);
143
144        let size = limits.resolve(Size::new(width, height));
145
146        layout::Node::new(size)
147    }
148
149    fn draw(
150        &self,
151        _state: &Tree,
152        renderer: &mut Renderer,
153        theme: &Renderer::Theme,
154        style: &renderer::Style,
155        layout: Layout<'_>,
156        _cursor_position: Point,
157        _viewport: &Rectangle,
158    ) {
159        draw(
160            renderer,
161            style,
162            layout,
163            &self.content,
164            self.size,
165            self.font.clone(),
166            theme.appearance(self.style),
167            self.horizontal_alignment,
168            self.vertical_alignment,
169        );
170    }
171}
172
173/// Draws text using the same logic as the [`Text`] widget.
174///
175/// Specifically:
176///
177/// * If no `size` is provided, the default text size of the `Renderer` will be
178///   used.
179/// * If no `color` is provided, the [`renderer::Style::text_color`] will be
180///   used.
181/// * The alignment attributes do not affect the position of the bounds of the
182///   [`Layout`].
183pub fn draw<Renderer>(
184    renderer: &mut Renderer,
185    style: &renderer::Style,
186    layout: Layout<'_>,
187    content: &str,
188    size: Option<f32>,
189    font: Renderer::Font,
190    appearance: Appearance,
191    horizontal_alignment: alignment::Horizontal,
192    vertical_alignment: alignment::Vertical,
193) where
194    Renderer: text::Renderer,
195{
196    let bounds = layout.bounds();
197
198    let x = match horizontal_alignment {
199        alignment::Horizontal::Left => bounds.x,
200        alignment::Horizontal::Center => bounds.center_x(),
201        alignment::Horizontal::Right => bounds.x + bounds.width,
202    };
203
204    let y = match vertical_alignment {
205        alignment::Vertical::Top => bounds.y,
206        alignment::Vertical::Center => bounds.center_y(),
207        alignment::Vertical::Bottom => bounds.y + bounds.height,
208    };
209
210    renderer.fill_text(crate::text::Text {
211        content,
212        size: size.unwrap_or_else(|| renderer.default_size()),
213        bounds: Rectangle { x, y, ..bounds },
214        color: appearance.color.unwrap_or(style.text_color),
215        font,
216        horizontal_alignment,
217        vertical_alignment,
218    });
219}
220
221impl<'a, Message, Renderer> From<Text<'a, Renderer>>
222    for Element<'a, Message, Renderer>
223where
224    Renderer: text::Renderer + 'a,
225    Renderer::Theme: StyleSheet,
226{
227    fn from(text: Text<'a, Renderer>) -> Element<'a, Message, Renderer> {
228        Element::new(text)
229    }
230}
231
232impl<'a, Renderer> Clone for Text<'a, Renderer>
233where
234    Renderer: text::Renderer,
235    Renderer::Theme: StyleSheet,
236{
237    fn clone(&self) -> Self {
238        Self {
239            content: self.content.clone(),
240            size: self.size,
241            width: self.width,
242            height: self.height,
243            horizontal_alignment: self.horizontal_alignment,
244            vertical_alignment: self.vertical_alignment,
245            font: self.font.clone(),
246            style: self.style,
247        }
248    }
249}
250
251impl<'a, Message, Renderer> From<&'a str> for Element<'a, Message, Renderer>
252where
253    Renderer: text::Renderer + 'a,
254    Renderer::Theme: StyleSheet,
255{
256    fn from(contents: &'a str) -> Self {
257        Text::new(contents).into()
258    }
259}