Skip to main content

gpui/text_system/
text_run.rs

1use crate::{Font, Pixels, StrikethroughStyle, UnderlineStyle};
2use palette::Hsla;
3use std::{
4    fmt::Debug,
5    hash::{Hash, Hasher},
6};
7
8/// A stretch of text that shares one font and style.
9///
10/// Text layout takes a slice of runs whose `len` values add up to the length of
11/// the text. Each run styles the bytes that follow the runs before it. Runs
12/// control how text is shaped and painted, so changing a run's `color`,
13/// `background_color`, `underline`, or `strikethrough` does not reshape the text.
14///
15/// # Examples
16///
17/// ```
18/// # use gpui::{TextRun, font, hsla};
19/// let text = "Hello world";
20/// let runs = [
21///     TextRun {
22///         len: 6,
23///         font: font("Lilex"),
24///         color: hsla(0., 0., 0., 1.),
25///         ..Default::default()
26///     },
27///     TextRun {
28///         len: 5,
29///         font: font("Lilex").bold(),
30///         color: hsla(0., 0., 0., 1.),
31///         ..Default::default()
32///     },
33/// ];
34/// assert_eq!(runs.iter().map(|run| run.len).sum::<usize>(), text.len());
35/// ```
36#[derive(Clone, Debug, PartialEq, Default)]
37pub struct TextRun {
38    /// The number of UTF-8 bytes this run covers.
39    pub len: usize,
40    /// The font to use for this run.
41    pub font: Font,
42    /// The color of the glyphs.
43    pub color: Hsla,
44    /// The background color behind the glyphs, or `None` for no background.
45    pub background_color: Option<Hsla>,
46    /// The underline drawn under the glyphs, or `None` for no underline.
47    pub underline: Option<UnderlineStyle>,
48    /// The strikethrough drawn through the glyphs, or `None` for none.
49    pub strikethrough: Option<StrikethroughStyle>,
50    /// The extra space added between glyphs, in logical pixels, or `None` for no extra space.
51    pub letter_spacing: Option<Pixels>,
52}
53
54impl Eq for TextRun {}
55
56impl Hash for TextRun {
57    fn hash<H: Hasher>(&self, state: &mut H) {
58        fn hash_float<H: Hasher>(value: f32, state: &mut H) {
59            if value == 0.0 {
60                0.0f32.to_bits().hash(state);
61            } else {
62                value.to_bits().hash(state);
63            }
64        }
65
66        fn hash_color<H: Hasher>(color: Hsla, state: &mut H) {
67            let color = crate::hsla_to_rgba(color);
68            hash_float(color.color.red, state);
69            hash_float(color.color.green, state);
70            hash_float(color.color.blue, state);
71            hash_float(color.alpha, state);
72        }
73
74        self.len.hash(state);
75        self.font.hash(state);
76        hash_color(self.color, state);
77        if let Some(color) = self.background_color {
78            hash_color(color, state);
79        }
80        self.background_color.is_some().hash(state);
81        self.underline.is_some().hash(state);
82        if let Some(underline) = self.underline {
83            hash_float(underline.thickness.into(), state);
84            underline.color.is_some().hash(state);
85            if let Some(color) = underline.color {
86                hash_color(color, state);
87            }
88            underline.wavy.hash(state);
89        }
90        self.strikethrough.is_some().hash(state);
91        if let Some(strikethrough) = self.strikethrough {
92            hash_float(strikethrough.thickness.into(), state);
93            strikethrough.color.is_some().hash(state);
94            if let Some(color) = strikethrough.color {
95                hash_color(color, state);
96            }
97        }
98        self.letter_spacing.is_some().hash(state);
99        if let Some(letter_spacing) = self.letter_spacing {
100            hash_float(letter_spacing.into(), state);
101        }
102    }
103}