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    /// A monospace font.
303    ///
304    /// It may be used by devtools.
305    const MONOSPACE_FONT: Self::Font;
306
307    /// The icon font of the backend.
308    const ICON_FONT: Self::Font;
309
310    /// The `char` representing a ✔ icon in the [`ICON_FONT`].
311    ///
312    /// [`ICON_FONT`]: Self::ICON_FONT
313    const CHECKMARK_ICON: char;
314
315    /// The `char` representing a ▼ icon in the built-in [`ICON_FONT`].
316    ///
317    /// [`ICON_FONT`]: Self::ICON_FONT
318    const ARROW_DOWN_ICON: char;
319
320    /// Returns the default [`Self::Font`].
321    fn default_font(&self) -> Self::Font;
322
323    /// Returns the default size of [`Text`].
324    fn default_size(&self) -> Pixels;
325
326    /// Draws the given [`Paragraph`] at the given position and with the given
327    /// [`Color`].
328    fn fill_paragraph(
329        &mut self,
330        text: &Self::Paragraph,
331        position: Point,
332        color: Color,
333        clip_bounds: Rectangle,
334    );
335
336    /// Draws the given [`Editor`] at the given position and with the given
337    /// [`Color`].
338    fn fill_editor(
339        &mut self,
340        editor: &Self::Editor,
341        position: Point,
342        color: Color,
343        clip_bounds: Rectangle,
344    );
345
346    /// Draws the given [`Text`] at the given position and with the given
347    /// [`Color`].
348    fn fill_text(
349        &mut self,
350        text: Text<String, Self::Font>,
351        position: Point,
352        color: Color,
353        clip_bounds: Rectangle,
354    );
355}
356
357/// A span of text.
358#[derive(Debug, Clone)]
359pub struct Span<'a, Link = (), Font = crate::Font> {
360    /// The [`Fragment`] of text.
361    pub text: Fragment<'a>,
362    /// The size of the [`Span`] in [`Pixels`].
363    pub size: Option<Pixels>,
364    /// The [`LineHeight`] of the [`Span`].
365    pub line_height: Option<LineHeight>,
366    /// The font of the [`Span`].
367    pub font: Option<Font>,
368    /// The [`Color`] of the [`Span`].
369    pub color: Option<Color>,
370    /// The link of the [`Span`].
371    pub link: Option<Link>,
372    /// The [`Highlight`] of the [`Span`].
373    pub highlight: Option<Highlight>,
374    /// The [`Padding`] of the [`Span`].
375    ///
376    /// Currently, it only affects the bounds of the [`Highlight`].
377    pub padding: Padding,
378    /// Whether the [`Span`] should be underlined or not.
379    pub underline: bool,
380    /// Whether the [`Span`] should be struck through or not.
381    pub strikethrough: bool,
382}
383
384/// A text highlight.
385#[derive(Debug, Clone, Copy, PartialEq)]
386pub struct Highlight {
387    /// The [`Background`] of the highlight.
388    pub background: Background,
389    /// The [`Border`] of the highlight.
390    pub border: Border,
391}
392
393impl<'a, Link, Font> Span<'a, Link, Font> {
394    /// Creates a new [`Span`] of text with the given text fragment.
395    pub fn new(fragment: impl IntoFragment<'a>) -> Self {
396        Self {
397            text: fragment.into_fragment(),
398            ..Self::default()
399        }
400    }
401
402    /// Sets the size of the [`Span`].
403    pub fn size(mut self, size: impl Into<Pixels>) -> Self {
404        self.size = Some(size.into());
405        self
406    }
407
408    /// Sets the [`LineHeight`] of the [`Span`].
409    pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
410        self.line_height = Some(line_height.into());
411        self
412    }
413
414    /// Sets the font of the [`Span`].
415    pub fn font(mut self, font: impl Into<Font>) -> Self {
416        self.font = Some(font.into());
417        self
418    }
419
420    /// Sets the font of the [`Span`], if any.
421    pub fn font_maybe(mut self, font: Option<impl Into<Font>>) -> Self {
422        self.font = font.map(Into::into);
423        self
424    }
425
426    /// Sets the [`Color`] of the [`Span`].
427    pub fn color(mut self, color: impl Into<Color>) -> Self {
428        self.color = Some(color.into());
429        self
430    }
431
432    /// Sets the [`Color`] of the [`Span`], if any.
433    pub fn color_maybe(mut self, color: Option<impl Into<Color>>) -> Self {
434        self.color = color.map(Into::into);
435        self
436    }
437
438    /// Sets the link of the [`Span`].
439    pub fn link(mut self, link: impl Into<Link>) -> Self {
440        self.link = Some(link.into());
441        self
442    }
443
444    /// Sets the link of the [`Span`], if any.
445    pub fn link_maybe(mut self, link: Option<impl Into<Link>>) -> Self {
446        self.link = link.map(Into::into);
447        self
448    }
449
450    /// Sets the [`Background`] of the [`Span`].
451    pub fn background(self, background: impl Into<Background>) -> Self {
452        self.background_maybe(Some(background))
453    }
454
455    /// Sets the [`Background`] of the [`Span`], if any.
456    pub fn background_maybe(
457        mut self,
458        background: Option<impl Into<Background>>,
459    ) -> Self {
460        let Some(background) = background else {
461            return self;
462        };
463
464        match &mut self.highlight {
465            Some(highlight) => {
466                highlight.background = background.into();
467            }
468            None => {
469                self.highlight = Some(Highlight {
470                    background: background.into(),
471                    border: Border::default(),
472                });
473            }
474        }
475
476        self
477    }
478
479    /// Sets the [`Border`] of the [`Span`].
480    pub fn border(self, border: impl Into<Border>) -> Self {
481        self.border_maybe(Some(border))
482    }
483
484    /// Sets the [`Border`] of the [`Span`], if any.
485    pub fn border_maybe(mut self, border: Option<impl Into<Border>>) -> Self {
486        let Some(border) = border else {
487            return self;
488        };
489
490        match &mut self.highlight {
491            Some(highlight) => {
492                highlight.border = border.into();
493            }
494            None => {
495                self.highlight = Some(Highlight {
496                    border: border.into(),
497                    background: Background::Color(Color::TRANSPARENT),
498                });
499            }
500        }
501
502        self
503    }
504
505    /// Sets the [`Padding`] of the [`Span`].
506    ///
507    /// It only affects the [`background`] and [`border`] of the
508    /// [`Span`], currently.
509    ///
510    /// [`background`]: Self::background
511    /// [`border`]: Self::border
512    pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
513        self.padding = padding.into();
514        self
515    }
516
517    /// Sets whether the [`Span`] should be underlined or not.
518    pub fn underline(mut self, underline: bool) -> Self {
519        self.underline = underline;
520        self
521    }
522
523    /// Sets whether the [`Span`] should be struck through or not.
524    pub fn strikethrough(mut self, strikethrough: bool) -> Self {
525        self.strikethrough = strikethrough;
526        self
527    }
528
529    /// Turns the [`Span`] into a static one.
530    pub fn to_static(self) -> Span<'static, Link, Font> {
531        Span {
532            text: Cow::Owned(self.text.into_owned()),
533            size: self.size,
534            line_height: self.line_height,
535            font: self.font,
536            color: self.color,
537            link: self.link,
538            highlight: self.highlight,
539            padding: self.padding,
540            underline: self.underline,
541            strikethrough: self.strikethrough,
542        }
543    }
544}
545
546impl<Link, Font> Default for Span<'_, Link, Font> {
547    fn default() -> Self {
548        Self {
549            text: Cow::default(),
550            size: None,
551            line_height: None,
552            font: None,
553            color: None,
554            link: None,
555            highlight: None,
556            padding: Padding::default(),
557            underline: false,
558            strikethrough: false,
559        }
560    }
561}
562
563impl<'a, Link, Font> From<&'a str> for Span<'a, Link, Font> {
564    fn from(value: &'a str) -> Self {
565        Span::new(value)
566    }
567}
568
569impl<Link, Font: PartialEq> PartialEq for Span<'_, Link, Font> {
570    fn eq(&self, other: &Self) -> bool {
571        self.text == other.text
572            && self.size == other.size
573            && self.line_height == other.line_height
574            && self.font == other.font
575            && self.color == other.color
576    }
577}
578
579/// A fragment of [`Text`].
580///
581/// This is just an alias to a string that may be either
582/// borrowed or owned.
583pub type Fragment<'a> = Cow<'a, str>;
584
585/// A trait for converting a value to some text [`Fragment`].
586pub trait IntoFragment<'a> {
587    /// Converts the value to some text [`Fragment`].
588    fn into_fragment(self) -> Fragment<'a>;
589}
590
591impl<'a> IntoFragment<'a> for Fragment<'a> {
592    fn into_fragment(self) -> Fragment<'a> {
593        self
594    }
595}
596
597impl<'a> IntoFragment<'a> for &'a Fragment<'_> {
598    fn into_fragment(self) -> Fragment<'a> {
599        Fragment::Borrowed(self)
600    }
601}
602
603impl<'a> IntoFragment<'a> for &'a str {
604    fn into_fragment(self) -> Fragment<'a> {
605        Fragment::Borrowed(self)
606    }
607}
608
609impl<'a> IntoFragment<'a> for &'a String {
610    fn into_fragment(self) -> Fragment<'a> {
611        Fragment::Borrowed(self.as_str())
612    }
613}
614
615impl<'a> IntoFragment<'a> for String {
616    fn into_fragment(self) -> Fragment<'a> {
617        Fragment::Owned(self)
618    }
619}
620
621macro_rules! into_fragment {
622    ($type:ty) => {
623        impl<'a> IntoFragment<'a> for $type {
624            fn into_fragment(self) -> Fragment<'a> {
625                Fragment::Owned(self.to_string())
626            }
627        }
628
629        impl<'a> IntoFragment<'a> for &$type {
630            fn into_fragment(self) -> Fragment<'a> {
631                Fragment::Owned(self.to_string())
632            }
633        }
634    };
635}
636
637into_fragment!(char);
638into_fragment!(bool);
639
640into_fragment!(u8);
641into_fragment!(u16);
642into_fragment!(u32);
643into_fragment!(u64);
644into_fragment!(u128);
645into_fragment!(usize);
646
647into_fragment!(i8);
648into_fragment!(i16);
649into_fragment!(i32);
650into_fragment!(i64);
651into_fragment!(i128);
652into_fragment!(isize);
653
654into_fragment!(f32);
655into_fragment!(f64);