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