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