1use serde_json::json;
2
3use crate::{
4 color::ColorU,
5 font_cache::FamilyId,
6 fonts::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, LayoutContext, PaintContext,
14 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
63impl Element for Label {
64 type LayoutState = LayoutState;
65 type PaintState = ();
66
67 fn layout(
68 &mut self,
69 constraint: SizeConstraint,
70 ctx: &mut LayoutContext,
71 ) -> (Vector2F, Self::LayoutState) {
72 let font_id = ctx
73 .font_cache
74 .select_font(self.family_id, &self.font_properties)
75 .unwrap();
76 let text_len = self.text.chars().count();
77 let mut styles;
78 let mut colors;
79 if let Some(highlights) = self.highlights.as_ref() {
80 styles = Vec::new();
81 colors = Vec::new();
82 let highlight_font_id = ctx
83 .font_cache
84 .select_font(self.family_id, &highlights.font_properties)
85 .unwrap_or(font_id);
86 let mut pending_highlight: Option<Range<usize>> = None;
87 for ix in &highlights.indices {
88 if let Some(pending_highlight) = pending_highlight.as_mut() {
89 if *ix == pending_highlight.end {
90 pending_highlight.end += 1;
91 } else {
92 styles.push((pending_highlight.clone(), highlight_font_id));
93 colors.push((pending_highlight.clone(), highlights.color));
94 styles.push((pending_highlight.end..*ix, font_id));
95 colors.push((pending_highlight.end..*ix, ColorU::black()));
96 *pending_highlight = *ix..*ix + 1;
97 }
98 } else {
99 styles.push((0..*ix, font_id));
100 colors.push((0..*ix, ColorU::black()));
101 pending_highlight = Some(*ix..*ix + 1);
102 }
103 }
104 if let Some(pending_highlight) = pending_highlight.as_mut() {
105 styles.push((pending_highlight.clone(), highlight_font_id));
106 colors.push((pending_highlight.clone(), highlights.color));
107 if text_len > pending_highlight.end {
108 styles.push((pending_highlight.end..text_len, font_id));
109 colors.push((pending_highlight.end..text_len, ColorU::black()));
110 }
111 } else {
112 styles.push((0..text_len, font_id));
113 colors.push((0..text_len, ColorU::black()));
114 }
115 } else {
116 styles = vec![(0..text_len, font_id)];
117 colors = vec![(0..text_len, ColorU::black())];
118 }
119
120 let line =
121 ctx.text_layout_cache
122 .layout_str(self.text.as_str(), self.font_size, styles.as_slice());
123
124 let size = vec2f(
125 line.width.max(constraint.min.x()).min(constraint.max.x()),
126 ctx.font_cache.line_height(font_id, self.font_size).ceil(),
127 );
128
129 (size, LayoutState { line, colors })
130 }
131
132 fn after_layout(&mut self, _: Vector2F, _: &mut Self::LayoutState, _: &mut AfterLayoutContext) {
133 }
134
135 fn paint(
136 &mut self,
137 bounds: RectF,
138 layout: &mut Self::LayoutState,
139 ctx: &mut PaintContext,
140 ) -> Self::PaintState {
141 layout.line.paint(
142 bounds.origin(),
143 RectF::new(vec2f(0., 0.), bounds.size()),
144 layout.colors.as_slice(),
145 ctx,
146 )
147 }
148
149 fn dispatch_event(
150 &mut self,
151 _: &Event,
152 _: RectF,
153 _: &mut Self::LayoutState,
154 _: &mut Self::PaintState,
155 _: &mut EventContext,
156 ) -> bool {
157 false
158 }
159
160 fn debug(
161 &self,
162 bounds: RectF,
163 _: &Self::LayoutState,
164 _: &Self::PaintState,
165 ctx: &DebugContext,
166 ) -> Value {
167 json!({
168 "type": "Label",
169 "bounds": bounds.to_json(),
170 "font_family": ctx.font_cache.family_name(self.family_id).unwrap(),
171 "font_size": self.font_size,
172 "font_properties": self.font_properties.to_json(),
173 "text": &self.text,
174 "highlights": self.highlights.to_json(),
175 })
176 }
177}
178
179impl ToJson for Highlights {
180 fn to_json(&self) -> Value {
181 json!({
182 "color": self.color.to_json(),
183 "indices": self.indices,
184 "font_properties": self.font_properties.to_json(),
185 })
186 }
187}