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