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