Skip to main content

iced_core/
text.rs

1//! Draw and interact with text.
2
3pub mod editor;
4pub mod highlighter;
5pub mod input;
6pub mod paragraph;
7pub mod parser;
8
9pub use editor::Editor;
10pub use highlighter::Highlighter;
11pub use input::Input;
12pub use paragraph::Paragraph;
13pub use parser::Parser;
14
15use crate::alignment;
16use crate::{Background, Border, Color, Font, Padding, Pixels, Point, Rectangle, Size};
17
18use std::borrow::Cow;
19use std::hash::{Hash, Hasher};
20
21/// A paragraph.
22#[derive(Debug, Clone, Copy)]
23pub struct Text<Content = String> {
24    /// The content of the paragraph.
25    pub content: Content,
26
27    /// The bounds of the paragraph.
28    pub bounds: Size,
29
30    /// The size of the [`Text`] in logical pixels.
31    pub size: Pixels,
32
33    /// The line height of the [`Text`].
34    pub line_height: LineHeight,
35
36    /// The [`Font`] of the [`Text`].
37    pub font: Font,
38
39    /// The horizontal alignment of the [`Text`].
40    pub align_x: Alignment,
41
42    /// The vertical alignment of the [`Text`].
43    pub align_y: alignment::Vertical,
44
45    /// The [`Shaping`] strategy of the [`Text`].
46    pub shaping: Shaping,
47
48    /// The [`Wrapping`] strategy of the [`Text`].
49    pub wrapping: Wrapping,
50
51    /// The [`Ellipsis`] strategy of the [`Text`].
52    pub ellipsis: Ellipsis,
53
54    /// The scale factor that may be used to internally scale the layout
55    /// calculation of the [`Paragraph`] and leverage metrics hinting.
56    ///
57    /// Effectively, this defines the "base" layout that will be used for
58    /// linear scaling.
59    ///
60    /// If `None`, hinting will be disabled and subpixel positioning will be
61    /// performed.
62    pub hint_factor: Option<f32>,
63}
64
65impl<Content> Text<Content> {
66    /// Returns a new [`Text`] replacing only the content with the
67    /// given value.
68    pub fn with_content<T>(&self, content: T) -> Text<T> {
69        Text {
70            content,
71            bounds: self.bounds,
72            size: self.size,
73            line_height: self.line_height,
74            font: self.font,
75            align_x: self.align_x,
76            align_y: self.align_y,
77            shaping: self.shaping,
78            wrapping: self.wrapping,
79            ellipsis: self.ellipsis,
80            hint_factor: self.hint_factor,
81        }
82    }
83}
84
85impl<Content: AsRef<str>> Text<Content> {
86    /// Returns a borrowed version of [`Text`].
87    pub fn as_ref(&self) -> Text<&str> {
88        self.with_content(self.content.as_ref())
89    }
90}
91
92/// The alignment of some text.
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
94pub enum Alignment {
95    /// No specific alignment.
96    ///
97    /// Left-to-right text will be aligned to the left, while
98    /// right-to-left text will be aligned to the right.
99    #[default]
100    Default,
101    /// Align text to the left.
102    Left,
103    /// Center text.
104    Center,
105    /// Align text to the right.
106    Right,
107    /// Justify text.
108    Justified,
109}
110
111impl From<alignment::Horizontal> for Alignment {
112    fn from(alignment: alignment::Horizontal) -> Self {
113        match alignment {
114            alignment::Horizontal::Left => Self::Left,
115            alignment::Horizontal::Center => Self::Center,
116            alignment::Horizontal::Right => Self::Right,
117        }
118    }
119}
120
121impl From<crate::Alignment> for Alignment {
122    fn from(alignment: crate::Alignment) -> Self {
123        match alignment {
124            crate::Alignment::Start => Self::Left,
125            crate::Alignment::Center => Self::Center,
126            crate::Alignment::End => Self::Right,
127        }
128    }
129}
130
131impl From<Alignment> for alignment::Horizontal {
132    fn from(alignment: Alignment) -> Self {
133        match alignment {
134            Alignment::Default | Alignment::Left | Alignment::Justified => {
135                alignment::Horizontal::Left
136            }
137            Alignment::Center => alignment::Horizontal::Center,
138            Alignment::Right => alignment::Horizontal::Right,
139        }
140    }
141}
142
143/// The shaping strategy of some text.
144#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
145pub enum Shaping {
146    /// Auto-detect the best shaping strategy from the text.
147    ///
148    /// This strategy will use [`Basic`](Self::Basic) shaping if the
149    /// text consists of only ASCII characters; otherwise, it will
150    /// use [`Advanced`](Self::Advanced) shaping.
151    ///
152    /// This is the default, if neither the `basic-shaping` nor `advanced-shaping`
153    /// features are enabled.
154    Auto,
155    /// No shaping and no font fallback.
156    ///
157    /// This shaping strategy is very cheap, but it will not display complex
158    /// scripts properly nor try to find missing glyphs in your system fonts.
159    ///
160    /// You should use this strategy when you have complete control of the text
161    /// and the font you are displaying in your application.
162    ///
163    /// This will be the default if the `basic-shaping` feature is enabled and
164    /// the `advanced-shaping` feature is disabled.
165    Basic,
166    /// Advanced text shaping and font fallback.
167    ///
168    /// You will need to enable this flag if the text contains a complex
169    /// script, the font used needs it, and/or multiple fonts in your system
170    /// may be needed to display all of the glyphs.
171    ///
172    /// Advanced shaping is expensive! You should only enable it when necessary.
173    ///
174    /// This will be the default if the `advanced-shaping` feature is enabled.
175    Advanced,
176}
177
178impl Default for Shaping {
179    fn default() -> Self {
180        if cfg!(feature = "advanced-shaping") {
181            Self::Advanced
182        } else if cfg!(feature = "basic-shaping") {
183            Self::Basic
184        } else {
185            Self::Auto
186        }
187    }
188}
189
190/// The wrapping strategy of some text.
191#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
192pub enum Wrapping {
193    /// No wrapping.
194    None,
195    /// Wraps at the word level.
196    ///
197    /// This is the default.
198    #[default]
199    Word,
200    /// Wraps at the glyph level.
201    Glyph,
202    /// Wraps at the word level, or fallback to glyph level if a word can't fit on a line by itself.
203    WordOrGlyph,
204}
205
206#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
207/// The ellipsis strategy of some text.
208pub enum Ellipsis {
209    /// No ellipsis.
210    ///
211    /// This is the default.
212    #[default]
213    None,
214    /// Ellipsize the start of the last visual line in the text.
215    Start,
216    /// Ellipsize the middle of the last visual line in the text.
217    Middle,
218    /// Ellipsize the end of the last visual line in the text.
219    End,
220}
221
222/// The height of a line of text in a paragraph.
223#[derive(Debug, Clone, Copy, PartialEq)]
224pub enum LineHeight {
225    /// A factor of the size of the text.
226    Relative(f32),
227
228    /// An absolute height in logical pixels.
229    Absolute(Pixels),
230}
231
232impl LineHeight {
233    /// Returns the [`LineHeight`] in absolute logical pixels.
234    pub fn to_absolute(self, text_size: impl Into<Pixels>) -> Pixels {
235        match self {
236            Self::Relative(factor) => Pixels(factor * text_size.into().0),
237            Self::Absolute(pixels) => pixels,
238        }
239    }
240}
241
242impl Default for LineHeight {
243    fn default() -> Self {
244        Self::Relative(1.375)
245    }
246}
247
248impl From<f32> for LineHeight {
249    fn from(factor: f32) -> Self {
250        Self::Relative(factor)
251    }
252}
253
254impl From<Pixels> for LineHeight {
255    fn from(pixels: Pixels) -> Self {
256        Self::Absolute(pixels)
257    }
258}
259
260impl Hash for LineHeight {
261    fn hash<H: Hasher>(&self, state: &mut H) {
262        match self {
263            Self::Relative(factor) => {
264                state.write_u8(0);
265                factor.to_bits().hash(state);
266            }
267            Self::Absolute(pixels) => {
268                state.write_u8(1);
269                f32::from(*pixels).to_bits().hash(state);
270            }
271        }
272    }
273}
274
275/// The result of hit testing on text.
276#[derive(Debug, Clone, Copy, PartialEq)]
277pub struct Hit {
278    /// The line of the hit.
279    pub line: usize,
280    /// The first byte index of the closest glyph.
281    pub index: usize,
282}
283
284/// The difference detected in some text.
285///
286/// You will obtain a [`Difference`] when you [`compare`] a [`Paragraph`] with some
287/// [`Text`].
288///
289/// [`compare`]: Paragraph::compare
290#[derive(Debug, Clone, Copy, PartialEq, Eq)]
291pub enum Difference {
292    /// No difference.
293    ///
294    /// The text can be reused as it is!
295    None,
296
297    /// A bounds difference.
298    ///
299    /// This normally means a relayout is necessary, but the shape of the text can
300    /// be reused.
301    Bounds,
302
303    /// A shape difference.
304    ///
305    /// The contents, alignment, sizes, fonts, or any other essential attributes
306    /// of the shape of the text have changed. A complete reshape and relayout of
307    /// the text is necessary.
308    Shape,
309}
310
311/// A renderer capable of measuring and drawing [`Text`].
312pub trait Renderer: crate::Renderer {
313    /// The [`Paragraph`] of this [`Renderer`].
314    type Paragraph: Paragraph + 'static;
315
316    /// The [`Editor`] of this [`Renderer`].
317    type Editor: Editor + 'static;
318
319    /// The icon font of the backend.
320    const ICON_FONT: Font;
321
322    /// The `char` representing a ✔ icon in the [`ICON_FONT`].
323    ///
324    /// [`ICON_FONT`]: Self::ICON_FONT
325    const CHECKMARK_ICON: char;
326
327    /// The `char` representing a ▼ icon in the built-in [`ICON_FONT`].
328    ///
329    /// [`ICON_FONT`]: Self::ICON_FONT
330    const ARROW_DOWN_ICON: char;
331
332    /// The `char` representing a ^ icon in the built-in [`ICON_FONT`].
333    ///
334    /// [`ICON_FONT`]: Self::ICON_FONT
335    const SCROLL_UP_ICON: char;
336
337    /// The `char` representing a v icon in the built-in [`ICON_FONT`].
338    ///
339    /// [`ICON_FONT`]: Self::ICON_FONT
340    const SCROLL_DOWN_ICON: char;
341
342    /// The `char` representing a < icon in the built-in [`ICON_FONT`].
343    ///
344    /// [`ICON_FONT`]: Self::ICON_FONT
345    const SCROLL_LEFT_ICON: char;
346
347    /// The `char` representing a > icon in the built-in [`ICON_FONT`].
348    ///
349    /// [`ICON_FONT`]: Self::ICON_FONT
350    const SCROLL_RIGHT_ICON: char;
351
352    /// The 'char' representing the iced logo in the built-in ['ICON_FONT'].
353    ///
354    /// ['ICON_FONT']: Self::ICON_FONT
355    const ICED_LOGO: char;
356
357    /// Returns the default [`Font`].
358    fn font(&self) -> Font {
359        self.settings().font
360    }
361
362    /// Returns the default size of [`Text`].
363    fn text_size(&self) -> Pixels {
364        self.settings().text_size
365    }
366
367    /// Returns the default line height of [`Text`].
368    fn line_height(&self) -> LineHeight {
369        self.settings().line_height
370    }
371
372    /// Draws the given [`Paragraph`] at the given position and with the given
373    /// [`Color`].
374    fn fill_paragraph(
375        &mut self,
376        text: &Self::Paragraph,
377        position: Point,
378        color: Color,
379        clip_bounds: Rectangle,
380    );
381
382    /// Draws the given [`Editor`] at the given position and with the given
383    /// [`Color`].
384    fn fill_editor(
385        &mut self,
386        editor: &Self::Editor,
387        position: Point,
388        color: Color,
389        clip_bounds: Rectangle,
390    );
391
392    /// Draws the given [`Text`] at the given position and with the given
393    /// [`Color`].
394    fn fill_text(
395        &mut self,
396        text: Text<String>,
397        position: Point,
398        color: Color,
399        clip_bounds: Rectangle,
400    );
401}
402
403/// A span of text.
404#[derive(Debug, Clone)]
405pub struct Span<'a, Link = ()> {
406    /// The [`Fragment`] of text.
407    pub text: Fragment<'a>,
408    /// The size of the [`Span`] in [`Pixels`].
409    pub size: Option<Pixels>,
410    /// The [`LineHeight`] of the [`Span`].
411    pub line_height: Option<LineHeight>,
412    /// The [`Font`] of the [`Span`].
413    pub font: Option<Font>,
414    /// The [`Color`] of the [`Span`].
415    pub color: Option<Color>,
416    /// The link of the [`Span`].
417    pub link: Option<Link>,
418    /// The [`Highlight`] of the [`Span`].
419    pub highlight: Option<Highlight>,
420    /// The [`Padding`] of the [`Span`].
421    ///
422    /// Currently, it only affects the bounds of the [`Highlight`].
423    pub padding: Padding,
424    /// Whether the [`Span`] should be underlined or not.
425    pub underline: bool,
426    /// Whether the [`Span`] should be struck through or not.
427    pub strikethrough: bool,
428}
429
430/// A text highlight.
431#[derive(Debug, Clone, Copy, PartialEq)]
432pub struct Highlight {
433    /// The [`Background`] of the highlight.
434    pub background: Background,
435    /// The [`Border`] of the highlight.
436    pub border: Border,
437}
438
439impl<'a, Link> Span<'a, Link> {
440    /// Creates a new [`Span`] of text with the given text fragment.
441    pub fn new(fragment: impl IntoFragment<'a>) -> Self {
442        Self {
443            text: fragment.into_fragment(),
444            ..Self::default()
445        }
446    }
447
448    /// Sets the size of the [`Span`].
449    pub fn size(mut self, size: impl Into<Pixels>) -> Self {
450        self.size = Some(size.into());
451        self
452    }
453
454    /// Sets the [`LineHeight`] of the [`Span`].
455    pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
456        self.line_height = Some(line_height.into());
457        self
458    }
459
460    /// Sets the font of the [`Span`].
461    pub fn font(mut self, font: impl Into<Font>) -> Self {
462        self.font = Some(font.into());
463        self
464    }
465
466    /// Sets the font of the [`Span`], if any.
467    pub fn font_maybe(mut self, font: Option<impl Into<Font>>) -> Self {
468        self.font = font.map(Into::into);
469        self
470    }
471
472    /// Sets the [`Color`] of the [`Span`].
473    pub fn color(mut self, color: impl Into<Color>) -> Self {
474        self.color = Some(color.into());
475        self
476    }
477
478    /// Sets the [`Color`] of the [`Span`], if any.
479    pub fn color_maybe(mut self, color: Option<impl Into<Color>>) -> Self {
480        self.color = color.map(Into::into);
481        self
482    }
483
484    /// Sets the link of the [`Span`].
485    pub fn link(mut self, link: impl Into<Link>) -> Self {
486        self.link = Some(link.into());
487        self
488    }
489
490    /// Sets the link of the [`Span`], if any.
491    pub fn link_maybe(mut self, link: Option<impl Into<Link>>) -> Self {
492        self.link = link.map(Into::into);
493        self
494    }
495
496    /// Sets the [`Background`] of the [`Span`].
497    pub fn background(self, background: impl Into<Background>) -> Self {
498        self.background_maybe(Some(background))
499    }
500
501    /// Sets the [`Background`] of the [`Span`], if any.
502    pub fn background_maybe(mut self, background: Option<impl Into<Background>>) -> Self {
503        let Some(background) = background else {
504            return self;
505        };
506
507        match &mut self.highlight {
508            Some(highlight) => {
509                highlight.background = background.into();
510            }
511            None => {
512                self.highlight = Some(Highlight {
513                    background: background.into(),
514                    border: Border::default(),
515                });
516            }
517        }
518
519        self
520    }
521
522    /// Sets the [`Border`] of the [`Span`].
523    pub fn border(self, border: impl Into<Border>) -> Self {
524        self.border_maybe(Some(border))
525    }
526
527    /// Sets the [`Border`] of the [`Span`], if any.
528    pub fn border_maybe(mut self, border: Option<impl Into<Border>>) -> Self {
529        let Some(border) = border else {
530            return self;
531        };
532
533        match &mut self.highlight {
534            Some(highlight) => {
535                highlight.border = border.into();
536            }
537            None => {
538                self.highlight = Some(Highlight {
539                    border: border.into(),
540                    background: Background::Color(Color::TRANSPARENT),
541                });
542            }
543        }
544
545        self
546    }
547
548    /// Sets the [`Padding`] of the [`Span`].
549    ///
550    /// It only affects the [`background`] and [`border`] of the
551    /// [`Span`], currently.
552    ///
553    /// [`background`]: Self::background
554    /// [`border`]: Self::border
555    pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
556        self.padding = padding.into();
557        self
558    }
559
560    /// Sets whether the [`Span`] should be underlined or not.
561    pub fn underline(mut self, underline: bool) -> Self {
562        self.underline = underline;
563        self
564    }
565
566    /// Sets whether the [`Span`] should be struck through or not.
567    pub fn strikethrough(mut self, strikethrough: bool) -> Self {
568        self.strikethrough = strikethrough;
569        self
570    }
571
572    /// Turns the [`Span`] into a static one.
573    pub fn to_static(self) -> Span<'static, Link> {
574        Span {
575            text: Cow::Owned(self.text.into_owned()),
576            size: self.size,
577            line_height: self.line_height,
578            font: self.font,
579            color: self.color,
580            link: self.link,
581            highlight: self.highlight,
582            padding: self.padding,
583            underline: self.underline,
584            strikethrough: self.strikethrough,
585        }
586    }
587}
588
589impl<Link> Default for Span<'_, Link> {
590    fn default() -> Self {
591        Self {
592            text: Cow::default(),
593            size: None,
594            line_height: None,
595            font: None,
596            color: None,
597            link: None,
598            highlight: None,
599            padding: Padding::default(),
600            underline: false,
601            strikethrough: false,
602        }
603    }
604}
605
606impl<'a, Link> From<&'a str> for Span<'a, Link> {
607    fn from(value: &'a str) -> Self {
608        Span::new(value)
609    }
610}
611
612impl<Link> PartialEq for Span<'_, Link> {
613    fn eq(&self, other: &Self) -> bool {
614        self.text == other.text
615            && self.size == other.size
616            && self.line_height == other.line_height
617            && self.font == other.font
618            && self.color == other.color
619    }
620}
621
622/// A specific position in some [`Text`].
623#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
624pub struct Position {
625    /// The line of the [`Text`].
626    pub line: usize,
627
628    /// The first byte index of a character boundary in the line.
629    pub index: usize,
630}
631
632/// The granularity of a text selection.
633///
634/// The target determines how a selection is expanded around its
635/// starting position:
636///
637/// - [`Target::Character`] selects the individual characters between
638///   the given positions;
639/// - [`Target::Word`] expands the selection to the nearest whole words;
640/// - [`Target::Line`] expands the selection to the nearest whole lines.
641#[derive(Debug, Clone, Copy, PartialEq, Eq)]
642pub enum Target {
643    /// Selects individual characters.
644    Character,
645    /// Selects whole words.
646    Word,
647    /// Selects whole lines.
648    Line,
649}
650
651/// A fragment of [`Text`].
652///
653/// This is just an alias to a string that may be either
654/// borrowed or owned.
655pub type Fragment<'a> = Cow<'a, str>;
656
657/// A trait for converting a value to some text [`Fragment`].
658pub trait IntoFragment<'a> {
659    /// Converts the value to some text [`Fragment`].
660    fn into_fragment(self) -> Fragment<'a>;
661}
662
663impl<'a> IntoFragment<'a> for Fragment<'a> {
664    fn into_fragment(self) -> Fragment<'a> {
665        self
666    }
667}
668
669impl<'a> IntoFragment<'a> for &'a Fragment<'_> {
670    fn into_fragment(self) -> Fragment<'a> {
671        Fragment::Borrowed(self)
672    }
673}
674
675impl<'a> IntoFragment<'a> for &'a str {
676    fn into_fragment(self) -> Fragment<'a> {
677        Fragment::Borrowed(self)
678    }
679}
680
681impl<'a> IntoFragment<'a> for &'a String {
682    fn into_fragment(self) -> Fragment<'a> {
683        Fragment::Borrowed(self.as_str())
684    }
685}
686
687impl<'a> IntoFragment<'a> for String {
688    fn into_fragment(self) -> Fragment<'a> {
689        Fragment::Owned(self)
690    }
691}
692
693macro_rules! into_fragment {
694    ($type:ty) => {
695        impl<'a> IntoFragment<'a> for $type {
696            fn into_fragment(self) -> Fragment<'a> {
697                Fragment::Owned(self.to_string())
698            }
699        }
700
701        impl<'a> IntoFragment<'a> for &$type {
702            fn into_fragment(self) -> Fragment<'a> {
703                Fragment::Owned(self.to_string())
704            }
705        }
706    };
707}
708
709into_fragment!(char);
710into_fragment!(bool);
711
712into_fragment!(u8);
713into_fragment!(u16);
714into_fragment!(u32);
715into_fragment!(u64);
716into_fragment!(u128);
717into_fragment!(usize);
718
719into_fragment!(i8);
720into_fragment!(i16);
721into_fragment!(i32);
722into_fragment!(i64);
723into_fragment!(i128);
724into_fragment!(isize);
725
726into_fragment!(f32);
727into_fragment!(f64);