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