iced_core/widget/
text.rs

1//! Text widgets display information through writing.
2//!
3//! # Example
4//! ```no_run
5//! # mod iced { pub mod widget { pub fn text<T>(t: T) -> iced_core::widget::Text<'static, iced_core::Theme, ()> { unimplemented!() } }
6//! #            pub use iced_core::color; }
7//! # pub type State = ();
8//! # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>;
9//! use iced::widget::text;
10//! use iced::color;
11//!
12//! enum Message {
13//!     // ...
14//! }
15//!
16//! fn view(state: &State) -> Element<'_, Message> {
17//!     text("Hello, this is iced!")
18//!         .size(20)
19//!         .color(color!(0x0000ff))
20//!         .into()
21//! }
22//! ```
23use crate::alignment;
24use crate::layout;
25use crate::mouse;
26use crate::renderer;
27use crate::text::paragraph::{self, Paragraph};
28use crate::text::{self, Fragment};
29use crate::widget::tree::{self, Tree};
30use crate::{
31    Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Theme,
32    Widget,
33};
34
35pub use text::{LineHeight, Shaping, Wrapping};
36
37/// A bunch of text.
38///
39/// # Example
40/// ```no_run
41/// # mod iced { pub mod widget { pub fn text<T>(t: T) -> iced_core::widget::Text<'static, iced_core::Theme, ()> { unimplemented!() } }
42/// #            pub use iced_core::color; }
43/// # pub type State = ();
44/// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>;
45/// use iced::widget::text;
46/// use iced::color;
47///
48/// enum Message {
49///     // ...
50/// }
51///
52/// fn view(state: &State) -> Element<'_, Message> {
53///     text("Hello, this is iced!")
54///         .size(20)
55///         .color(color!(0x0000ff))
56///         .into()
57/// }
58/// ```
59#[allow(missing_debug_implementations)]
60pub struct Text<'a, Theme, Renderer>
61where
62    Theme: Catalog,
63    Renderer: text::Renderer,
64{
65    fragment: Fragment<'a>,
66    id: crate::widget::Id,
67    size: Option<Pixels>,
68    line_height: LineHeight,
69    width: Length,
70    height: Length,
71    horizontal_alignment: alignment::Horizontal,
72    vertical_alignment: alignment::Vertical,
73    font: Option<Renderer::Font>,
74    shaping: Shaping,
75    wrapping: Wrapping,
76    class: Theme::Class<'a>,
77}
78
79impl<'a, Theme, Renderer> Text<'a, Theme, Renderer>
80where
81    Theme: Catalog,
82    Renderer: text::Renderer,
83{
84    /// Create a new fragment of [`Text`] with the given contents.
85    pub fn new(fragment: impl text::IntoFragment<'a>) -> Self {
86        Text {
87            fragment: fragment.into_fragment(),
88            id: crate::widget::Id::unique(),
89            size: None,
90            line_height: LineHeight::default(),
91            font: None,
92            width: Length::Shrink,
93            height: Length::Shrink,
94            horizontal_alignment: alignment::Horizontal::Left,
95            vertical_alignment: alignment::Vertical::Top,
96            shaping: Shaping::default(),
97            wrapping: Wrapping::default(),
98            class: Theme::default(),
99        }
100    }
101
102    /// Sets the size of the [`Text`].
103    pub fn size(mut self, size: impl Into<Pixels>) -> Self {
104        self.size = Some(size.into());
105        self
106    }
107
108    /// Sets the [`LineHeight`] of the [`Text`].
109    pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
110        self.line_height = line_height.into();
111        self
112    }
113
114    /// Sets the [`Font`] of the [`Text`].
115    ///
116    /// [`Font`]: crate::text::Renderer::Font
117    pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
118        self.font = Some(font.into());
119        self
120    }
121
122    /// Sets the width of the [`Text`] boundaries.
123    pub fn width(mut self, width: impl Into<Length>) -> Self {
124        self.width = width.into();
125        self
126    }
127
128    /// Sets the height of the [`Text`] boundaries.
129    pub fn height(mut self, height: impl Into<Length>) -> Self {
130        self.height = height.into();
131        self
132    }
133
134    /// Centers the [`Text`], both horizontally and vertically.
135    pub fn center(self) -> Self {
136        self.align_x(alignment::Horizontal::Center)
137            .align_y(alignment::Vertical::Center)
138    }
139
140    /// Sets the [`alignment::Horizontal`] of the [`Text`].
141    pub fn align_x(
142        mut self,
143        alignment: impl Into<alignment::Horizontal>,
144    ) -> Self {
145        self.horizontal_alignment = alignment.into();
146        self
147    }
148
149    /// Sets the [`alignment::Vertical`] of the [`Text`].
150    pub fn align_y(
151        mut self,
152        alignment: impl Into<alignment::Vertical>,
153    ) -> Self {
154        self.vertical_alignment = alignment.into();
155        self
156    }
157
158    /// Sets the [`Shaping`] strategy of the [`Text`].
159    pub fn shaping(mut self, shaping: Shaping) -> Self {
160        self.shaping = shaping;
161        self
162    }
163
164    /// Sets the [`Wrapping`] strategy of the [`Text`].
165    pub fn wrapping(mut self, wrapping: Wrapping) -> Self {
166        self.wrapping = wrapping;
167        self
168    }
169
170    /// Sets the style of the [`Text`].
171    #[must_use]
172    pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
173    where
174        Theme::Class<'a>: From<StyleFn<'a, Theme>>,
175    {
176        self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
177        self
178    }
179
180    /// Sets the [`Color`] of the [`Text`].
181    pub fn color(self, color: impl Into<Color>) -> Self
182    where
183        Theme::Class<'a>: From<StyleFn<'a, Theme>>,
184    {
185        self.color_maybe(Some(color))
186    }
187
188    /// Sets the [`Color`] of the [`Text`], if `Some`.
189    pub fn color_maybe(self, color: Option<impl Into<Color>>) -> Self
190    where
191        Theme::Class<'a>: From<StyleFn<'a, Theme>>,
192    {
193        let color = color.map(Into::into);
194
195        self.style(move |_theme| Style { color })
196    }
197
198    /// Sets the style class of the [`Text`].
199    #[cfg(feature = "advanced")]
200    #[must_use]
201    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
202        self.class = class.into();
203        self
204    }
205}
206
207/// The internal state of a [`Text`] widget.
208#[derive(Debug, Default)]
209pub struct State<P: Paragraph>(pub paragraph::Plain<P>);
210
211impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
212    for Text<'a, Theme, Renderer>
213where
214    Theme: Catalog,
215    Renderer: text::Renderer,
216{
217    fn tag(&self) -> tree::Tag {
218        tree::Tag::of::<State<Renderer::Paragraph>>()
219    }
220
221    fn state(&self) -> tree::State {
222        tree::State::new(State::<Renderer::Paragraph>(
223            paragraph::Plain::default(),
224        ))
225    }
226
227    fn size(&self) -> Size<Length> {
228        Size {
229            width: self.width,
230            height: self.height,
231        }
232    }
233
234    fn layout(
235        &self,
236        tree: &mut Tree,
237        renderer: &Renderer,
238        limits: &layout::Limits,
239    ) -> layout::Node {
240        layout(
241            tree.state.downcast_mut::<State<Renderer::Paragraph>>(),
242            renderer,
243            limits,
244            self.width,
245            self.height,
246            &self.fragment,
247            self.line_height,
248            self.size,
249            self.font,
250            self.horizontal_alignment,
251            self.vertical_alignment,
252            self.shaping,
253            self.wrapping,
254        )
255    }
256
257    fn draw(
258        &self,
259        tree: &Tree,
260        renderer: &mut Renderer,
261        theme: &Theme,
262        defaults: &renderer::Style,
263        layout: Layout<'_>,
264        _cursor_position: mouse::Cursor,
265        viewport: &Rectangle,
266    ) {
267        let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
268        let style = theme.style(&self.class);
269
270        draw(renderer, defaults, layout, state.0.raw(), style, viewport);
271    }
272
273    #[cfg(feature = "a11y")]
274    fn a11y_nodes(
275        &self,
276        layout: Layout<'_>,
277        _state: &Tree,
278        _: mouse::Cursor,
279    ) -> iced_accessibility::A11yTree {
280        use iced_accessibility::{
281            accesskit::{Live, NodeBuilder, Rect, Role},
282            A11yTree,
283        };
284
285        let Rectangle {
286            x,
287            y,
288            width,
289            height,
290        } = layout.bounds();
291        let bounds = Rect::new(
292            x as f64,
293            y as f64,
294            (x + width) as f64,
295            (y + height) as f64,
296        );
297
298        let mut node = NodeBuilder::new(Role::Paragraph);
299
300        // TODO is the name likely different from the content?
301        node.set_name(self.fragment.to_string().into_boxed_str());
302        node.set_bounds(bounds);
303
304        // TODO make this configurable
305        node.set_live(Live::Polite);
306        A11yTree::leaf(node, self.id.clone())
307    }
308
309    fn id(&self) -> Option<crate::widget::Id> {
310        Some(self.id.clone())
311    }
312
313    fn set_id(&mut self, id: crate::widget::Id) {
314        self.id = id;
315    }
316}
317
318/// Produces the [`layout::Node`] of a [`Text`] widget.
319pub fn layout<Renderer>(
320    state: &mut State<Renderer::Paragraph>,
321    renderer: &Renderer,
322    limits: &layout::Limits,
323    width: Length,
324    height: Length,
325    content: &str,
326    line_height: LineHeight,
327    size: Option<Pixels>,
328    font: Option<Renderer::Font>,
329    horizontal_alignment: alignment::Horizontal,
330    vertical_alignment: alignment::Vertical,
331    shaping: Shaping,
332    wrapping: Wrapping,
333) -> layout::Node
334where
335    Renderer: text::Renderer,
336{
337    layout::sized(limits, width, height, |limits| {
338        let bounds = limits.max();
339
340        let size = size.unwrap_or_else(|| renderer.default_size());
341        let font = font.unwrap_or_else(|| renderer.default_font());
342
343        let State(ref mut paragraph) = state;
344
345        paragraph.update(text::Text {
346            content,
347            bounds,
348            size,
349            line_height,
350            font,
351            horizontal_alignment,
352            vertical_alignment,
353            shaping,
354            wrapping,
355        });
356
357        paragraph.min_bounds()
358    })
359}
360
361/// Draws text using the same logic as the [`Text`] widget.
362///
363/// Specifically:
364///
365/// * If no `size` is provided, the default text size of the `Renderer` will be
366///   used.
367/// * If no `color` is provided, the [`renderer::Style::text_color`] will be
368///   used.
369/// * The alignment attributes do not affect the position of the bounds of the
370///   [`Layout`].
371pub fn draw<Renderer>(
372    renderer: &mut Renderer,
373    style: &renderer::Style,
374    layout: Layout<'_>,
375    paragraph: &Renderer::Paragraph,
376    appearance: Style,
377    viewport: &Rectangle,
378) where
379    Renderer: text::Renderer,
380{
381    let bounds = layout.bounds();
382
383    let x = match paragraph.horizontal_alignment() {
384        alignment::Horizontal::Left => bounds.x,
385        alignment::Horizontal::Center => bounds.center_x(),
386        alignment::Horizontal::Right => bounds.x + bounds.width,
387    };
388
389    let y = match paragraph.vertical_alignment() {
390        alignment::Vertical::Top => bounds.y,
391        alignment::Vertical::Center => bounds.center_y(),
392        alignment::Vertical::Bottom => bounds.y + bounds.height,
393    };
394
395    renderer.fill_paragraph(
396        paragraph,
397        Point::new(x, y),
398        appearance.color.unwrap_or(style.text_color),
399        *viewport,
400    );
401}
402
403impl<'a, Message, Theme, Renderer> From<Text<'a, Theme, Renderer>>
404    for Element<'a, Message, Theme, Renderer>
405where
406    Theme: Catalog + 'a,
407    Renderer: text::Renderer + 'a,
408{
409    fn from(
410        text: Text<'a, Theme, Renderer>,
411    ) -> Element<'a, Message, Theme, Renderer> {
412        Element::new(text)
413    }
414}
415
416// impl<'a, Theme, Renderer> Clone for Text<'a, Theme, Renderer>
417// where
418//     Renderer: text::Renderer,
419// {
420//     fn clone(&self) -> Self {
421//         Self {
422//             id: self.id.clone(),
423//             content: self.content.clone(),
424//             size: self.size,
425//             line_height: self.line_height,
426//             width: self.width,
427//             height: self.height,
428//             horizontal_alignment: self.horizontal_alignment,
429//             vertical_alignment: self.vertical_alignment,
430//             font: self.font,
431//             style: self.style,
432//             shaping: self.shaping,
433//             wrap: self.wrap,
434//         }
435//     }
436// }
437// TODO(POP): Clone no longer can be implemented because of style being a Box(style)
438
439impl<'a, Theme, Renderer> From<&'a str> for Text<'a, Theme, Renderer>
440where
441    Theme: Catalog + 'a,
442    Renderer: text::Renderer,
443{
444    fn from(content: &'a str) -> Self {
445        Self::new(content)
446    }
447}
448
449impl<'a, Message, Theme, Renderer> From<&'a str>
450    for Element<'a, Message, Theme, Renderer>
451where
452    Theme: Catalog + 'a,
453    Renderer: text::Renderer + 'a,
454{
455    fn from(content: &'a str) -> Self {
456        Text::from(content).into()
457    }
458}
459
460/// The appearance of some text.
461#[derive(Debug, Clone, Copy, PartialEq, Default)]
462pub struct Style {
463    /// The [`Color`] of the text.
464    ///
465    /// The default, `None`, means using the inherited color.
466    pub color: Option<Color>,
467}
468
469/// The theme catalog of a [`Text`].
470pub trait Catalog: Sized {
471    /// The item class of this [`Catalog`].
472    type Class<'a>;
473
474    /// The default class produced by this [`Catalog`].
475    fn default<'a>() -> Self::Class<'a>;
476
477    /// The [`Style`] of a class with the given status.
478    fn style(&self, item: &Self::Class<'_>) -> Style;
479}
480
481/// A styling function for a [`Text`].
482///
483/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
484pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
485
486impl Catalog for Theme {
487    type Class<'a> = StyleFn<'a, Self>;
488
489    fn default<'a>() -> Self::Class<'a> {
490        Box::new(|_theme| Style::default())
491    }
492
493    fn style(&self, class: &Self::Class<'_>) -> Style {
494        class(self)
495    }
496}
497
498/// The default text styling; color is inherited.
499pub fn default(_theme: &Theme) -> Style {
500    Style { color: None }
501}
502
503/// Text with the default base color.
504pub fn base(theme: &Theme) -> Style {
505    Style {
506        color: Some(theme.palette().text),
507    }
508}
509
510/// Text conveying some important information, like an action.
511pub fn primary(theme: &Theme) -> Style {
512    Style {
513        color: Some(theme.palette().primary),
514    }
515}
516
517/// Text conveying some secondary information, like a footnote.
518pub fn secondary(theme: &Theme) -> Style {
519    Style {
520        color: Some(theme.extended_palette().secondary.strong.color),
521    }
522}
523
524/// Text conveying some positive information, like a successful event.
525pub fn success(theme: &Theme) -> Style {
526    Style {
527        color: Some(theme.palette().success),
528    }
529}
530
531/// Text conveying some negative information, like an error.
532pub fn danger(theme: &Theme) -> Style {
533    Style {
534        color: Some(theme.palette().danger),
535    }
536}