1use std::{
4 any::Any,
5 borrow::Cow,
6 cell::RefCell,
7 fmt::{
8 Debug,
9 Display,
10 },
11 rc::Rc,
12};
13
14use freya_engine::prelude::{
15 BlendMode,
16 Canvas,
17 FontStyle,
18 Paint,
19 PaintStyle,
20 ParagraphBuilder,
21 ParagraphStyle,
22 RectHeightStyle,
23 RectWidthStyle,
24 SaveLayerRec,
25 SkParagraph,
26 SkRect,
27 TextStyle,
28};
29use rustc_hash::FxHashMap;
30use torin::prelude::{
31 Area,
32 Point2D,
33 Size2D,
34};
35
36use crate::{
37 data::{
38 AccessibilityData,
39 CursorStyleData,
40 EffectData,
41 LayoutData,
42 StyleState,
43 TextStyleData,
44 TextStyleState,
45 },
46 diff_key::DiffKey,
47 element::{
48 Element,
49 ElementExt,
50 EventHandlerType,
51 LayoutContext,
52 RenderContext,
53 },
54 events::name::EventName,
55 layers::Layer,
56 prelude::{
57 AccessibilityExt,
58 Color,
59 ContainerExt,
60 EventHandlersExt,
61 Fill,
62 KeyExt,
63 LayerExt,
64 LayoutExt,
65 MaybeExt,
66 TextAlign,
67 TextStyleExt,
68 VerticalAlign,
69 },
70 style::cursor::{
71 CursorMode,
72 CursorStyle,
73 },
74 text_cache::CachedParagraph,
75 tree::DiffModifies,
76};
77
78pub fn paragraph() -> Paragraph {
91 Paragraph {
92 key: DiffKey::None,
93 element: ParagraphElement::default(),
94 }
95}
96
97pub struct ParagraphHolderInner {
98 pub paragraph: Rc<SkParagraph>,
99 pub scale_factor: f64,
100}
101
102#[derive(Clone)]
103pub struct ParagraphHolder(pub Rc<RefCell<Option<ParagraphHolderInner>>>);
104
105impl PartialEq for ParagraphHolder {
106 fn eq(&self, other: &Self) -> bool {
107 Rc::ptr_eq(&self.0, &other.0)
108 }
109}
110
111impl Debug for ParagraphHolder {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 f.write_str("ParagraphHolder")
114 }
115}
116
117impl Default for ParagraphHolder {
118 fn default() -> Self {
119 Self(Rc::new(RefCell::new(None)))
120 }
121}
122
123#[derive(PartialEq, Clone)]
124pub struct ParagraphElement {
125 pub layout: LayoutData,
126 pub spans: Vec<Span<'static>>,
127 pub accessibility: AccessibilityData,
128 pub text_style_data: TextStyleData,
129 pub cursor_style_data: CursorStyleData,
130 pub event_handlers: FxHashMap<EventName, EventHandlerType>,
131 pub sk_paragraph: ParagraphHolder,
132 pub cursor_index: Option<usize>,
133 pub highlights: Vec<(usize, usize)>,
134 pub max_lines: Option<usize>,
135 pub line_height: Option<f32>,
136 pub relative_layer: Layer,
137 pub cursor_style: CursorStyle,
138 pub cursor_mode: CursorMode,
139 pub vertical_align: VerticalAlign,
140}
141
142impl Default for ParagraphElement {
143 fn default() -> Self {
144 let mut accessibility = AccessibilityData::default();
145 accessibility.builder.set_role(accesskit::Role::Paragraph);
146 Self {
147 layout: Default::default(),
148 spans: Default::default(),
149 accessibility,
150 text_style_data: Default::default(),
151 cursor_style_data: Default::default(),
152 event_handlers: Default::default(),
153 sk_paragraph: Default::default(),
154 cursor_index: Default::default(),
155 highlights: Default::default(),
156 max_lines: Default::default(),
157 line_height: Default::default(),
158 relative_layer: Default::default(),
159 cursor_style: CursorStyle::default(),
160 cursor_mode: CursorMode::default(),
161 vertical_align: VerticalAlign::default(),
162 }
163 }
164}
165
166impl Display for ParagraphElement {
167 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168 f.write_str(
169 &self
170 .spans
171 .iter()
172 .map(|s| s.text.clone())
173 .collect::<Vec<_>>()
174 .join("\n"),
175 )
176 }
177}
178
179impl ElementExt for ParagraphElement {
180 fn changed(&self, other: &Rc<dyn ElementExt>) -> bool {
181 let Some(paragraph) = (other.as_ref() as &dyn Any).downcast_ref::<ParagraphElement>()
182 else {
183 return false;
184 };
185 self != paragraph
186 }
187
188 fn diff(&self, other: &Rc<dyn ElementExt>) -> DiffModifies {
189 let Some(paragraph) = (other.as_ref() as &dyn Any).downcast_ref::<ParagraphElement>()
190 else {
191 return DiffModifies::all();
192 };
193
194 let mut diff = DiffModifies::empty();
195
196 if self.spans != paragraph.spans {
197 diff.insert(DiffModifies::STYLE);
198 diff.insert(DiffModifies::LAYOUT);
199 }
200
201 if self.accessibility != paragraph.accessibility {
202 diff.insert(DiffModifies::ACCESSIBILITY);
203 }
204
205 if self.relative_layer != paragraph.relative_layer {
206 diff.insert(DiffModifies::LAYER);
207 }
208
209 if self.text_style_data != paragraph.text_style_data {
210 diff.insert(DiffModifies::STYLE);
211 }
212
213 if self.event_handlers != paragraph.event_handlers {
214 diff.insert(DiffModifies::EVENT_HANDLERS);
215 }
216
217 if self.cursor_index != paragraph.cursor_index
218 || self.highlights != paragraph.highlights
219 || self.cursor_mode != paragraph.cursor_mode
220 || self.cursor_style != paragraph.cursor_style
221 || self.cursor_style_data != paragraph.cursor_style_data
222 || self.vertical_align != paragraph.vertical_align
223 {
224 diff.insert(DiffModifies::STYLE);
225 }
226
227 if self.text_style_data != paragraph.text_style_data
228 || self.line_height != paragraph.line_height
229 || self.max_lines != paragraph.max_lines
230 {
231 diff.insert(DiffModifies::TEXT_STYLE);
232 diff.insert(DiffModifies::LAYOUT);
233 }
234
235 if self.layout != paragraph.layout {
236 diff.insert(DiffModifies::STYLE);
237 diff.insert(DiffModifies::LAYOUT);
238 }
239
240 diff
241 }
242
243 fn layout(&'_ self) -> Cow<'_, LayoutData> {
244 Cow::Borrowed(&self.layout)
245 }
246 fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
247 None
248 }
249
250 fn style(&'_ self) -> Cow<'_, StyleState> {
251 Cow::Owned(StyleState::default())
252 }
253
254 fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
255 Cow::Borrowed(&self.text_style_data)
256 }
257
258 fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
259 Cow::Borrowed(&self.accessibility)
260 }
261
262 fn layer(&self) -> Layer {
263 self.relative_layer
264 }
265
266 fn measure(&self, context: LayoutContext) -> Option<(Size2D, Rc<dyn Any>)> {
267 let cached_paragraph = CachedParagraph {
268 text_style_state: context.text_style_state,
269 spans: &self.spans,
270 max_lines: self.max_lines,
271 line_height: self.line_height,
272 width: context.area_size.width,
273 };
274 let paragraph = context
275 .text_cache
276 .utilize(context.node_id, &cached_paragraph)
277 .unwrap_or_else(|| {
278 let mut paragraph_style = ParagraphStyle::default();
279 let mut text_style = TextStyle::default();
280
281 let mut font_families = context.text_style_state.font_families.clone();
282 font_families.extend_from_slice(context.fallback_fonts);
283
284 text_style.set_color(
285 context
286 .text_style_state
287 .color
288 .as_color()
289 .unwrap_or(Color::WHITE),
290 );
291 text_style.set_font_size(
292 f32::from(context.text_style_state.font_size) * context.scale_factor as f32,
293 );
294 text_style.set_font_families(&font_families);
295 text_style.set_font_style(FontStyle::new(
296 context.text_style_state.font_weight.into(),
297 context.text_style_state.font_width.into(),
298 context.text_style_state.font_slant.into(),
299 ));
300
301 if context.text_style_state.text_height.needs_custom_height() {
302 text_style.set_height_override(true);
303 text_style.set_half_leading(true);
304 }
305
306 if let Some(line_height) = self.line_height {
307 text_style.set_height_override(true);
308 text_style.set_height(line_height);
309 }
310
311 for text_shadow in context.text_style_state.text_shadows.iter() {
312 text_style.add_shadow((*text_shadow).into());
313 }
314
315 if let Some(ellipsis) = context.text_style_state.text_overflow.get_ellipsis() {
316 paragraph_style.set_ellipsis(ellipsis);
317 }
318
319 paragraph_style.set_text_style(&text_style);
320 paragraph_style.set_max_lines(self.max_lines);
321 paragraph_style.set_text_align(context.text_style_state.text_align.into());
322
323 let mut paragraph_builder =
324 ParagraphBuilder::new(¶graph_style, &*context.font_collection);
325
326 for span in &self.spans {
327 let text_style_state =
328 TextStyleState::from_data(context.text_style_state, &span.text_style_data);
329 let mut text_style = TextStyle::new();
330 let mut font_families = context.text_style_state.font_families.clone();
331 font_families.extend_from_slice(context.fallback_fonts);
332
333 for text_shadow in text_style_state.text_shadows.iter() {
334 text_style.add_shadow((*text_shadow).into());
335 }
336
337 text_style.set_color(text_style_state.color.as_color().unwrap_or(Color::WHITE));
338 text_style.set_font_size(
339 f32::from(text_style_state.font_size) * context.scale_factor as f32,
340 );
341 text_style.set_font_families(&font_families);
342 text_style.set_font_style(FontStyle::new(
343 text_style_state.font_weight.into(),
344 text_style_state.font_width.into(),
345 text_style_state.font_slant.into(),
346 ));
347 text_style.set_decoration_type(text_style_state.text_decoration.into());
348 if let Some(line_height) = self.line_height {
349 text_style.set_height_override(true);
350 text_style.set_height(line_height);
351 }
352 paragraph_builder.push_style(&text_style);
353 paragraph_builder.add_text(&span.text);
354 }
355
356 let mut paragraph = paragraph_builder.build();
357 paragraph.layout(
358 if self.max_lines == Some(1)
359 && context.text_style_state.text_align == TextAlign::default()
360 && !paragraph_style.ellipsized()
361 {
362 f32::MAX
363 } else {
364 context.area_size.width + 1.0
365 },
366 );
367 context
368 .text_cache
369 .insert(context.node_id, &cached_paragraph, paragraph)
370 });
371
372 let size = Size2D::new(paragraph.longest_line(), paragraph.height());
373
374 self.sk_paragraph
375 .0
376 .borrow_mut()
377 .replace(ParagraphHolderInner {
378 paragraph,
379 scale_factor: context.scale_factor,
380 });
381
382 Some((size, Rc::new(())))
383 }
384
385 fn should_hook_measurement(&self) -> bool {
386 true
387 }
388
389 fn should_measure_inner_children(&self) -> bool {
390 false
391 }
392
393 fn events_handlers(&'_ self) -> Option<Cow<'_, FxHashMap<EventName, EventHandlerType>>> {
394 Some(Cow::Borrowed(&self.event_handlers))
395 }
396
397 fn render(&self, context: RenderContext) {
398 let paragraph = self.sk_paragraph.0.borrow();
399 let ParagraphHolderInner { paragraph, .. } = paragraph.as_ref().unwrap();
400 let visible_area = context.layout_node.visible_area();
401
402 let cursor_area = match self.cursor_mode {
403 CursorMode::Fit => visible_area,
404 CursorMode::Expanded => context.layout_node.area,
405 };
406
407 let paragraph_height = paragraph.height();
408 let area_height = visible_area.height();
409 let vertical_offset = match self.vertical_align {
410 VerticalAlign::Start => 0.0,
411 VerticalAlign::Center => (area_height - paragraph_height).max(0.0) / 2.0,
412 };
413
414 let cursor_vertical_offset = match self.cursor_mode {
415 CursorMode::Fit => vertical_offset,
416 CursorMode::Expanded => 0.0,
417 };
418 let cursor_vertical_size_offset = match self.cursor_mode {
419 CursorMode::Fit => 0.,
420 CursorMode::Expanded => vertical_offset * 2.,
421 };
422
423 for (from, to) in self.highlights.iter() {
425 if from == to {
426 continue;
427 }
428 let (from, to) = { if from < to { (from, to) } else { (to, from) } };
429 let rects = paragraph.get_rects_for_range(
430 *from..*to,
431 RectHeightStyle::Tight,
432 RectWidthStyle::Tight,
433 );
434
435 let mut highlights_paint = Paint::default();
436 highlights_paint.set_anti_alias(true);
437 highlights_paint.set_style(PaintStyle::Fill);
438 highlights_paint.set_color(self.cursor_style_data.highlight_color);
439
440 if rects.is_empty() && *from == 0 {
441 let avg_line_height =
442 paragraph.height() / paragraph.get_line_metrics().len().max(1) as f32;
443 let rect = SkRect::new(
444 cursor_area.min_x(),
445 cursor_area.min_y() + cursor_vertical_offset,
446 cursor_area.min_x() + 6.,
447 cursor_area.min_y() + avg_line_height + cursor_vertical_size_offset,
448 );
449
450 context.canvas.draw_rect(rect, &highlights_paint);
451 }
452
453 for rect in rects {
454 let rect = SkRect::new(
455 cursor_area.min_x() + rect.rect.left,
456 cursor_area.min_y() + rect.rect.top + cursor_vertical_offset,
457 cursor_area.min_x() + rect.rect.right.max(6.),
458 cursor_area.min_y() + rect.rect.bottom + cursor_vertical_size_offset,
459 );
460 context.canvas.draw_rect(rect, &highlights_paint);
461 }
462 }
463
464 let visible_highlights = self
466 .highlights
467 .iter()
468 .filter(|highlight| highlight.0 != highlight.1)
469 .count()
470 > 0;
471
472 if let Some(cursor_index) = self.cursor_index
474 && self.cursor_style == CursorStyle::Block
475 && let Some(cursor_rect) = paragraph
476 .get_rects_for_range(
477 cursor_index..cursor_index + 1,
478 RectHeightStyle::Tight,
479 RectWidthStyle::Tight,
480 )
481 .first()
482 .map(|text| text.rect)
483 .or_else(|| {
484 let text_len = paragraph
486 .get_glyph_position_at_coordinate((f32::MAX, f32::MAX))
487 .position as usize;
488 let last_rects = paragraph.get_rects_for_range(
489 text_len.saturating_sub(1)..text_len,
490 RectHeightStyle::Tight,
491 RectWidthStyle::Tight,
492 );
493
494 if let Some(last_rect) = last_rects.first() {
495 let mut caret = last_rect.rect;
496 caret.left = caret.right;
497 Some(caret)
498 } else {
499 let avg_line_height =
500 paragraph.height() / paragraph.get_line_metrics().len().max(1) as f32;
501 Some(SkRect::new(0., 0., 6., avg_line_height))
502 }
503 })
504 {
505 let width = (cursor_rect.right - cursor_rect.left).max(6.0);
506 let cursor_rect = SkRect::new(
507 cursor_area.min_x() + cursor_rect.left,
508 cursor_area.min_y() + cursor_rect.top + cursor_vertical_offset,
509 cursor_area.min_x() + cursor_rect.left + width,
510 cursor_area.min_y() + cursor_rect.bottom + cursor_vertical_size_offset,
511 );
512
513 let mut paint = Paint::default();
514 paint.set_anti_alias(true);
515 paint.set_style(PaintStyle::Fill);
516 paint.set_color(self.cursor_style_data.color);
517
518 context.canvas.draw_rect(cursor_rect, &paint);
519 }
520
521 paint_paragraph_with_fill(
523 paragraph,
524 context.canvas,
525 Point2D::new(visible_area.min_x(), visible_area.min_y() + vertical_offset),
526 &context.text_style_state.color,
527 );
528
529 if let Some(cursor_index) = self.cursor_index
531 && !visible_highlights
532 {
533 let cursor_rects = paragraph.get_rects_for_range(
534 cursor_index..cursor_index + 1,
535 RectHeightStyle::Tight,
536 RectWidthStyle::Tight,
537 );
538 if let Some(cursor_rect) = cursor_rects.first().map(|text| text.rect).or_else(|| {
539 let text_len = paragraph
541 .get_glyph_position_at_coordinate((f32::MAX, f32::MAX))
542 .position as usize;
543 let last_rects = paragraph.get_rects_for_range(
544 text_len.saturating_sub(1)..text_len,
545 RectHeightStyle::Tight,
546 RectWidthStyle::Tight,
547 );
548
549 if let Some(last_rect) = last_rects.first() {
550 let mut caret = last_rect.rect;
551 caret.left = caret.right;
552 Some(caret)
553 } else {
554 None
555 }
556 }) {
557 let paint_color = self.cursor_style_data.color;
558 match self.cursor_style {
559 CursorStyle::Underline => {
560 let thickness = 2.0;
561 let underline_rect = SkRect::new(
562 cursor_area.min_x() + cursor_rect.left,
563 cursor_area.min_y() + cursor_rect.bottom - thickness
564 + cursor_vertical_offset,
565 cursor_area.min_x() + cursor_rect.right,
566 cursor_area.min_y() + cursor_rect.bottom + cursor_vertical_size_offset,
567 );
568
569 let mut paint = Paint::default();
570 paint.set_anti_alias(true);
571 paint.set_style(PaintStyle::Fill);
572 paint.set_color(paint_color);
573
574 context.canvas.draw_rect(underline_rect, &paint);
575 }
576 CursorStyle::Line => {
577 let cursor_rect = SkRect::new(
578 cursor_area.min_x() + cursor_rect.left,
579 cursor_area.min_y() + cursor_rect.top + cursor_vertical_offset,
580 cursor_area.min_x() + cursor_rect.left + 2.,
581 cursor_area.min_y() + cursor_rect.bottom + cursor_vertical_size_offset,
582 );
583
584 let mut paint = Paint::default();
585 paint.set_anti_alias(true);
586 paint.set_style(PaintStyle::Fill);
587 paint.set_color(paint_color);
588
589 context.canvas.draw_rect(cursor_rect, &paint);
590 }
591 _ => {}
592 }
593 }
594 }
595 }
596}
597
598impl From<Paragraph> for Element {
599 fn from(value: Paragraph) -> Self {
600 Element::Element {
601 key: value.key,
602 element: Rc::new(value.element),
603 elements: vec![],
604 }
605 }
606}
607
608pub(crate) fn paint_paragraph_with_fill(
611 paragraph: &SkParagraph,
612 canvas: &Canvas,
613 origin: Point2D,
614 fill: &Fill,
615) {
616 if matches!(fill, Fill::Color(_)) {
617 paragraph.paint(canvas, origin.to_tuple());
618 return;
619 }
620
621 let width = paragraph.longest_line().max(paragraph.max_width());
622 let height = paragraph.height();
623 let area = Area::new(origin, Size2D::new(width, height));
624 let bounds_rect = SkRect::from_xywh(area.min_x(), area.min_y(), width, height);
625
626 let layer = canvas.save_layer(&SaveLayerRec::default().bounds(&bounds_rect));
627
628 paragraph.paint(canvas, origin.to_tuple());
629
630 let mut paint = Paint::default();
631 paint.set_anti_alias(true);
632 paint.set_style(PaintStyle::Fill);
633 paint.set_blend_mode(BlendMode::SrcIn);
634 fill.apply_to_paint(&mut paint, area);
635
636 canvas.draw_rect(bounds_rect, &paint);
637
638 canvas.restore_to_count(layer);
639}
640
641impl KeyExt for Paragraph {
642 fn write_key(&mut self) -> &mut DiffKey {
643 &mut self.key
644 }
645}
646
647impl EventHandlersExt for Paragraph {
648 fn get_event_handlers(&mut self) -> &mut FxHashMap<EventName, EventHandlerType> {
649 &mut self.element.event_handlers
650 }
651}
652
653impl MaybeExt for Paragraph {}
654
655impl LayerExt for Paragraph {
656 fn get_layer(&mut self) -> &mut Layer {
657 &mut self.element.relative_layer
658 }
659}
660
661pub struct Paragraph {
662 key: DiffKey,
663 element: ParagraphElement,
664}
665
666impl LayoutExt for Paragraph {
667 fn get_layout(&mut self) -> &mut LayoutData {
668 &mut self.element.layout
669 }
670}
671
672impl ContainerExt for Paragraph {}
673
674impl AccessibilityExt for Paragraph {
675 fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
676 &mut self.element.accessibility
677 }
678}
679
680impl TextStyleExt for Paragraph {
681 fn get_text_style_data(&mut self) -> &mut TextStyleData {
682 &mut self.element.text_style_data
683 }
684}
685
686impl Paragraph {
687 pub fn try_downcast(element: &dyn ElementExt) -> Option<ParagraphElement> {
688 (element as &dyn Any)
689 .downcast_ref::<ParagraphElement>()
690 .cloned()
691 }
692
693 pub fn spans_iter(mut self, spans: impl Iterator<Item = Span<'static>>) -> Self {
694 let spans = spans.collect::<Vec<Span>>();
695 self.element.spans.extend(spans);
698 self
699 }
700
701 pub fn span(mut self, span: impl Into<Span<'static>>) -> Self {
702 let span = span.into();
703 self.element.spans.push(span);
706 self
707 }
708
709 pub fn cursor_color(mut self, cursor_color: impl Into<Color>) -> Self {
710 self.element.cursor_style_data.color = cursor_color.into();
711 self
712 }
713
714 pub fn highlight_color(mut self, highlight_color: impl Into<Color>) -> Self {
715 self.element.cursor_style_data.highlight_color = highlight_color.into();
716 self
717 }
718
719 pub fn cursor_style(mut self, cursor_style: impl Into<CursorStyle>) -> Self {
720 self.element.cursor_style = cursor_style.into();
721 self
722 }
723
724 pub fn holder(mut self, holder: ParagraphHolder) -> Self {
725 self.element.sk_paragraph = holder;
726 self
727 }
728
729 pub fn cursor_index(mut self, cursor_index: impl Into<Option<usize>>) -> Self {
730 self.element.cursor_index = cursor_index.into();
731 self
732 }
733
734 pub fn highlights(mut self, highlights: impl Into<Option<Vec<(usize, usize)>>>) -> Self {
735 if let Some(highlights) = highlights.into() {
736 self.element.highlights = highlights;
737 }
738 self
739 }
740
741 pub fn max_lines(mut self, max_lines: impl Into<Option<usize>>) -> Self {
742 self.element.max_lines = max_lines.into();
743 self
744 }
745
746 pub fn line_height(mut self, line_height: impl Into<Option<f32>>) -> Self {
747 self.element.line_height = line_height.into();
748 self
749 }
750
751 pub fn cursor_mode(mut self, cursor_mode: impl Into<CursorMode>) -> Self {
755 self.element.cursor_mode = cursor_mode.into();
756 self
757 }
758
759 pub fn vertical_align(mut self, vertical_align: impl Into<VerticalAlign>) -> Self {
763 self.element.vertical_align = vertical_align.into();
764 self
765 }
766}
767
768#[derive(Clone, PartialEq, Hash)]
769pub struct Span<'a> {
770 pub text_style_data: TextStyleData,
771 pub text: Cow<'a, str>,
772}
773
774impl From<&'static str> for Span<'static> {
775 fn from(text: &'static str) -> Self {
776 Span {
777 text_style_data: TextStyleData::default(),
778 text: text.into(),
779 }
780 }
781}
782
783impl From<String> for Span<'static> {
784 fn from(text: String) -> Self {
785 Span {
786 text_style_data: TextStyleData::default(),
787 text: text.into(),
788 }
789 }
790}
791
792impl<'a> Span<'a> {
793 pub fn new(text: impl Into<Cow<'a, str>>) -> Self {
794 Self {
795 text: text.into(),
796 text_style_data: TextStyleData::default(),
797 }
798 }
799}
800
801impl<'a> TextStyleExt for Span<'a> {
802 fn get_text_style_data(&mut self) -> &mut TextStyleData {
803 &mut self.text_style_data
804 }
805}