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 trait Widget<Message>: iced_core::Widget<Message, iced_core::Theme, ()> {}
8//! # impl<T, Message> Widget<Message> for T where T: iced_core::Widget<Message, iced_core::Theme, ()> {}
9//! # pub type State = ();
10//! use iced::widget::text;
11//! use iced::color;
12//!
13//! enum Message {
14//!     // ...
15//! }
16//!
17//! fn view(state: &State) -> impl Widget<Message> {
18//!     text("Hello, this is iced!")
19//!         .size(20)
20//!         .color(color!(0x0000ff))
21//! }
22//! ```
23use crate::alignment;
24use crate::layout;
25use crate::mouse;
26use crate::renderer;
27use crate::text::paragraph::{self, Paragraph};
28use crate::text::{self, Target};
29use crate::widget;
30use crate::widget::operation;
31use crate::widget::tree::{self, Tree};
32use crate::window;
33use crate::{Color, Event, Font, Layout, Length, Pixels, Point, Rectangle, Size, Theme, Widget};
34
35pub use text::{Alignment, Ellipsis, LineHeight, Position, Shaping, Wrapping};
36
37/// A bunch of text.
38///
39/// # Example
40/// ```no_run
41/// # mod iced { pub mod widget { pub fn text<T>(t: T) -> iced_core::widget::Text<'static, iced_core::Theme> { unimplemented!() } }
42/// #            pub use iced_core::color; }
43/// # pub trait Widget<Message>: iced_core::Widget<Message, iced_core::Theme, ()> {}
44/// # impl<T, Message> Widget<Message> for T where T: iced_core::Widget<Message, iced_core::Theme, ()> {}
45/// # pub type State = ();
46/// use iced::widget::text;
47/// use iced::color;
48///
49/// enum Message {
50///     // ...
51/// }
52///
53/// fn view(state: &State) -> impl Widget<Message> {
54///     text("Hello, this is iced!")
55///         .size(20)
56///         .color(color!(0x0000ff))
57/// }
58/// ```
59#[must_use]
60pub struct Text<'a, Theme>
61where
62    Theme: Catalog,
63{
64    fragment: text::Fragment<'a>,
65    format: Format,
66    selectable: bool,
67    class: Theme::Class<'a>,
68}
69
70impl<'a, Theme> Text<'a, Theme>
71where
72    Theme: Catalog,
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            selectable: false,
80            class: Theme::default(),
81        }
82    }
83
84    /// Sets the size of the [`Text`].
85    pub fn size(mut self, size: impl Into<Pixels>) -> Self {
86        self.format.size = Some(size.into());
87        self
88    }
89
90    /// Sets the [`LineHeight`] of the [`Text`].
91    pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
92        self.format.line_height = Some(line_height.into());
93        self
94    }
95
96    /// Sets the [`Font`] of the [`Text`].
97    pub fn font(mut self, font: impl Into<Font>) -> Self {
98        self.format.font = Some(font.into());
99        self
100    }
101
102    /// Sets the [`Font`] of the [`Text`], if `Some`.
103    pub fn font_maybe(mut self, font: Option<impl Into<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 [`Ellipsis`] strategy of the [`Text`].
151    pub fn ellipsis(mut self, ellipsis: Ellipsis) -> Self {
152        self.format.ellipsis = ellipsis;
153        self
154    }
155
156    /// Sets whether the [`Text`] can be selected.
157    ///
158    /// By default, it is `false`.
159    pub fn selectable(mut self, selectable: bool) -> Self {
160        self.selectable = selectable;
161        self
162    }
163
164    /// Sets the style of the [`Text`].
165    pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
166    where
167        Theme::Class<'a>: From<StyleFn<'a, Theme>>,
168    {
169        self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
170        self
171    }
172
173    /// Sets the [`Color`] of the [`Text`].
174    pub fn color(self, color: impl Into<Color>) -> Self
175    where
176        Theme::Class<'a>: From<StyleFn<'a, Theme>>,
177    {
178        self.color_maybe(Some(color))
179    }
180
181    /// Sets the [`Color`] of the [`Text`], if `Some`.
182    pub fn color_maybe(self, color: Option<impl Into<Color>>) -> Self
183    where
184        Theme::Class<'a>: From<StyleFn<'a, Theme>>,
185    {
186        let color = color.map(Into::into);
187
188        self.style(move |_theme| Style {
189            color,
190            selection: None,
191        })
192    }
193
194    /// Sets the style class of the [`Text`].
195    #[cfg(feature = "advanced")]
196    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
197        self.class = class.into();
198        self
199    }
200}
201
202impl<Theme> widget::Meta for Text<'_, Theme> where Theme: Catalog {}
203
204impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Text<'_, Theme>
205where
206    Theme: Catalog,
207    Renderer: text::Renderer,
208{
209    fn size(&self) -> Size<Length> {
210        Size {
211            width: self.format.width,
212            height: self.format.height,
213        }
214    }
215
216    fn tag(&self) -> tree::Tag {
217        tree::Tag::of::<State<Renderer::Paragraph>>()
218    }
219
220    fn state(&self) -> tree::State {
221        tree::State::new(State::<Renderer::Paragraph>::default())
222    }
223
224    fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits) {
225        let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
226
227        tree.size = layout(
228            &mut state.paragraph,
229            renderer,
230            limits,
231            &self.fragment,
232            self.format,
233        );
234    }
235
236    fn update(
237        &mut self,
238        tree: &mut Tree,
239        event: &crate::Event,
240        layout: Layout,
241        cursor: mouse::Cursor,
242        _renderer: &Renderer,
243        _shell: &mut crate::Shell<'_, Message>,
244        _viewport: &Rectangle,
245    ) {
246        update::<Renderer::Paragraph>(tree, event, layout, cursor);
247    }
248
249    fn mouse_interaction(
250        &self,
251        tree: &Tree,
252        _layout: Layout,
253        _cursor: mouse::Cursor,
254        _viewport: &Rectangle,
255        _renderer: &Renderer,
256    ) -> mouse::Interaction {
257        if self.selectable {
258            let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
259
260            if state.is_hovered {
261                return mouse::Interaction::Text;
262            }
263        }
264
265        mouse::Interaction::None
266    }
267
268    fn draw(
269        &self,
270        tree: &Tree,
271        renderer: &mut Renderer,
272        theme: &Theme,
273        defaults: &renderer::Style,
274        layout: Layout,
275        _cursor_position: mouse::Cursor,
276        viewport: &Rectangle,
277    ) {
278        let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
279        let style = theme.style(&self.class);
280
281        draw(
282            renderer,
283            defaults,
284            layout.bounds(),
285            state.paragraph.raw(),
286            style,
287            theme.selection(),
288            viewport,
289        );
290    }
291
292    fn operate(
293        &mut self,
294        tree: &mut Tree,
295        layout: Layout,
296        _viewport: &Rectangle,
297        _renderer: &Renderer,
298        operation: &mut dyn super::Operation,
299    ) {
300        let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
301        operation.text(
302            None,
303            layout.bounds(),
304            &mut Operand {
305                paragraph: &mut state.paragraph,
306                layout,
307                selectable: self.selectable,
308            },
309        );
310    }
311}
312
313impl widget::Meta for &str {}
314
315impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for &str
316where
317    Theme: Catalog,
318    Renderer: text::Renderer,
319{
320    fn size(&self) -> Size<Length> {
321        Size {
322            width: Length::Fit,
323            height: Length::Fit,
324        }
325    }
326
327    fn tag(&self) -> tree::Tag {
328        tree::Tag::of::<State<Renderer::Paragraph>>()
329    }
330
331    fn state(&self) -> tree::State {
332        tree::State::new(State::<Renderer::Paragraph>::default())
333    }
334
335    fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits) {
336        let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
337
338        tree.size = layout(
339            &mut state.paragraph,
340            renderer,
341            limits,
342            self,
343            Format::default(),
344        );
345    }
346
347    fn draw(
348        &self,
349        tree: &Tree,
350        renderer: &mut Renderer,
351        theme: &Theme,
352        defaults: &renderer::Style,
353        layout: Layout,
354        _cursor_position: mouse::Cursor,
355        viewport: &Rectangle,
356    ) {
357        let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
358        let style = theme.style(&Theme::default());
359
360        draw(
361            renderer,
362            defaults,
363            layout.bounds(),
364            state.paragraph.raw(),
365            style,
366            theme.selection(),
367            viewport,
368        );
369    }
370
371    fn operate(
372        &mut self,
373        tree: &mut Tree,
374        layout: Layout,
375        _viewport: &Rectangle,
376        _renderer: &Renderer,
377        operation: &mut dyn super::Operation,
378    ) {
379        let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
380        operation.text(
381            None,
382            layout.bounds(),
383            &mut Operand {
384                paragraph: &mut state.paragraph,
385                layout,
386                selectable: false,
387            },
388        );
389    }
390}
391
392#[derive(Default)]
393struct State<P: Paragraph> {
394    paragraph: paragraph::Plain<P>,
395    is_hovered: bool,
396}
397
398/// The state of a widget with text, operated on by [`super::Operation::text`].
399pub struct Operand<'a, P: Paragraph> {
400    /// The [`Paragraph`] of the widget.
401    pub paragraph: &'a mut paragraph::Plain<P>,
402
403    /// The layout of the widget.
404    pub layout: Layout,
405
406    /// Whether the text of the widget can be selected.
407    pub selectable: bool,
408}
409
410impl<P: Paragraph> operation::Text for Operand<'_, P> {
411    fn text(&self) -> text::Fragment<'_> {
412        self.paragraph.content().into()
413    }
414
415    fn select(&mut self, start: Point, end: Point, target: Target) {
416        if !self.selectable {
417            return;
418        }
419
420        let anchor = self.layout.bounds().anchor(
421            self.paragraph.min_bounds(),
422            self.paragraph.align_x(),
423            self.paragraph.align_y(),
424        );
425
426        let translation = anchor - Point::ORIGIN;
427
428        self.paragraph
429            .raw_mut()
430            .select(start - translation, end - translation, target);
431    }
432
433    fn select_all(&mut self) {
434        if !self.selectable {
435            return;
436        }
437
438        self.paragraph.raw_mut().select_all();
439    }
440
441    fn deselect(&mut self) {
442        self.paragraph.raw_mut().deselect();
443    }
444
445    fn copy(&mut self) -> Option<String> {
446        self.paragraph.raw_mut().copy()
447    }
448}
449
450/// The format of some [`Text`].
451///
452/// Check out the methods of the [`Text`] widget
453/// to learn more about each field.
454#[derive(Debug, Clone, Copy)]
455#[allow(missing_docs)]
456pub struct Format {
457    pub width: Length,
458    pub height: Length,
459    pub size: Option<Pixels>,
460    pub font: Option<Font>,
461    pub line_height: Option<LineHeight>,
462    pub align_x: text::Alignment,
463    pub align_y: alignment::Vertical,
464    pub shaping: Shaping,
465    pub wrapping: Wrapping,
466    pub ellipsis: Ellipsis,
467}
468
469impl Default for Format {
470    fn default() -> Self {
471        Self {
472            size: None,
473            line_height: None,
474            font: None,
475            width: Length::Fit,
476            height: Length::Fit,
477            align_x: text::Alignment::Default,
478            align_y: alignment::Vertical::Top,
479            shaping: Shaping::default(),
480            wrapping: Wrapping::default(),
481            ellipsis: Ellipsis::default(),
482        }
483    }
484}
485
486fn update<P: Paragraph + 'static>(
487    tree: &mut Tree,
488    event: &crate::Event,
489    layout: Layout,
490    cursor: mouse::Cursor,
491) {
492    match event {
493        Event::Mouse(mouse::Event::CursorMoved { .. })
494        | Event::Window(window::Event::RedrawRequested(_)) => {
495            let state = tree.state.downcast_mut::<State<P>>();
496
497            let Some(position) = cursor.position_in(layout.bounds()) else {
498                state.is_hovered = false;
499                return;
500            };
501
502            let anchor = Rectangle::with_size(layout.size()).anchor(
503                state.paragraph.min_bounds(),
504                state.paragraph.align_x(),
505                state.paragraph.align_y(),
506            );
507
508            let translation = anchor - Point::ORIGIN;
509
510            state.is_hovered = state.paragraph.raw().hit_glyph(position - translation);
511        }
512        _ => {}
513    }
514}
515
516/// Computes the [`Size`] of a [`Text`] widget.
517pub fn layout<Renderer>(
518    paragraph: &mut paragraph::Plain<Renderer::Paragraph>,
519    renderer: &Renderer,
520    limits: &layout::Limits,
521    content: &str,
522    format: Format,
523) -> Size
524where
525    Renderer: text::Renderer,
526{
527    layout::sized(limits, format.width, format.height, |limits| {
528        let bounds = limits.bounds();
529
530        let size = format.size.unwrap_or_else(|| renderer.text_size());
531        let font = format.font.unwrap_or_else(|| renderer.font());
532        let line_height = format.line_height.unwrap_or_else(|| renderer.line_height());
533
534        let _ = paragraph.update(text::Text {
535            content,
536            bounds,
537            size,
538            line_height,
539            font,
540            align_x: format.align_x,
541            align_y: format.align_y,
542            shaping: format.shaping,
543            wrapping: format.wrapping,
544            ellipsis: format.ellipsis,
545            hint_factor: renderer.hint_factor(),
546        });
547
548        paragraph.min_bounds()
549    })
550}
551
552/// Draws text using the same logic as the [`Text`] widget.
553pub fn draw<Renderer>(
554    renderer: &mut Renderer,
555    style: &renderer::Style,
556    bounds: Rectangle,
557    paragraph: &Renderer::Paragraph,
558    appearance: Style,
559    selection_color: Color,
560    viewport: &Rectangle,
561) where
562    Renderer: text::Renderer,
563{
564    let anchor = bounds.anchor(
565        paragraph.min_bounds(),
566        paragraph.align_x(),
567        paragraph.align_y(),
568    );
569
570    renderer.fill_paragraph(
571        paragraph,
572        anchor,
573        appearance.color.unwrap_or(style.text_color),
574        *viewport,
575    );
576
577    let selection = paragraph.selection();
578
579    if !selection.is_empty() {
580        let translation = anchor - Point::ORIGIN;
581
582        for region in selection {
583            renderer.fill_quad(
584                renderer::Quad {
585                    bounds: *region + translation,
586                    ..renderer::Quad::default()
587                },
588                appearance.selection.unwrap_or(selection_color),
589            );
590        }
591    }
592}
593
594impl<'a, Theme> From<&'a str> for Text<'a, Theme>
595where
596    Theme: Catalog + 'a,
597{
598    fn from(content: &'a str) -> Self {
599        Self::new(content)
600    }
601}
602
603/// The appearance of some text.
604#[derive(Debug, Clone, Copy, PartialEq, Default)]
605pub struct Style {
606    /// The [`Color`] of the text.
607    ///
608    /// The default, `None`, means using the inherited color.
609    pub color: Option<Color>,
610
611    /// The [`Color`] of the selection, if any.
612    ///
613    /// The default, `None`, means using the global selection color.
614    pub selection: Option<Color>,
615}
616
617/// The theme catalog of a [`Text`].
618pub trait Catalog: Sized {
619    /// The item class of this [`Catalog`].
620    type Class<'a>;
621
622    /// The default class produced by this [`Catalog`].
623    fn default<'a>() -> Self::Class<'a>;
624
625    /// The [`Style`] of a class with the given status.
626    fn style(&self, item: &Self::Class<'_>) -> Style;
627
628    /// The global selection [`Color`].
629    fn selection(&self) -> Color;
630}
631
632/// A styling function for a [`Text`].
633///
634/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
635pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
636
637impl Catalog for Theme {
638    type Class<'a> = StyleFn<'a, Self>;
639
640    fn default<'a>() -> Self::Class<'a> {
641        Box::new(|_theme| Style::default())
642    }
643
644    fn selection(&self) -> Color {
645        self.palette().background.strongest.color
646    }
647
648    fn style(&self, class: &Self::Class<'_>) -> Style {
649        class(self)
650    }
651}
652
653/// The default text styling; color is inherited.
654pub fn default(_theme: &Theme) -> Style {
655    Style {
656        color: None,
657        selection: None,
658    }
659}
660
661/// Text with the default base color.
662pub fn base(theme: &Theme) -> Style {
663    Style {
664        color: Some(theme.seed().text),
665        selection: None,
666    }
667}
668
669/// Text conveying some important information, like an action.
670pub fn primary(theme: &Theme) -> Style {
671    Style {
672        color: Some(theme.seed().primary),
673        selection: None,
674    }
675}
676
677/// Text conveying some secondary information, like a footnote.
678pub fn secondary(theme: &Theme) -> Style {
679    Style {
680        color: Some(theme.palette().secondary.base.color),
681        selection: None,
682    }
683}
684
685/// Text conveying some positive information, like a successful event.
686pub fn success(theme: &Theme) -> Style {
687    Style {
688        color: Some(theme.seed().success),
689        selection: None,
690    }
691}
692
693/// Text conveying some mildly negative information, like a warning.
694pub fn warning(theme: &Theme) -> Style {
695    Style {
696        color: Some(theme.seed().warning),
697        selection: None,
698    }
699}
700
701/// Text conveying some negative information, like an error.
702pub fn danger(theme: &Theme) -> Style {
703    Style {
704        color: Some(theme.seed().danger),
705        selection: None,
706    }
707}