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