text.rs

  1use crate::{
  2    color::Color,
  3    font_cache::FamilyId,
  4    fonts::{FontId, TextStyle},
  5    geometry::{
  6        rect::RectF,
  7        vector::{vec2f, Vector2F},
  8    },
  9    json::{ToJson, Value},
 10    text_layout::Line,
 11    DebugContext, Element, Event, EventContext, FontCache, LayoutContext, PaintContext,
 12    SizeConstraint,
 13};
 14use serde::Deserialize;
 15use serde_json::json;
 16use smallvec::{smallvec, SmallVec};
 17
 18pub struct Text {
 19    text: String,
 20    family_id: FamilyId,
 21    font_size: f32,
 22    style: TextStyle,
 23}
 24
 25impl Text {
 26    pub fn new(text: String, family_id: FamilyId, font_size: f32) -> Self {
 27        Self {
 28            text,
 29            family_id,
 30            font_size,
 31            style: Default::default(),
 32        }
 33    }
 34
 35    pub fn with_style(mut self, style: &TextStyle) -> Self {
 36        self.style = style.clone();
 37        self
 38    }
 39
 40    pub fn with_default_color(mut self, color: Color) -> Self {
 41        self.style.color = color;
 42        self
 43    }
 44}
 45
 46impl Element for Text {
 47    type LayoutState = Line;
 48    type PaintState = ();
 49
 50    fn layout(
 51        &mut self,
 52        constraint: SizeConstraint,
 53        cx: &mut LayoutContext,
 54    ) -> (Vector2F, Self::LayoutState) {
 55        let font_id = cx
 56            .font_cache
 57            .select_font(self.family_id, &self.style.font_properties)
 58            .unwrap();
 59        let line =
 60            cx.text_layout_cache
 61                .layout_str(self.text.as_str(), self.font_size, runs.as_slice());
 62
 63        let size = vec2f(
 64            line.width().max(constraint.min.x()).min(constraint.max.x()),
 65            cx.font_cache.line_height(font_id, self.font_size).ceil(),
 66        );
 67
 68        (size, line)
 69    }
 70
 71    fn paint(
 72        &mut self,
 73        bounds: RectF,
 74        line: &mut Self::LayoutState,
 75        cx: &mut PaintContext,
 76    ) -> Self::PaintState {
 77        line.paint(
 78            bounds.origin(),
 79            RectF::new(vec2f(0., 0.), bounds.size()),
 80            cx,
 81        )
 82    }
 83
 84    fn dispatch_event(
 85        &mut self,
 86        _: &Event,
 87        _: RectF,
 88        _: &mut Self::LayoutState,
 89        _: &mut Self::PaintState,
 90        _: &mut EventContext,
 91    ) -> bool {
 92        false
 93    }
 94
 95    fn debug(
 96        &self,
 97        bounds: RectF,
 98        _: &Self::LayoutState,
 99        _: &Self::PaintState,
100        cx: &DebugContext,
101    ) -> Value {
102        json!({
103            "type": "Label",
104            "bounds": bounds.to_json(),
105            "text": &self.text,
106            "highlight_indices": self.highlight_indices,
107            "font_family": cx.font_cache.family_name(self.family_id).unwrap(),
108            "font_size": self.font_size,
109            "style": self.style.to_json(),
110        })
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117    use crate::fonts::{Properties as FontProperties, Weight};
118
119    #[crate::test(self)]
120    fn test_layout_label_with_highlights(cx: &mut crate::MutableAppContext) {
121        let menlo = cx.font_cache().load_family(&["Menlo"]).unwrap();
122        let menlo_regular = cx
123            .font_cache()
124            .select_font(menlo, &FontProperties::new())
125            .unwrap();
126        let menlo_bold = cx
127            .font_cache()
128            .select_font(menlo, FontProperties::new().weight(Weight::BOLD))
129            .unwrap();
130        let black = Color::black();
131        let red = Color::new(255, 0, 0, 255);
132
133        let label = Text::new(".αβγδε.ⓐⓑⓒⓓⓔ.abcde.".to_string(), menlo, 12.0)
134            .with_style(&TextStyle {
135                text: TextStyle {
136                    color: black,
137                    font_properties: Default::default(),
138                },
139                highlight_text: Some(TextStyle {
140                    color: red,
141                    font_properties: *FontProperties::new().weight(Weight::BOLD),
142                }),
143            })
144            .with_highlights(vec![
145                "".len(),
146                ".αβ".len(),
147                ".αβγδ".len(),
148                ".αβγδε.ⓐ".len(),
149                ".αβγδε.ⓐⓑ".len(),
150            ]);
151
152        let runs = label.compute_runs(cx.font_cache().as_ref(), menlo_regular);
153        assert_eq!(
154            runs.as_slice(),
155            &[
156                ("".len(), menlo_regular, black),
157                ("βγ".len(), menlo_bold, red),
158                ("δ".len(), menlo_regular, black),
159                ("ε".len(), menlo_bold, red),
160                (".ⓐ".len(), menlo_regular, black),
161                ("ⓑⓒ".len(), menlo_bold, red),
162                ("ⓓⓔ.abcde.".len(), menlo_regular, black),
163            ]
164        );
165    }
166}