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    Basic,
61    /// Advanced text shaping and font fallback.
62    ///
63    /// You will need to enable this flag if the text contains a complex
64    /// script, the font used needs it, and/or multiple fonts in your system
65    /// may be needed to display all of the glyphs.
66    ///
67    /// Advanced shaping is expensive! You should only enable it when necessary.
68    /// This is the default.
69    #[default]
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.4)
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 Raw of this [`Renderer`].
197    type Raw;
198
199    /// The icon font of the backend.
200    const ICON_FONT: Self::Font;
201
202    /// The `char` representing a ✔ icon in the [`ICON_FONT`].
203    ///
204    /// [`ICON_FONT`]: Self::ICON_FONT
205    const CHECKMARK_ICON: char;
206
207    /// The `char` representing a ▼ icon in the built-in [`ICON_FONT`].
208    ///
209    /// [`ICON_FONT`]: Self::ICON_FONT
210    const ARROW_DOWN_ICON: char;
211
212    /// Returns the default [`Self::Font`].
213    fn default_font(&self) -> Self::Font;
214
215    /// Returns the default size of [`Text`].
216    fn default_size(&self) -> Pixels;
217
218    /// Draws the given [`Paragraph`] at the given position and with the given
219    /// [`Color`].
220    fn fill_paragraph(
221        &mut self,
222        text: &Self::Paragraph,
223        position: Point,
224        color: Color,
225        clip_bounds: Rectangle,
226    );
227
228    /// Draws the given [`Editor`] at the given position and with the given
229    /// [`Color`].
230    fn fill_editor(
231        &mut self,
232        editor: &Self::Editor,
233        position: Point,
234        color: Color,
235        clip_bounds: Rectangle,
236    );
237
238    /// Draws the given Raw
239    fn fill_raw(&mut self, raw: Self::Raw);
240
241    /// Draws the given [`Text`] at the given position and with the given
242    /// [`Color`].
243    fn fill_text(
244        &mut self,
245        text: Text<String, Self::Font>,
246        position: Point,
247        color: Color,
248        clip_bounds: Rectangle,
249    );
250}
251
252/// A span of text.
253#[derive(Debug, Clone)]
254pub struct Span<'a, Link = (), Font = crate::Font> {
255    /// The [`Fragment`] of text.
256    pub text: Fragment<'a>,
257    /// The size of the [`Span`] in [`Pixels`].
258    pub size: Option<Pixels>,
259    /// The [`LineHeight`] of the [`Span`].
260    pub line_height: Option<LineHeight>,
261    /// The font of the [`Span`].
262    pub font: Option<Font>,
263    /// The [`Color`] of the [`Span`].
264    pub color: Option<Color>,
265    /// The link of the [`Span`].
266    pub link: Option<Link>,
267    /// The [`Highlight`] of the [`Span`].
268    pub highlight: Option<Highlight>,
269    /// The [`Padding`] of the [`Span`].
270    ///
271    /// Currently, it only affects the bounds of the [`Highlight`].
272    pub padding: Padding,
273    /// Whether the [`Span`] should be underlined or not.
274    pub underline: bool,
275    /// Whether the [`Span`] should be struck through or not.
276    pub strikethrough: bool,
277}
278
279/// A text highlight.
280#[derive(Debug, Clone, Copy, PartialEq)]
281pub struct Highlight {
282    /// The [`Background`] of the highlight.
283    pub background: Background,
284    /// The [`Border`] of the highlight.
285    pub border: Border,
286}
287
288impl<'a, Link, Font> Span<'a, Link, Font> {
289    /// Creates a new [`Span`] of text with the given text fragment.
290    pub fn new(fragment: impl IntoFragment<'a>) -> Self {
291        Self {
292            text: fragment.into_fragment(),
293            size: None,
294            line_height: None,
295            font: None,
296            color: None,
297            highlight: None,
298            link: None,
299            padding: Padding::ZERO,
300            underline: false,
301            strikethrough: false,
302        }
303    }
304
305    /// Sets the size of the [`Span`].
306    pub fn size(mut self, size: impl Into<Pixels>) -> Self {
307        self.size = Some(size.into());
308        self
309    }
310
311    /// Sets the [`LineHeight`] of the [`Span`].
312    pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
313        self.line_height = Some(line_height.into());
314        self
315    }
316
317    /// Sets the font of the [`Span`].
318    pub fn font(mut self, font: impl Into<Font>) -> Self {
319        self.font = Some(font.into());
320        self
321    }
322
323    /// Sets the font of the [`Span`], if any.
324    pub fn font_maybe(mut self, font: Option<impl Into<Font>>) -> Self {
325        self.font = font.map(Into::into);
326        self
327    }
328
329    /// Sets the [`Color`] of the [`Span`].
330    pub fn color(mut self, color: impl Into<Color>) -> Self {
331        self.color = Some(color.into());
332        self
333    }
334
335    /// Sets the [`Color`] of the [`Span`], if any.
336    pub fn color_maybe(mut self, color: Option<impl Into<Color>>) -> Self {
337        self.color = color.map(Into::into);
338        self
339    }
340
341    /// Sets the link of the [`Span`].
342    pub fn link(mut self, link: impl Into<Link>) -> Self {
343        self.link = Some(link.into());
344        self
345    }
346
347    /// Sets the link of the [`Span`], if any.
348    pub fn link_maybe(mut self, link: Option<impl Into<Link>>) -> Self {
349        self.link = link.map(Into::into);
350        self
351    }
352
353    /// Sets the [`Background`] of the [`Span`].
354    pub fn background(self, background: impl Into<Background>) -> Self {
355        self.background_maybe(Some(background))
356    }
357
358    /// Sets the [`Background`] of the [`Span`], if any.
359    pub fn background_maybe(
360        mut self,
361        background: Option<impl Into<Background>>,
362    ) -> Self {
363        let Some(background) = background else {
364            return self;
365        };
366
367        match &mut self.highlight {
368            Some(highlight) => {
369                highlight.background = background.into();
370            }
371            None => {
372                self.highlight = Some(Highlight {
373                    background: background.into(),
374                    border: Border::default(),
375                });
376            }
377        }
378
379        self
380    }
381
382    /// Sets the [`Border`] of the [`Span`].
383    pub fn border(self, border: impl Into<Border>) -> Self {
384        self.border_maybe(Some(border))
385    }
386
387    /// Sets the [`Border`] of the [`Span`], if any.
388    pub fn border_maybe(mut self, border: Option<impl Into<Border>>) -> Self {
389        let Some(border) = border else {
390            return self;
391        };
392
393        match &mut self.highlight {
394            Some(highlight) => {
395                highlight.border = border.into();
396            }
397            None => {
398                self.highlight = Some(Highlight {
399                    border: border.into(),
400                    background: Background::Color(Color::TRANSPARENT),
401                });
402            }
403        }
404
405        self
406    }
407
408    /// Sets the [`Padding`] of the [`Span`].
409    ///
410    /// It only affects the [`background`] and [`border`] of the
411    /// [`Span`], currently.
412    ///
413    /// [`background`]: Self::background
414    /// [`border`]: Self::border
415    pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
416        self.padding = padding.into();
417        self
418    }
419
420    /// Sets whether the [`Span`] should be underlined or not.
421    pub fn underline(mut self, underline: bool) -> Self {
422        self.underline = underline;
423        self
424    }
425
426    /// Sets whether the [`Span`] should be struck through or not.
427    pub fn strikethrough(mut self, strikethrough: bool) -> Self {
428        self.strikethrough = strikethrough;
429        self
430    }
431
432    /// Turns the [`Span`] into a static one.
433    pub fn to_static(self) -> Span<'static, Link, Font> {
434        Span {
435            text: Cow::Owned(self.text.into_owned()),
436            size: self.size,
437            line_height: self.line_height,
438            font: self.font,
439            color: self.color,
440            link: self.link,
441            highlight: self.highlight,
442            padding: self.padding,
443            underline: self.underline,
444            strikethrough: self.strikethrough,
445        }
446    }
447}
448
449impl<'a, Link, Font> From<&'a str> for Span<'a, Link, Font> {
450    fn from(value: &'a str) -> Self {
451        Span::new(value)
452    }
453}
454
455impl<'a, Link, Font: PartialEq> PartialEq for Span<'a, Link, Font> {
456    fn eq(&self, other: &Self) -> bool {
457        self.text == other.text
458            && self.size == other.size
459            && self.line_height == other.line_height
460            && self.font == other.font
461            && self.color == other.color
462    }
463}
464
465/// A fragment of [`Text`].
466///
467/// This is just an alias to a string that may be either
468/// borrowed or owned.
469pub type Fragment<'a> = Cow<'a, str>;
470
471/// A trait for converting a value to some text [`Fragment`].
472pub trait IntoFragment<'a> {
473    /// Converts the value to some text [`Fragment`].
474    fn into_fragment(self) -> Fragment<'a>;
475}
476
477impl<'a> IntoFragment<'a> for Fragment<'a> {
478    fn into_fragment(self) -> Fragment<'a> {
479        self
480    }
481}
482
483impl<'a, 'b> IntoFragment<'a> for &'a Fragment<'b> {
484    fn into_fragment(self) -> Fragment<'a> {
485        Fragment::Borrowed(self)
486    }
487}
488
489impl<'a> IntoFragment<'a> for &'a str {
490    fn into_fragment(self) -> Fragment<'a> {
491        Fragment::Borrowed(self)
492    }
493}
494
495impl<'a> IntoFragment<'a> for &'a String {
496    fn into_fragment(self) -> Fragment<'a> {
497        Fragment::Borrowed(self.as_str())
498    }
499}
500
501impl<'a> IntoFragment<'a> for String {
502    fn into_fragment(self) -> Fragment<'a> {
503        Fragment::Owned(self)
504    }
505}
506
507macro_rules! into_fragment {
508    ($type:ty) => {
509        impl<'a> IntoFragment<'a> for $type {
510            fn into_fragment(self) -> Fragment<'a> {
511                Fragment::Owned(self.to_string())
512            }
513        }
514
515        impl<'a> IntoFragment<'a> for &$type {
516            fn into_fragment(self) -> Fragment<'a> {
517                Fragment::Owned(self.to_string())
518            }
519        }
520    };
521}
522
523into_fragment!(char);
524into_fragment!(bool);
525
526into_fragment!(u8);
527into_fragment!(u16);
528into_fragment!(u32);
529into_fragment!(u64);
530into_fragment!(u128);
531into_fragment!(usize);
532
533into_fragment!(i8);
534into_fragment!(i16);
535into_fragment!(i32);
536into_fragment!(i64);
537into_fragment!(i128);
538into_fragment!(isize);
539
540into_fragment!(f32);
541into_fragment!(f64);