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