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