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}