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