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