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 horizontal_alignment: alignment::Horizontal,
38
39    /// The vertical alignment of the [`Text`].
40    pub vertical_alignment: 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
49/// The shaping strategy of some text.
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
51pub enum Shaping {
52    /// No shaping and no font fallback.
53    ///
54    /// This shaping strategy is very cheap, but it will not display complex
55    /// scripts properly nor try to find missing glyphs in your system fonts.
56    ///
57    /// You should use this strategy when you have complete control of the text
58    /// and the font you are displaying in your application.
59    ///
60    /// This is the default.
61    #[default]
62    Basic,
63    /// Advanced text shaping and font fallback.
64    ///
65    /// You will need to enable this flag if the text contains a complex
66    /// script, the font used needs it, and/or multiple fonts in your system
67    /// may be needed to display all of the glyphs.
68    ///
69    /// Advanced shaping is expensive! You should only enable it when necessary.
70    Advanced,
71}
72
73/// The wrapping strategy of some text.
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
75pub enum Wrapping {
76    /// No wrapping.
77    None,
78    /// Wraps at the word level.
79    ///
80    /// This is the default.
81    #[default]
82    Word,
83    /// Wraps at the glyph level.
84    Glyph,
85    /// Wraps at the word level, or fallback to glyph level if a word can't fit on a line by itself.
86    WordOrGlyph,
87}
88
89/// The height of a line of text in a paragraph.
90#[derive(Debug, Clone, Copy, PartialEq)]
91pub enum LineHeight {
92    /// A factor of the size of the text.
93    Relative(f32),
94
95    /// An absolute height in logical pixels.
96    Absolute(Pixels),
97}
98
99impl LineHeight {
100    /// Returns the [`LineHeight`] in absolute logical pixels.
101    pub fn to_absolute(self, text_size: Pixels) -> Pixels {
102        match self {
103            Self::Relative(factor) => Pixels(factor * text_size.0),
104            Self::Absolute(pixels) => pixels,
105        }
106    }
107}
108
109impl Default for LineHeight {
110    fn default() -> Self {
111        Self::Relative(1.3)
112    }
113}
114
115impl From<f32> for LineHeight {
116    fn from(factor: f32) -> Self {
117        Self::Relative(factor)
118    }
119}
120
121impl From<Pixels> for LineHeight {
122    fn from(pixels: Pixels) -> Self {
123        Self::Absolute(pixels)
124    }
125}
126
127impl Hash for LineHeight {
128    fn hash<H: Hasher>(&self, state: &mut H) {
129        match self {
130            Self::Relative(factor) => {
131                state.write_u8(0);
132                factor.to_bits().hash(state);
133            }
134            Self::Absolute(pixels) => {
135                state.write_u8(1);
136                f32::from(*pixels).to_bits().hash(state);
137            }
138        }
139    }
140}
141
142/// The result of hit testing on text.
143#[derive(Debug, Clone, Copy, PartialEq)]
144pub enum Hit {
145    /// The point was within the bounds of the returned character index.
146    CharOffset(usize),
147}
148
149impl Hit {
150    /// Computes the cursor position of the [`Hit`] .
151    pub fn cursor(self) -> usize {
152        match self {
153            Self::CharOffset(i) => i,
154        }
155    }
156}
157
158/// The difference detected in some text.
159///
160/// You will obtain a [`Difference`] when you [`compare`] a [`Paragraph`] with some
161/// [`Text`].
162///
163/// [`compare`]: Paragraph::compare
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165pub enum Difference {
166    /// No difference.
167    ///
168    /// The text can be reused as it is!
169    None,
170
171    /// A bounds difference.
172    ///
173    /// This normally means a relayout is necessary, but the shape of the text can
174    /// be reused.
175    Bounds,
176
177    /// A shape difference.
178    ///
179    /// The contents, alignment, sizes, fonts, or any other essential attributes
180    /// of the shape of the text have changed. A complete reshape and relayout of
181    /// the text is necessary.
182    Shape,
183}
184
185/// A renderer capable of measuring and drawing [`Text`].
186pub trait Renderer: crate::Renderer {
187    /// The font type used.
188    type Font: Copy + PartialEq;
189
190    /// The [`Paragraph`] of this [`Renderer`].
191    type Paragraph: Paragraph<Font = Self::Font> + 'static;
192
193    /// The [`Editor`] of this [`Renderer`].
194    type Editor: Editor<Font = Self::Font> + 'static;
195
196    /// The icon font of the backend.
197    const ICON_FONT: Self::Font;
198
199    /// The `char` representing a ✔ icon in the [`ICON_FONT`].
200    ///
201    /// [`ICON_FONT`]: Self::ICON_FONT
202    const CHECKMARK_ICON: char;
203
204    /// The `char` representing a ▼ icon in the built-in [`ICON_FONT`].
205    ///
206    /// [`ICON_FONT`]: Self::ICON_FONT
207    const ARROW_DOWN_ICON: char;
208
209    /// Returns the default [`Self::Font`].
210    fn default_font(&self) -> Self::Font;
211
212    /// Returns the default size of [`Text`].
213    fn default_size(&self) -> Pixels;
214
215    /// Draws the given [`Paragraph`] at the given position and with the given
216    /// [`Color`].
217    fn fill_paragraph(
218        &mut self,
219        text: &Self::Paragraph,
220        position: Point,
221        color: Color,
222        clip_bounds: Rectangle,
223    );
224
225    /// Draws the given [`Editor`] at the given position and with the given
226    /// [`Color`].
227    fn fill_editor(
228        &mut self,
229        editor: &Self::Editor,
230        position: Point,
231        color: Color,
232        clip_bounds: Rectangle,
233    );
234
235    /// Draws the given [`Text`] at the given position and with the given
236    /// [`Color`].
237    fn fill_text(
238        &mut self,
239        text: Text<String, Self::Font>,
240        position: Point,
241        color: Color,
242        clip_bounds: Rectangle,
243    );
244}
245
246/// A span of text.
247#[derive(Debug, Clone)]
248pub struct Span<'a, Link = (), Font = crate::Font> {
249    /// The [`Fragment`] of text.
250    pub text: Fragment<'a>,
251    /// The size of the [`Span`] in [`Pixels`].
252    pub size: Option<Pixels>,
253    /// The [`LineHeight`] of the [`Span`].
254    pub line_height: Option<LineHeight>,
255    /// The font of the [`Span`].
256    pub font: Option<Font>,
257    /// The [`Color`] of the [`Span`].
258    pub color: Option<Color>,
259    /// The link of the [`Span`].
260    pub link: Option<Link>,
261    /// The [`Highlight`] of the [`Span`].
262    pub highlight: Option<Highlight>,
263    /// The [`Padding`] of the [`Span`].
264    ///
265    /// Currently, it only affects the bounds of the [`Highlight`].
266    pub padding: Padding,
267    /// Whether the [`Span`] should be underlined or not.
268    pub underline: bool,
269    /// Whether the [`Span`] should be struck through or not.
270    pub strikethrough: bool,
271}
272
273/// A text highlight.
274#[derive(Debug, Clone, Copy, PartialEq)]
275pub struct Highlight {
276    /// The [`Background`] of the highlight.
277    pub background: Background,
278    /// The [`Border`] of the highlight.
279    pub border: Border,
280}
281
282impl<'a, Link, Font> Span<'a, Link, Font> {
283    /// Creates a new [`Span`] of text with the given text fragment.
284    pub fn new(fragment: impl IntoFragment<'a>) -> Self {
285        Self {
286            text: fragment.into_fragment(),
287            ..Self::default()
288        }
289    }
290
291    /// Sets the size of the [`Span`].
292    pub fn size(mut self, size: impl Into<Pixels>) -> Self {
293        self.size = Some(size.into());
294        self
295    }
296
297    /// Sets the [`LineHeight`] of the [`Span`].
298    pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
299        self.line_height = Some(line_height.into());
300        self
301    }
302
303    /// Sets the font of the [`Span`].
304    pub fn font(mut self, font: impl Into<Font>) -> Self {
305        self.font = Some(font.into());
306        self
307    }
308
309    /// Sets the font of the [`Span`], if any.
310    pub fn font_maybe(mut self, font: Option<impl Into<Font>>) -> Self {
311        self.font = font.map(Into::into);
312        self
313    }
314
315    /// Sets the [`Color`] of the [`Span`].
316    pub fn color(mut self, color: impl Into<Color>) -> Self {
317        self.color = Some(color.into());
318        self
319    }
320
321    /// Sets the [`Color`] of the [`Span`], if any.
322    pub fn color_maybe(mut self, color: Option<impl Into<Color>>) -> Self {
323        self.color = color.map(Into::into);
324        self
325    }
326
327    /// Sets the link of the [`Span`].
328    pub fn link(mut self, link: impl Into<Link>) -> Self {
329        self.link = Some(link.into());
330        self
331    }
332
333    /// Sets the link of the [`Span`], if any.
334    pub fn link_maybe(mut self, link: Option<impl Into<Link>>) -> Self {
335        self.link = link.map(Into::into);
336        self
337    }
338
339    /// Sets the [`Background`] of the [`Span`].
340    pub fn background(self, background: impl Into<Background>) -> Self {
341        self.background_maybe(Some(background))
342    }
343
344    /// Sets the [`Background`] of the [`Span`], if any.
345    pub fn background_maybe(
346        mut self,
347        background: Option<impl Into<Background>>,
348    ) -> Self {
349        let Some(background) = background else {
350            return self;
351        };
352
353        match &mut self.highlight {
354            Some(highlight) => {
355                highlight.background = background.into();
356            }
357            None => {
358                self.highlight = Some(Highlight {
359                    background: background.into(),
360                    border: Border::default(),
361                });
362            }
363        }
364
365        self
366    }
367
368    /// Sets the [`Border`] of the [`Span`].
369    pub fn border(self, border: impl Into<Border>) -> Self {
370        self.border_maybe(Some(border))
371    }
372
373    /// Sets the [`Border`] of the [`Span`], if any.
374    pub fn border_maybe(mut self, border: Option<impl Into<Border>>) -> Self {
375        let Some(border) = border else {
376            return self;
377        };
378
379        match &mut self.highlight {
380            Some(highlight) => {
381                highlight.border = border.into();
382            }
383            None => {
384                self.highlight = Some(Highlight {
385                    border: border.into(),
386                    background: Background::Color(Color::TRANSPARENT),
387                });
388            }
389        }
390
391        self
392    }
393
394    /// Sets the [`Padding`] of the [`Span`].
395    ///
396    /// It only affects the [`background`] and [`border`] of the
397    /// [`Span`], currently.
398    ///
399    /// [`background`]: Self::background
400    /// [`border`]: Self::border
401    pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
402        self.padding = padding.into();
403        self
404    }
405
406    /// Sets whether the [`Span`] should be underlined or not.
407    pub fn underline(mut self, underline: bool) -> Self {
408        self.underline = underline;
409        self
410    }
411
412    /// Sets whether the [`Span`] should be struck through or not.
413    pub fn strikethrough(mut self, strikethrough: bool) -> Self {
414        self.strikethrough = strikethrough;
415        self
416    }
417
418    /// Turns the [`Span`] into a static one.
419    pub fn to_static(self) -> Span<'static, Link, Font> {
420        Span {
421            text: Cow::Owned(self.text.into_owned()),
422            size: self.size,
423            line_height: self.line_height,
424            font: self.font,
425            color: self.color,
426            link: self.link,
427            highlight: self.highlight,
428            padding: self.padding,
429            underline: self.underline,
430            strikethrough: self.strikethrough,
431        }
432    }
433}
434
435impl<Link, Font> Default for Span<'_, Link, Font> {
436    fn default() -> Self {
437        Self {
438            text: Cow::default(),
439            size: None,
440            line_height: None,
441            font: None,
442            color: None,
443            link: None,
444            highlight: None,
445            padding: Padding::default(),
446            underline: false,
447            strikethrough: false,
448        }
449    }
450}
451
452impl<'a, Link, Font> From<&'a str> for Span<'a, Link, Font> {
453    fn from(value: &'a str) -> Self {
454        Span::new(value)
455    }
456}
457
458impl<Link, Font: PartialEq> PartialEq for Span<'_, Link, Font> {
459    fn eq(&self, other: &Self) -> bool {
460        self.text == other.text
461            && self.size == other.size
462            && self.line_height == other.line_height
463            && self.font == other.font
464            && self.color == other.color
465    }
466}
467
468/// A fragment of [`Text`].
469///
470/// This is just an alias to a string that may be either
471/// borrowed or owned.
472pub type Fragment<'a> = Cow<'a, str>;
473
474/// A trait for converting a value to some text [`Fragment`].
475pub trait IntoFragment<'a> {
476    /// Converts the value to some text [`Fragment`].
477    fn into_fragment(self) -> Fragment<'a>;
478}
479
480impl<'a> IntoFragment<'a> for Fragment<'a> {
481    fn into_fragment(self) -> Fragment<'a> {
482        self
483    }
484}
485
486impl<'a> IntoFragment<'a> for &'a Fragment<'_> {
487    fn into_fragment(self) -> Fragment<'a> {
488        Fragment::Borrowed(self)
489    }
490}
491
492impl<'a> IntoFragment<'a> for &'a str {
493    fn into_fragment(self) -> Fragment<'a> {
494        Fragment::Borrowed(self)
495    }
496}
497
498impl<'a> IntoFragment<'a> for &'a String {
499    fn into_fragment(self) -> Fragment<'a> {
500        Fragment::Borrowed(self.as_str())
501    }
502}
503
504impl<'a> IntoFragment<'a> for String {
505    fn into_fragment(self) -> Fragment<'a> {
506        Fragment::Owned(self)
507    }
508}
509
510macro_rules! into_fragment {
511    ($type:ty) => {
512        impl<'a> IntoFragment<'a> for $type {
513            fn into_fragment(self) -> Fragment<'a> {
514                Fragment::Owned(self.to_string())
515            }
516        }
517
518        impl<'a> IntoFragment<'a> for &$type {
519            fn into_fragment(self) -> Fragment<'a> {
520                Fragment::Owned(self.to_string())
521            }
522        }
523    };
524}
525
526into_fragment!(char);
527into_fragment!(bool);
528
529into_fragment!(u8);
530into_fragment!(u16);
531into_fragment!(u32);
532into_fragment!(u64);
533into_fragment!(u128);
534into_fragment!(usize);
535
536into_fragment!(i8);
537into_fragment!(i16);
538into_fragment!(i32);
539into_fragment!(i64);
540into_fragment!(i128);
541into_fragment!(isize);
542
543into_fragment!(f32);
544into_fragment!(f64);