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;
28use crate::text::paragraph::{self, Paragraph};
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::{Alignment, 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: text::Fragment<'a>,
66    size: Option<Pixels>,
67    line_height: LineHeight,
68    width: Length,
69    height: Length,
70    align_x: text::Alignment,
71    align_y: alignment::Vertical,
72    font: Option<Renderer::Font>,
73    shaping: Shaping,
74    wrapping: Wrapping,
75    class: Theme::Class<'a>,
76}
77
78impl<'a, Theme, Renderer> Text<'a, Theme, Renderer>
79where
80    Theme: Catalog,
81    Renderer: text::Renderer,
82{
83    /// Create a new fragment of [`Text`] with the given contents.
84    pub fn new(fragment: impl text::IntoFragment<'a>) -> Self {
85        Text {
86            fragment: fragment.into_fragment(),
87            size: None,
88            line_height: LineHeight::default(),
89            font: None,
90            width: Length::Shrink,
91            height: Length::Shrink,
92            align_x: text::Alignment::Default,
93            align_y: alignment::Vertical::Top,
94            shaping: Shaping::default(),
95            wrapping: Wrapping::default(),
96            class: Theme::default(),
97        }
98    }
99
100    /// Sets the size of the [`Text`].
101    pub fn size(mut self, size: impl Into<Pixels>) -> Self {
102        self.size = Some(size.into());
103        self
104    }
105
106    /// Sets the [`LineHeight`] of the [`Text`].
107    pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
108        self.line_height = line_height.into();
109        self
110    }
111
112    /// Sets the [`Font`] of the [`Text`].
113    ///
114    /// [`Font`]: crate::text::Renderer::Font
115    pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
116        self.font = Some(font.into());
117        self
118    }
119
120    /// Sets the width of the [`Text`] boundaries.
121    pub fn width(mut self, width: impl Into<Length>) -> Self {
122        self.width = width.into();
123        self
124    }
125
126    /// Sets the height of the [`Text`] boundaries.
127    pub fn height(mut self, height: impl Into<Length>) -> Self {
128        self.height = height.into();
129        self
130    }
131
132    /// Centers the [`Text`], both horizontally and vertically.
133    pub fn center(self) -> Self {
134        self.align_x(alignment::Horizontal::Center)
135            .align_y(alignment::Vertical::Center)
136    }
137
138    /// Sets the [`alignment::Horizontal`] of the [`Text`].
139    pub fn align_x(mut self, alignment: impl Into<text::Alignment>) -> Self {
140        self.align_x = alignment.into();
141        self
142    }
143
144    /// Sets the [`alignment::Vertical`] of the [`Text`].
145    pub fn align_y(
146        mut self,
147        alignment: impl Into<alignment::Vertical>,
148    ) -> Self {
149        self.align_y = alignment.into();
150        self
151    }
152
153    /// Sets the [`Shaping`] strategy of the [`Text`].
154    pub fn shaping(mut self, shaping: Shaping) -> Self {
155        self.shaping = shaping;
156        self
157    }
158
159    /// Sets the [`Wrapping`] strategy of the [`Text`].
160    pub fn wrapping(mut self, wrapping: Wrapping) -> Self {
161        self.wrapping = wrapping;
162        self
163    }
164
165    /// Sets the style of the [`Text`].
166    #[must_use]
167    pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
168    where
169        Theme::Class<'a>: From<StyleFn<'a, Theme>>,
170    {
171        self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
172        self
173    }
174
175    /// Sets the [`Color`] of the [`Text`].
176    pub fn color(self, color: impl Into<Color>) -> Self
177    where
178        Theme::Class<'a>: From<StyleFn<'a, Theme>>,
179    {
180        self.color_maybe(Some(color))
181    }
182
183    /// Sets the [`Color`] of the [`Text`], if `Some`.
184    pub fn color_maybe(self, color: Option<impl Into<Color>>) -> Self
185    where
186        Theme::Class<'a>: From<StyleFn<'a, Theme>>,
187    {
188        let color = color.map(Into::into);
189
190        self.style(move |_theme| Style { color })
191    }
192
193    /// Sets the style class of the [`Text`].
194    #[cfg(feature = "advanced")]
195    #[must_use]
196    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
197        self.class = class.into();
198        self
199    }
200}
201
202/// The internal state of a [`Text`] widget.
203#[derive(Debug, Default)]
204pub struct State<P: Paragraph>(pub paragraph::Plain<P>);
205
206impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
207    for Text<'_, Theme, Renderer>
208where
209    Theme: Catalog,
210    Renderer: text::Renderer,
211{
212    fn tag(&self) -> tree::Tag {
213        tree::Tag::of::<State<Renderer::Paragraph>>()
214    }
215
216    fn state(&self) -> tree::State {
217        tree::State::new(State::<Renderer::Paragraph>(
218            paragraph::Plain::default(),
219        ))
220    }
221
222    fn size(&self) -> Size<Length> {
223        Size {
224            width: self.width,
225            height: self.height,
226        }
227    }
228
229    fn layout(
230        &self,
231        tree: &mut Tree,
232        renderer: &Renderer,
233        limits: &layout::Limits,
234    ) -> layout::Node {
235        layout(
236            tree.state.downcast_mut::<State<Renderer::Paragraph>>(),
237            renderer,
238            limits,
239            self.width,
240            self.height,
241            &self.fragment,
242            self.line_height,
243            self.size,
244            self.font,
245            self.align_x,
246            self.align_y,
247            self.shaping,
248            self.wrapping,
249        )
250    }
251
252    fn draw(
253        &self,
254        tree: &Tree,
255        renderer: &mut Renderer,
256        theme: &Theme,
257        defaults: &renderer::Style,
258        layout: Layout<'_>,
259        _cursor_position: mouse::Cursor,
260        viewport: &Rectangle,
261    ) {
262        let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
263        let style = theme.style(&self.class);
264
265        draw(renderer, defaults, layout, state.0.raw(), style, viewport);
266    }
267
268    fn operate(
269        &self,
270        _state: &mut Tree,
271        layout: Layout<'_>,
272        _renderer: &Renderer,
273        operation: &mut dyn super::Operation,
274    ) {
275        operation.text(None, layout.bounds(), &self.fragment);
276    }
277}
278
279/// Produces the [`layout::Node`] of a [`Text`] widget.
280pub fn layout<Renderer>(
281    state: &mut State<Renderer::Paragraph>,
282    renderer: &Renderer,
283    limits: &layout::Limits,
284    width: Length,
285    height: Length,
286    content: &str,
287    line_height: LineHeight,
288    size: Option<Pixels>,
289    font: Option<Renderer::Font>,
290    align_x: text::Alignment,
291    align_y: alignment::Vertical,
292    shaping: Shaping,
293    wrapping: Wrapping,
294) -> layout::Node
295where
296    Renderer: text::Renderer,
297{
298    layout::sized(limits, width, height, |limits| {
299        let bounds = limits.max();
300
301        let size = size.unwrap_or_else(|| renderer.default_size());
302        let font = font.unwrap_or_else(|| renderer.default_font());
303
304        let State(paragraph) = state;
305
306        paragraph.update(text::Text {
307            content,
308            bounds,
309            size,
310            line_height,
311            font,
312            align_x,
313            align_y,
314            shaping,
315            wrapping,
316        });
317
318        paragraph.min_bounds()
319    })
320}
321
322/// Draws text using the same logic as the [`Text`] widget.
323///
324/// Specifically:
325///
326/// * If no `size` is provided, the default text size of the `Renderer` will be
327///   used.
328/// * If no `color` is provided, the [`renderer::Style::text_color`] will be
329///   used.
330/// * The alignment attributes do not affect the position of the bounds of the
331///   [`Layout`].
332pub fn draw<Renderer>(
333    renderer: &mut Renderer,
334    style: &renderer::Style,
335    layout: Layout<'_>,
336    paragraph: &Renderer::Paragraph,
337    appearance: Style,
338    viewport: &Rectangle,
339) where
340    Renderer: text::Renderer,
341{
342    let bounds = layout.bounds();
343
344    let x = match paragraph.align_x() {
345        Alignment::Default | Alignment::Left | Alignment::Justified => bounds.x,
346        Alignment::Center => bounds.center_x(),
347        Alignment::Right => bounds.x + bounds.width,
348    };
349
350    let y = match paragraph.align_y() {
351        alignment::Vertical::Top => bounds.y,
352        alignment::Vertical::Center => bounds.center_y(),
353        alignment::Vertical::Bottom => bounds.y + bounds.height,
354    };
355
356    renderer.fill_paragraph(
357        paragraph,
358        Point::new(x, y),
359        appearance.color.unwrap_or(style.text_color),
360        *viewport,
361    );
362}
363
364impl<'a, Message, Theme, Renderer> From<Text<'a, Theme, Renderer>>
365    for Element<'a, Message, Theme, Renderer>
366where
367    Theme: Catalog + 'a,
368    Renderer: text::Renderer + 'a,
369{
370    fn from(
371        text: Text<'a, Theme, Renderer>,
372    ) -> Element<'a, Message, Theme, Renderer> {
373        Element::new(text)
374    }
375}
376
377impl<'a, Theme, Renderer> From<&'a str> for Text<'a, Theme, Renderer>
378where
379    Theme: Catalog + 'a,
380    Renderer: text::Renderer,
381{
382    fn from(content: &'a str) -> Self {
383        Self::new(content)
384    }
385}
386
387impl<'a, Message, Theme, Renderer> From<&'a str>
388    for Element<'a, Message, Theme, Renderer>
389where
390    Theme: Catalog + 'a,
391    Renderer: text::Renderer + 'a,
392{
393    fn from(content: &'a str) -> Self {
394        Text::from(content).into()
395    }
396}
397
398/// The appearance of some text.
399#[derive(Debug, Clone, Copy, PartialEq, Default)]
400pub struct Style {
401    /// The [`Color`] of the text.
402    ///
403    /// The default, `None`, means using the inherited color.
404    pub color: Option<Color>,
405}
406
407/// The theme catalog of a [`Text`].
408pub trait Catalog: Sized {
409    /// The item class of this [`Catalog`].
410    type Class<'a>;
411
412    /// The default class produced by this [`Catalog`].
413    fn default<'a>() -> Self::Class<'a>;
414
415    /// The [`Style`] of a class with the given status.
416    fn style(&self, item: &Self::Class<'_>) -> Style;
417}
418
419/// A styling function for a [`Text`].
420///
421/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
422pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
423
424impl Catalog for Theme {
425    type Class<'a> = StyleFn<'a, Self>;
426
427    fn default<'a>() -> Self::Class<'a> {
428        Box::new(|_theme| Style::default())
429    }
430
431    fn style(&self, class: &Self::Class<'_>) -> Style {
432        class(self)
433    }
434}
435
436/// The default text styling; color is inherited.
437pub fn default(_theme: &Theme) -> Style {
438    Style { color: None }
439}
440
441/// Text with the default base color.
442pub fn base(theme: &Theme) -> Style {
443    Style {
444        color: Some(theme.palette().text),
445    }
446}
447
448/// Text conveying some important information, like an action.
449pub fn primary(theme: &Theme) -> Style {
450    Style {
451        color: Some(theme.palette().primary),
452    }
453}
454
455/// Text conveying some secondary information, like a footnote.
456pub fn secondary(theme: &Theme) -> Style {
457    Style {
458        color: Some(theme.extended_palette().secondary.strong.color),
459    }
460}
461
462/// Text conveying some positive information, like a successful event.
463pub fn success(theme: &Theme) -> Style {
464    Style {
465        color: Some(theme.palette().success),
466    }
467}
468
469/// Text conveying some negative information, like an error.
470pub fn danger(theme: &Theme) -> Style {
471    Style {
472        color: Some(theme.palette().danger),
473    }
474}