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, Font, Layout, Length, Pixels, Rectangle, Size, Theme, Widget};
31
32pub use text::{Alignment, Ellipsis, LineHeight, Position, 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>
58where
59    Theme: Catalog,
60{
61    fragment: text::Fragment<'a>,
62    format: Format,
63    class: Theme::Class<'a>,
64}
65
66impl<'a, Theme> Text<'a, Theme>
67where
68    Theme: Catalog,
69{
70    /// Create a new fragment of [`Text`] with the given contents.
71    pub fn new(fragment: impl text::IntoFragment<'a>) -> Self {
72        Text {
73            fragment: fragment.into_fragment(),
74            format: Format::default(),
75            class: Theme::default(),
76        }
77    }
78
79    /// Sets the size of the [`Text`].
80    pub fn size(mut self, size: impl Into<Pixels>) -> Self {
81        self.format.size = Some(size.into());
82        self
83    }
84
85    /// Sets the [`LineHeight`] of the [`Text`].
86    pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
87        self.format.line_height = Some(line_height.into());
88        self
89    }
90
91    /// Sets the [`Font`] of the [`Text`].
92    pub fn font(mut self, font: impl Into<Font>) -> Self {
93        self.format.font = Some(font.into());
94        self
95    }
96
97    /// Sets the [`Font`] of the [`Text`], if `Some`.
98    pub fn font_maybe(mut self, font: Option<impl Into<Font>>) -> Self {
99        self.format.font = font.map(Into::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(mut self, alignment: impl Into<alignment::Vertical>) -> Self {
129        self.format.align_y = alignment.into();
130        self
131    }
132
133    /// Sets the [`Shaping`] strategy of the [`Text`].
134    pub fn shaping(mut self, shaping: Shaping) -> Self {
135        self.format.shaping = shaping;
136        self
137    }
138
139    /// Sets the [`Wrapping`] strategy of the [`Text`].
140    pub fn wrapping(mut self, wrapping: Wrapping) -> Self {
141        self.format.wrapping = wrapping;
142        self
143    }
144
145    /// Sets the [`Ellipsis`] strategy of the [`Text`].
146    pub fn ellipsis(mut self, ellipsis: Ellipsis) -> Self {
147        self.format.ellipsis = ellipsis;
148        self
149    }
150
151    /// Sets the style of the [`Text`].
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    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
181        self.class = class.into();
182        self
183    }
184}
185
186/// The internal state of a [`Text`] widget.
187pub type State<P> = paragraph::Plain<P>;
188
189impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Text<'_, Theme>
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        &mut 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        &mut self,
249        _tree: &mut Tree,
250        layout: Layout<'_>,
251        _viewport: &Rectangle,
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 {
266    pub width: Length,
267    pub height: Length,
268    pub size: Option<Pixels>,
269    pub font: Option<Font>,
270    pub line_height: Option<LineHeight>,
271    pub align_x: text::Alignment,
272    pub align_y: alignment::Vertical,
273    pub shaping: Shaping,
274    pub wrapping: Wrapping,
275    pub ellipsis: Ellipsis,
276}
277
278impl Default for Format {
279    fn default() -> Self {
280        Self {
281            size: None,
282            line_height: None,
283            font: None,
284            width: Length::Shrink,
285            height: Length::Shrink,
286            align_x: text::Alignment::Default,
287            align_y: alignment::Vertical::Top,
288            shaping: Shaping::default(),
289            wrapping: Wrapping::default(),
290            ellipsis: Ellipsis::default(),
291        }
292    }
293}
294
295/// Produces the [`layout::Node`] of a [`Text`] widget.
296pub fn layout<Renderer>(
297    paragraph: &mut paragraph::Plain<Renderer::Paragraph>,
298    renderer: &Renderer,
299    limits: &layout::Limits,
300    content: &str,
301    format: Format,
302) -> layout::Node
303where
304    Renderer: text::Renderer,
305{
306    layout::sized(limits, format.width, format.height, |limits| {
307        let bounds = limits.max();
308
309        let size = format.size.unwrap_or_else(|| renderer.text_size());
310        let font = format.font.unwrap_or_else(|| renderer.font());
311        let line_height = format.line_height.unwrap_or_else(|| renderer.line_height());
312
313        let _ = paragraph.update(text::Text {
314            content,
315            bounds,
316            size,
317            line_height,
318            font,
319            align_x: format.align_x,
320            align_y: format.align_y,
321            shaping: format.shaping,
322            wrapping: format.wrapping,
323            ellipsis: format.ellipsis,
324            hint_factor: renderer.hint_factor(),
325        });
326
327        paragraph.min_bounds()
328    })
329}
330
331/// Draws text using the same logic as the [`Text`] widget.
332pub fn draw<Renderer>(
333    renderer: &mut Renderer,
334    style: &renderer::Style,
335    bounds: Rectangle,
336    paragraph: &Renderer::Paragraph,
337    appearance: Style,
338    viewport: &Rectangle,
339) where
340    Renderer: text::Renderer,
341{
342    let anchor = bounds.anchor(
343        paragraph.min_bounds(),
344        paragraph.align_x(),
345        paragraph.align_y(),
346    );
347
348    renderer.fill_paragraph(
349        paragraph,
350        anchor,
351        appearance.color.unwrap_or(style.text_color),
352        *viewport,
353    );
354}
355
356impl<'a, Message, Theme, Renderer> From<Text<'a, Theme>> for Element<'a, Message, Theme, Renderer>
357where
358    Theme: Catalog + 'a,
359    Renderer: text::Renderer + 'a,
360{
361    fn from(text: Text<'a, Theme>) -> Element<'a, Message, Theme, Renderer> {
362        Element::new(text)
363    }
364}
365
366impl<'a, Theme> From<&'a str> for Text<'a, Theme>
367where
368    Theme: Catalog + 'a,
369{
370    fn from(content: &'a str) -> Self {
371        Self::new(content)
372    }
373}
374
375impl<'a, Message, Theme, Renderer> From<&'a str> for Element<'a, Message, Theme, Renderer>
376where
377    Theme: Catalog + 'a,
378    Renderer: text::Renderer + 'a,
379{
380    fn from(content: &'a str) -> Self {
381        Text::from(content).into()
382    }
383}
384
385/// The appearance of some text.
386#[derive(Debug, Clone, Copy, PartialEq, Default)]
387pub struct Style {
388    /// The [`Color`] of the text.
389    ///
390    /// The default, `None`, means using the inherited color.
391    pub color: Option<Color>,
392}
393
394/// The theme catalog of a [`Text`].
395pub trait Catalog: Sized {
396    /// The item class of this [`Catalog`].
397    type Class<'a>;
398
399    /// The default class produced by this [`Catalog`].
400    fn default<'a>() -> Self::Class<'a>;
401
402    /// The [`Style`] of a class with the given status.
403    fn style(&self, item: &Self::Class<'_>) -> Style;
404}
405
406/// A styling function for a [`Text`].
407///
408/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
409pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
410
411impl Catalog for Theme {
412    type Class<'a> = StyleFn<'a, Self>;
413
414    fn default<'a>() -> Self::Class<'a> {
415        Box::new(|_theme| Style::default())
416    }
417
418    fn style(&self, class: &Self::Class<'_>) -> Style {
419        class(self)
420    }
421}
422
423/// The default text styling; color is inherited.
424pub fn default(_theme: &Theme) -> Style {
425    Style { color: None }
426}
427
428/// Text with the default base color.
429pub fn base(theme: &Theme) -> Style {
430    Style {
431        color: Some(theme.seed().text),
432    }
433}
434
435/// Text conveying some important information, like an action.
436pub fn primary(theme: &Theme) -> Style {
437    Style {
438        color: Some(theme.seed().primary),
439    }
440}
441
442/// Text conveying some secondary information, like a footnote.
443pub fn secondary(theme: &Theme) -> Style {
444    Style {
445        color: Some(theme.palette().secondary.base.color),
446    }
447}
448
449/// Text conveying some positive information, like a successful event.
450pub fn success(theme: &Theme) -> Style {
451    Style {
452        color: Some(theme.seed().success),
453    }
454}
455
456/// Text conveying some mildly negative information, like a warning.
457pub fn warning(theme: &Theme) -> Style {
458    Style {
459        color: Some(theme.seed().warning),
460    }
461}
462
463/// Text conveying some negative information, like an error.
464pub fn danger(theme: &Theme) -> Style {
465    Style {
466        color: Some(theme.seed().danger),
467    }
468}