text.rs

  1use crate::{
  2    color::Color,
  3    fonts::TextStyle,
  4    geometry::{
  5        rect::RectF,
  6        vector::{vec2f, Vector2F},
  7    },
  8    json::{ToJson, Value},
  9    text_layout::{Line, ShapedBoundary},
 10    DebugContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
 11};
 12use serde_json::json;
 13
 14pub struct Text {
 15    text: String,
 16    style: TextStyle,
 17}
 18
 19pub struct LayoutState {
 20    lines: Vec<(Line, Vec<ShapedBoundary>)>,
 21    line_height: f32,
 22}
 23
 24impl Text {
 25    pub fn new(text: String, style: TextStyle) -> Self {
 26        Self { text, style }
 27    }
 28
 29    pub fn with_default_color(mut self, color: Color) -> Self {
 30        self.style.color = color;
 31        self
 32    }
 33}
 34
 35impl Element for Text {
 36    type LayoutState = LayoutState;
 37    type PaintState = ();
 38
 39    fn layout(
 40        &mut self,
 41        constraint: SizeConstraint,
 42        cx: &mut LayoutContext,
 43    ) -> (Vector2F, Self::LayoutState) {
 44        let font_id = self.style.font_id;
 45        let line_height = cx.font_cache.line_height(font_id, self.style.font_size);
 46
 47        let mut wrapper = cx.font_cache.line_wrapper(font_id, self.style.font_size);
 48        let mut lines = Vec::new();
 49        let mut line_count = 0;
 50        let mut max_line_width = 0_f32;
 51        for line in self.text.lines() {
 52            let shaped_line = cx.text_layout_cache.layout_str(
 53                line,
 54                self.style.font_size,
 55                &[(line.len(), self.style.to_run())],
 56            );
 57            let wrap_boundaries = wrapper
 58                .wrap_shaped_line(line, &shaped_line, constraint.max.x())
 59                .collect::<Vec<_>>();
 60
 61            max_line_width = max_line_width.max(shaped_line.width());
 62            line_count += wrap_boundaries.len() + 1;
 63            lines.push((shaped_line, wrap_boundaries));
 64        }
 65
 66        let size = vec2f(
 67            max_line_width
 68                .ceil()
 69                .max(constraint.min.x())
 70                .min(constraint.max.x()),
 71            (line_height * line_count as f32).ceil(),
 72        );
 73        (size, LayoutState { lines, line_height })
 74    }
 75
 76    fn paint(
 77        &mut self,
 78        bounds: RectF,
 79        visible_bounds: RectF,
 80        layout: &mut Self::LayoutState,
 81        cx: &mut PaintContext,
 82    ) -> Self::PaintState {
 83        let mut origin = bounds.origin();
 84        for (line, wrap_boundaries) in &layout.lines {
 85            let wrapped_line_boundaries = RectF::new(
 86                origin,
 87                vec2f(
 88                    bounds.width(),
 89                    (wrap_boundaries.len() + 1) as f32 * layout.line_height,
 90                ),
 91            );
 92
 93            if wrapped_line_boundaries.intersects(visible_bounds) {
 94                line.paint_wrapped(
 95                    origin,
 96                    visible_bounds,
 97                    layout.line_height,
 98                    wrap_boundaries.iter().copied(),
 99                    cx,
100                );
101            }
102            origin.set_y(wrapped_line_boundaries.max_y());
103        }
104    }
105
106    fn dispatch_event(
107        &mut self,
108        _: &Event,
109        _: RectF,
110        _: &mut Self::LayoutState,
111        _: &mut Self::PaintState,
112        _: &mut EventContext,
113    ) -> bool {
114        false
115    }
116
117    fn debug(
118        &self,
119        bounds: RectF,
120        _: &Self::LayoutState,
121        _: &Self::PaintState,
122        _: &DebugContext,
123    ) -> Value {
124        json!({
125            "type": "Text",
126            "bounds": bounds.to_json(),
127            "text": &self.text,
128            "style": self.style.to_json(),
129        })
130    }
131}