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}