1use std::{
2 any::Any,
3 borrow::Cow,
4 cell::RefCell,
5 fmt::Display,
6 rc::Rc,
7};
8
9use freya_engine::prelude::{
10 FontStyle,
11 Paint,
12 PaintStyle,
13 ParagraphBuilder,
14 ParagraphStyle,
15 RectHeightStyle,
16 RectWidthStyle,
17 SkParagraph,
18 SkRect,
19 TextStyle,
20};
21use rustc_hash::FxHashMap;
22use torin::prelude::Size2D;
23
24use crate::{
25 data::{
26 AccessibilityData,
27 CursorStyleData,
28 EffectData,
29 LayoutData,
30 StyleState,
31 TextStyleData,
32 TextStyleState,
33 },
34 diff_key::DiffKey,
35 element::{
36 Element,
37 ElementExt,
38 EventHandlerType,
39 LayoutContext,
40 RenderContext,
41 },
42 events::name::EventName,
43 prelude::{
44 AccessibilityExt,
45 Color,
46 ContainerExt,
47 EventHandlersExt,
48 KeyExt,
49 LayerExt,
50 LayoutExt,
51 MaybeExt,
52 TextAlign,
53 TextStyleExt,
54 },
55 text_cache::CachedParagraph,
56 tree::DiffModifies,
57};
58pub struct ParagraphHolderInner {
59 pub paragraph: Rc<SkParagraph>,
60 pub scale_factor: f64,
61}
62
63#[derive(Clone)]
64pub struct ParagraphHolder(pub Rc<RefCell<Option<ParagraphHolderInner>>>);
65
66impl PartialEq for ParagraphHolder {
67 fn eq(&self, other: &Self) -> bool {
68 Rc::ptr_eq(&self.0, &other.0)
69 }
70}
71
72impl Default for ParagraphHolder {
73 fn default() -> Self {
74 Self(Rc::new(RefCell::new(None)))
75 }
76}
77
78#[derive(Default, PartialEq, Clone)]
79pub struct ParagraphElement {
80 pub layout: LayoutData,
81 pub spans: Vec<Span<'static>>,
82 pub accessibility: AccessibilityData,
83 pub text_style_data: TextStyleData,
84 pub cursor_style_data: CursorStyleData,
85 pub event_handlers: FxHashMap<EventName, EventHandlerType>,
86 pub sk_paragraph: ParagraphHolder,
87 pub cursor_index: Option<usize>,
88 pub highlights: Vec<(usize, usize)>,
89 pub max_lines: Option<usize>,
90 pub line_height: Option<f32>,
91 pub relative_layer: i16,
92}
93
94impl Display for ParagraphElement {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 f.write_str(
97 &self
98 .spans
99 .iter()
100 .map(|s| s.text.clone())
101 .collect::<Vec<_>>()
102 .join("\n"),
103 )
104 }
105}
106
107impl ElementExt for ParagraphElement {
108 fn changed(&self, other: &Rc<dyn ElementExt>) -> bool {
109 let Some(paragraph) = (other.as_ref() as &dyn Any).downcast_ref::<ParagraphElement>()
110 else {
111 return false;
112 };
113 self != paragraph
114 }
115
116 fn diff(&self, other: &Rc<dyn ElementExt>) -> DiffModifies {
117 let Some(paragraph) = (other.as_ref() as &dyn Any).downcast_ref::<ParagraphElement>()
118 else {
119 return DiffModifies::all();
120 };
121
122 let mut diff = DiffModifies::empty();
123
124 if self.spans != paragraph.spans {
125 diff.insert(DiffModifies::STYLE);
126 diff.insert(DiffModifies::LAYOUT);
127 }
128
129 if self.accessibility != paragraph.accessibility {
130 diff.insert(DiffModifies::ACCESSIBILITY);
131 }
132
133 if self.relative_layer != paragraph.relative_layer {
134 diff.insert(DiffModifies::LAYER);
135 }
136
137 if self.text_style_data != paragraph.text_style_data {
138 diff.insert(DiffModifies::STYLE);
139 }
140
141 if self.event_handlers != paragraph.event_handlers {
142 diff.insert(DiffModifies::EVENT_HANDLERS);
143 }
144
145 if self.cursor_index != paragraph.cursor_index || self.highlights != paragraph.highlights {
146 diff.insert(DiffModifies::STYLE);
147 }
148
149 if self.text_style_data != paragraph.text_style_data
150 || self.line_height != paragraph.line_height
151 || self.max_lines != paragraph.max_lines
152 {
153 diff.insert(DiffModifies::TEXT_STYLE);
154 diff.insert(DiffModifies::LAYOUT);
155 }
156
157 if self.layout != paragraph.layout {
158 diff.insert(DiffModifies::STYLE);
159 diff.insert(DiffModifies::LAYOUT);
160 }
161
162 diff
163 }
164
165 fn layout(&'_ self) -> Cow<'_, LayoutData> {
166 Cow::Borrowed(&self.layout)
167 }
168 fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
169 None
170 }
171
172 fn style(&'_ self) -> Cow<'_, StyleState> {
173 Cow::Owned(StyleState::default())
174 }
175
176 fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
177 Cow::Borrowed(&self.text_style_data)
178 }
179
180 fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
181 Cow::Borrowed(&self.accessibility)
182 }
183
184 fn relative_layer(&self) -> i16 {
185 self.relative_layer
186 }
187
188 fn measure(&self, context: LayoutContext) -> Option<(Size2D, Rc<dyn Any>)> {
189 let cached_paragraph = CachedParagraph {
190 text_style_state: context.text_style_state,
191 spans: &self.spans,
192 max_lines: self.max_lines,
193 line_height: self.line_height,
194 width: context.area_size.width,
195 };
196 let paragraph = context
197 .text_cache
198 .utilize(context.node_id, &cached_paragraph)
199 .unwrap_or_else(|| {
200 let mut paragraph_style = ParagraphStyle::default();
201 let mut text_style = TextStyle::default();
202
203 let mut font_families = context.text_style_state.font_families.clone();
204 font_families.extend_from_slice(context.fallback_fonts);
205
206 text_style.set_color(context.text_style_state.color);
207 text_style.set_font_size(
208 f32::from(context.text_style_state.font_size) * context.scale_factor as f32,
209 );
210 text_style.set_font_families(&font_families);
211 text_style.set_font_style(FontStyle::new(
212 context.text_style_state.font_weight.into(),
213 context.text_style_state.font_width.into(),
214 context.text_style_state.font_slant.into(),
215 ));
216
217 if context.text_style_state.text_height.needs_custom_height() {
218 text_style.set_height_override(true);
219 text_style.set_half_leading(true);
220 }
221
222 if let Some(line_height) = self.line_height {
223 text_style.set_height_override(true).set_height(line_height);
224 }
225
226 for text_shadow in context.text_style_state.text_shadows.iter() {
227 text_style.add_shadow((*text_shadow).into());
228 }
229
230 if let Some(ellipsis) = context.text_style_state.text_overflow.get_ellipsis() {
231 paragraph_style.set_ellipsis(ellipsis);
232 }
233
234 paragraph_style.set_text_style(&text_style);
235 paragraph_style.set_max_lines(self.max_lines);
236 paragraph_style.set_text_align(context.text_style_state.text_align.into());
237
238 let mut paragraph_builder =
239 ParagraphBuilder::new(¶graph_style, context.font_collection);
240
241 for span in &self.spans {
242 let text_style_state =
243 TextStyleState::from_data(context.text_style_state, &span.text_style_data);
244 let mut text_style = TextStyle::new();
245 let mut font_families = context.text_style_state.font_families.clone();
246 font_families.extend_from_slice(context.fallback_fonts);
247
248 for text_shadow in text_style_state.text_shadows.iter() {
249 text_style.add_shadow((*text_shadow).into());
250 }
251
252 text_style.set_color(text_style_state.color);
253 text_style.set_font_size(
254 f32::from(text_style_state.font_size) * context.scale_factor as f32,
255 );
256 text_style.set_font_families(&font_families);
257 paragraph_builder.push_style(&text_style);
258 paragraph_builder.add_text(&span.text);
259 }
260
261 let mut paragraph = paragraph_builder.build();
262 paragraph.layout(
263 if self.max_lines == Some(1)
264 && context.text_style_state.text_align == TextAlign::Start
265 && !paragraph_style.ellipsized()
266 {
267 f32::MAX
268 } else {
269 context.area_size.width + 1.0
270 },
271 );
272 context
273 .text_cache
274 .insert(context.node_id, &cached_paragraph, paragraph)
275 });
276
277 let size = Size2D::new(paragraph.longest_line(), paragraph.height());
278
279 self.sk_paragraph
280 .0
281 .borrow_mut()
282 .replace(ParagraphHolderInner {
283 paragraph,
284 scale_factor: context.scale_factor,
285 });
286
287 Some((size, Rc::new(())))
288 }
289
290 fn should_hook_measurement(&self) -> bool {
291 true
292 }
293
294 fn should_measure_inner_children(&self) -> bool {
295 false
296 }
297
298 fn events_handlers(&'_ self) -> Option<Cow<'_, FxHashMap<EventName, EventHandlerType>>> {
299 Some(Cow::Borrowed(&self.event_handlers))
300 }
301
302 fn render(&self, context: RenderContext) {
303 let paragraph = self.sk_paragraph.0.borrow();
304 let ParagraphHolderInner { paragraph, .. } = paragraph.as_ref().unwrap();
305 let area = context.layout_node.visible_area();
306
307 for (from, to) in self.highlights.iter() {
309 let (from, to) = { if from < to { (from, to) } else { (to, from) } };
310 let rects = paragraph.get_rects_for_range(
311 *from..*to,
312 RectHeightStyle::Tight,
313 RectWidthStyle::Tight,
314 );
315
316 let mut highlights_paint = Paint::default();
317 highlights_paint.set_anti_alias(true);
318 highlights_paint.set_style(PaintStyle::Fill);
319 highlights_paint.set_color(self.cursor_style_data.highlight_color);
320
321 for rect in rects {
324 let rect = SkRect::new(
325 area.min_x() + rect.rect.left,
326 area.min_y() + rect.rect.top,
327 area.min_x() + rect.rect.right,
328 area.min_y() + rect.rect.bottom,
329 );
330 context.canvas.draw_rect(rect, &highlights_paint);
331 }
332 }
333
334 paragraph.paint(context.canvas, area.origin.to_tuple());
336
337 if let Some(cursor_index) = self.cursor_index {
339 let cursor_rects = paragraph.get_rects_for_range(
340 cursor_index..cursor_index + 1,
341 RectHeightStyle::Tight,
342 RectWidthStyle::Tight,
343 );
344 if let Some(cursor_rect) = cursor_rects.first().map(|text| text.rect).or_else(|| {
345 let text_len = paragraph
347 .get_glyph_position_at_coordinate((f32::MAX, f32::MAX))
348 .position as usize;
349 let last_rects = paragraph.get_rects_for_range(
350 (text_len - 1)..text_len,
351 RectHeightStyle::Tight,
352 RectWidthStyle::Tight,
353 );
354
355 if let Some(last_rect) = last_rects.first() {
356 let mut caret = last_rect.rect;
357 caret.left = caret.right;
358 Some(caret)
359 } else {
360 None
361 }
362 }) {
363 let cursor_rect = SkRect::new(
364 area.min_x() + cursor_rect.left,
365 area.min_y() + cursor_rect.top,
366 area.min_x() + cursor_rect.left + 2.,
367 area.min_y() + cursor_rect.bottom,
368 );
369
370 let mut paint = Paint::default();
371 paint.set_anti_alias(true);
372 paint.set_style(PaintStyle::Fill);
373 paint.set_color(self.cursor_style_data.color);
374
375 context.canvas.draw_rect(cursor_rect, &paint);
376 }
377 }
378 }
379}
380
381impl From<Paragraph> for Element {
382 fn from(value: Paragraph) -> Self {
383 Element::Element {
384 key: value.key,
385 element: Rc::new(value.element),
386 elements: vec![],
387 }
388 }
389}
390
391impl KeyExt for Paragraph {
392 fn write_key(&mut self) -> &mut DiffKey {
393 &mut self.key
394 }
395}
396
397impl EventHandlersExt for Paragraph {
398 fn get_event_handlers(&mut self) -> &mut FxHashMap<EventName, EventHandlerType> {
399 &mut self.element.event_handlers
400 }
401}
402
403impl MaybeExt for Paragraph {}
404
405impl LayerExt for Paragraph {
406 fn get_layer(&mut self) -> &mut i16 {
407 &mut self.element.relative_layer
408 }
409}
410
411pub struct Paragraph {
412 key: DiffKey,
413 element: ParagraphElement,
414}
415
416pub fn paragraph() -> Paragraph {
429 Paragraph {
430 key: DiffKey::None,
431 element: ParagraphElement::default(),
432 }
433}
434
435impl LayoutExt for Paragraph {
436 fn get_layout(&mut self) -> &mut LayoutData {
437 &mut self.element.layout
438 }
439}
440
441impl ContainerExt for Paragraph {}
442
443impl AccessibilityExt for Paragraph {
444 fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
445 &mut self.element.accessibility
446 }
447}
448
449impl TextStyleExt for Paragraph {
450 fn get_text_style_data(&mut self) -> &mut TextStyleData {
451 &mut self.element.text_style_data
452 }
453}
454
455impl Paragraph {
456 pub fn try_downcast(element: &dyn ElementExt) -> Option<ParagraphElement> {
457 (element as &dyn Any)
458 .downcast_ref::<ParagraphElement>()
459 .cloned()
460 }
461
462 pub fn spans_iter(mut self, spans: impl Iterator<Item = Span<'static>>) -> Self {
463 let spans = spans.collect::<Vec<Span>>();
464 self.element.spans.extend(spans);
467 self
468 }
469
470 pub fn span(mut self, span: impl Into<Span<'static>>) -> Self {
471 let span = span.into();
472 self.element.spans.push(span);
475 self
476 }
477
478 pub fn cursor_color(mut self, cursor_color: impl Into<Color>) -> Self {
479 self.element.cursor_style_data.color = cursor_color.into();
480 self
481 }
482
483 pub fn highlight_color(mut self, highlight_color: impl Into<Color>) -> Self {
484 self.element.cursor_style_data.highlight_color = highlight_color.into();
485 self
486 }
487
488 pub fn holder(mut self, holder: ParagraphHolder) -> Self {
489 self.element.sk_paragraph = holder;
490 self
491 }
492
493 pub fn cursor_index(mut self, cursor_index: impl Into<Option<usize>>) -> Self {
494 self.element.cursor_index = cursor_index.into();
495 self
496 }
497
498 pub fn highlights(mut self, highlights: impl Into<Option<Vec<(usize, usize)>>>) -> Self {
499 if let Some(highlights) = highlights.into() {
500 self.element.highlights = highlights;
501 }
502 self
503 }
504
505 pub fn max_lines(mut self, max_lines: impl Into<Option<usize>>) -> Self {
506 self.element.max_lines = max_lines.into();
507 self
508 }
509
510 pub fn line_height(mut self, line_height: impl Into<Option<f32>>) -> Self {
511 self.element.line_height = line_height.into();
512 self
513 }
514}
515
516#[derive(Clone, PartialEq, Hash)]
517pub struct Span<'a> {
518 pub text_style_data: TextStyleData,
519 pub text: Cow<'a, str>,
520}
521
522impl From<&'static str> for Span<'static> {
523 fn from(text: &'static str) -> Self {
524 Span {
525 text_style_data: TextStyleData::default(),
526 text: text.into(),
527 }
528 }
529}
530
531impl From<String> for Span<'static> {
532 fn from(text: String) -> Self {
533 Span {
534 text_style_data: TextStyleData::default(),
535 text: text.into(),
536 }
537 }
538}
539
540impl<'a> Span<'a> {
541 pub fn new(text: impl Into<Cow<'a, str>>) -> Self {
542 Self {
543 text: text.into(),
544 text_style_data: TextStyleData::default(),
545 }
546 }
547}
548
549impl<'a> TextStyleExt for Span<'a> {
550 fn get_text_style_data(&mut self) -> &mut TextStyleData {
551 &mut self.text_style_data
552 }
553}