1use serde_json::json;
2
3use crate::{
4 color::ColorU,
5 font_cache::FamilyId,
6 fonts::{FontId, Properties},
7 geometry::{
8 rect::RectF,
9 vector::{vec2f, Vector2F},
10 },
11 json::{ToJson, Value},
12 text_layout::Line,
13 AfterLayoutContext, DebugContext, Element, Event, EventContext, FontCache, LayoutContext,
14 PaintContext, SizeConstraint,
15};
16use std::{ops::Range, sync::Arc};
17
18pub struct Label {
19 text: String,
20 family_id: FamilyId,
21 font_properties: Properties,
22 font_size: f32,
23 highlights: Option<Highlights>,
24}
25
26pub struct LayoutState {
27 line: Arc<Line>,
28 colors: Vec<(Range<usize>, ColorU)>,
29}
30
31pub struct Highlights {
32 color: ColorU,
33 indices: Vec<usize>,
34 font_properties: Properties,
35}
36
37impl Label {
38 pub fn new(text: String, family_id: FamilyId, font_size: f32) -> Self {
39 Self {
40 text,
41 family_id,
42 font_properties: Properties::new(),
43 font_size,
44 highlights: None,
45 }
46 }
47
48 pub fn with_highlights(
49 mut self,
50 color: ColorU,
51 font_properties: Properties,
52 indices: Vec<usize>,
53 ) -> Self {
54 self.highlights = Some(Highlights {
55 color,
56 font_properties,
57 indices,
58 });
59 self
60 }
61
62 fn layout_text(
63 &self,
64 font_cache: &FontCache,
65 font_id: FontId,
66 ) -> (Vec<(Range<usize>, FontId)>, Vec<(Range<usize>, ColorU)>) {
67 let text_len = self.text.len();
68 let mut styles;
69 let mut colors;
70 if let Some(highlights) = self.highlights.as_ref() {
71 styles = Vec::new();
72 colors = Vec::new();
73 let highlight_font_id = font_cache
74 .select_font(self.family_id, &highlights.font_properties)
75 .unwrap_or(font_id);
76 let mut pending_highlight: Option<Range<usize>> = None;
77 for ix in &highlights.indices {
78 if let Some(pending_highlight) = pending_highlight.as_mut() {
79 if *ix == pending_highlight.end {
80 pending_highlight.end += 1;
81 } else {
82 styles.push((pending_highlight.clone(), highlight_font_id));
83 colors.push((pending_highlight.clone(), highlights.color));
84 styles.push((pending_highlight.end..*ix, font_id));
85 colors.push((pending_highlight.end..*ix, ColorU::black()));
86 *pending_highlight = *ix..*ix + 1;
87 }
88 } else {
89 styles.push((0..*ix, font_id));
90 colors.push((0..*ix, ColorU::black()));
91 pending_highlight = Some(*ix..*ix + 1);
92 }
93 }
94 if let Some(pending_highlight) = pending_highlight.as_mut() {
95 styles.push((pending_highlight.clone(), highlight_font_id));
96 colors.push((pending_highlight.clone(), highlights.color));
97 if text_len > pending_highlight.end {
98 styles.push((pending_highlight.end..text_len, font_id));
99 colors.push((pending_highlight.end..text_len, ColorU::black()));
100 }
101 } else {
102 styles.push((0..text_len, font_id));
103 colors.push((0..text_len, ColorU::black()));
104 }
105 } else {
106 styles = vec![(0..text_len, font_id)];
107 colors = vec![(0..text_len, ColorU::black())];
108 }
109
110 (styles, colors)
111 }
112}
113
114impl Element for Label {
115 type LayoutState = LayoutState;
116 type PaintState = ();
117
118 fn layout(
119 &mut self,
120 constraint: SizeConstraint,
121 ctx: &mut LayoutContext,
122 ) -> (Vector2F, Self::LayoutState) {
123 let font_id = ctx
124 .font_cache
125 .select_font(self.family_id, &self.font_properties)
126 .unwrap();
127 let (styles, colors) = self.layout_text(&ctx.font_cache, font_id);
128 let line =
129 ctx.text_layout_cache
130 .layout_str(self.text.as_str(), self.font_size, styles.as_slice());
131
132 let size = vec2f(
133 line.width.max(constraint.min.x()).min(constraint.max.x()),
134 ctx.font_cache.line_height(font_id, self.font_size).ceil(),
135 );
136
137 (size, LayoutState { line, colors })
138 }
139
140 fn after_layout(&mut self, _: Vector2F, _: &mut Self::LayoutState, _: &mut AfterLayoutContext) {
141 }
142
143 fn paint(
144 &mut self,
145 bounds: RectF,
146 layout: &mut Self::LayoutState,
147 ctx: &mut PaintContext,
148 ) -> Self::PaintState {
149 layout.line.paint(
150 bounds.origin(),
151 RectF::new(vec2f(0., 0.), bounds.size()),
152 layout.colors.as_slice(),
153 ctx,
154 )
155 }
156
157 fn dispatch_event(
158 &mut self,
159 _: &Event,
160 _: RectF,
161 _: &mut Self::LayoutState,
162 _: &mut Self::PaintState,
163 _: &mut EventContext,
164 ) -> bool {
165 false
166 }
167
168 fn debug(
169 &self,
170 bounds: RectF,
171 _: &Self::LayoutState,
172 _: &Self::PaintState,
173 ctx: &DebugContext,
174 ) -> Value {
175 json!({
176 "type": "Label",
177 "bounds": bounds.to_json(),
178 "font_family": ctx.font_cache.family_name(self.family_id).unwrap(),
179 "font_size": self.font_size,
180 "font_properties": self.font_properties.to_json(),
181 "text": &self.text,
182 "highlights": self.highlights.to_json(),
183 })
184 }
185}
186
187impl ToJson for Highlights {
188 fn to_json(&self) -> Value {
189 json!({
190 "color": self.color.to_json(),
191 "indices": self.indices,
192 "font_properties": self.font_properties.to_json(),
193 })
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 #[crate::test(self)]
202 fn test_layout_label_with_highlights(app: &mut crate::MutableAppContext) {
203 let family_id = app.font_cache().load_family(&["Menlo"]).unwrap();
204 let font_id = app
205 .font_cache()
206 .select_font(family_id, &Properties::new())
207 .unwrap();
208
209 let label = Label::new(".αβγδε.ⓐⓑⓒⓓⓔ.abcde.".to_string(), family_id, 12.0).with_highlights(
210 ColorU::black(),
211 Properties::new(),
212 vec![
213 ".α".len(),
214 ".αβ".len(),
215 ".αβγδ".len(),
216 ".αβγδε.ⓐ".len(),
217 ".αβγδε.ⓐⓑ".len(),
218 ],
219 );
220
221 let (styles, colors) = label.layout_text(app.font_cache().as_ref(), font_id);
222 assert_eq!(styles, &[]);
223 }
224}