Skip to main content

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