iced_core/
text.rs

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