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}