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 FontCollection,
18 FontStyle,
19 Paint,
20 PaintStyle,
21 ParagraphBuilder,
22 ParagraphStyle,
23 PlaceholderAlignment,
24 PlaceholderStyle,
25 RectHeightStyle,
26 RectWidthStyle,
27 SaveLayerRec,
28 SkParagraph,
29 SkRect,
30 TextBaseline,
31 TextStyle,
32};
33use rustc_hash::FxHashMap;
34use torin::prelude::{
35 Area,
36 Length,
37 Point2D,
38 Position,
39 PostMeasure,
40 Size2D,
41};
42
43use crate::{
44 data::{
45 AccessibilityData,
46 CursorStyleData,
47 EffectData,
48 LayoutData,
49 StyleState,
50 TextStyleData,
51 TextStyleState,
52 },
53 diff_key::DiffKey,
54 element::{
55 Element,
56 ElementExt,
57 EventHandlerType,
58 IntoElement,
59 LayoutContext,
60 PostMeasureContext,
61 RenderContext,
62 },
63 elements::rect::rect,
64 events::name::EventName,
65 layers::Layer,
66 node_id::NodeId,
67 prelude::{
68 AccessibilityExt,
69 ChildrenExt,
70 Color,
71 ContainerExt,
72 ContainerPositionExt,
73 EventHandlersExt,
74 Fill,
75 KeyExt,
76 LayerExt,
77 LayoutExt,
78 MaybeExt,
79 TextAlign,
80 TextStyleExt,
81 VerticalAlign,
82 },
83 style::cursor::{
84 CursorMode,
85 CursorStyle,
86 },
87 text_cache::CachedParagraph,
88 tree::DiffModifies,
89};
90
91pub fn paragraph() -> Paragraph {
104 Paragraph {
105 key: DiffKey::None,
106 element: ParagraphElement::default(),
107 children: Vec::new(),
108 }
109}
110
111pub struct ParagraphHolderInner {
112 pub paragraph: Rc<SkParagraph>,
113 pub scale_factor: f64,
114}
115
116#[derive(Clone)]
119pub struct ParagraphHolder(pub Rc<RefCell<Option<ParagraphHolderInner>>>);
120
121impl PartialEq for ParagraphHolder {
122 fn eq(&self, other: &Self) -> bool {
123 Rc::ptr_eq(&self.0, &other.0)
124 }
125}
126
127impl Debug for ParagraphHolder {
128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129 f.write_str("ParagraphHolder")
130 }
131}
132
133impl Default for ParagraphHolder {
134 fn default() -> Self {
135 Self(Rc::new(RefCell::new(None)))
136 }
137}
138
139#[derive(PartialEq, Clone)]
141pub enum ParagraphContent {
142 Span,
143 Element,
144}
145
146#[derive(PartialEq, Clone)]
147pub struct ParagraphElement {
148 pub layout: LayoutData,
149 pub spans: Vec<Span<'static>>,
150 pub contents: Vec<ParagraphContent>,
151 pub accessibility: AccessibilityData,
152 pub text_style_data: TextStyleData,
153 pub cursor_style_data: CursorStyleData,
154 pub event_handlers: FxHashMap<EventName, EventHandlerType>,
155 pub sk_paragraph: ParagraphHolder,
156 pub cursor_index: Option<usize>,
157 pub highlights: Vec<(usize, usize)>,
158 pub max_lines: Option<usize>,
159 pub line_height: Option<f32>,
160 pub relative_layer: Layer,
161 pub cursor_style: CursorStyle,
162 pub cursor_mode: CursorMode,
163 pub vertical_align: VerticalAlign,
164}
165
166impl Default for ParagraphElement {
167 fn default() -> Self {
168 let mut accessibility = AccessibilityData::default();
169 accessibility.builder.set_role(accesskit::Role::Paragraph);
170 Self {
171 layout: Default::default(),
172 spans: Default::default(),
173 contents: Default::default(),
174 accessibility,
175 text_style_data: Default::default(),
176 cursor_style_data: Default::default(),
177 event_handlers: Default::default(),
178 sk_paragraph: Default::default(),
179 cursor_index: Default::default(),
180 highlights: Default::default(),
181 max_lines: Default::default(),
182 line_height: Default::default(),
183 relative_layer: Default::default(),
184 cursor_style: CursorStyle::default(),
185 cursor_mode: CursorMode::default(),
186 vertical_align: VerticalAlign::default(),
187 }
188 }
189}
190
191impl Display for ParagraphElement {
192 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193 f.write_str(
194 &self
195 .spans
196 .iter()
197 .map(|s| s.text.clone())
198 .collect::<Vec<_>>()
199 .join("\n"),
200 )
201 }
202}
203
204impl ElementExt for ParagraphElement {
205 fn changed(&self, other: &Rc<dyn ElementExt>) -> bool {
206 let Some(paragraph) = (other.as_ref() as &dyn Any).downcast_ref::<ParagraphElement>()
207 else {
208 return false;
209 };
210 self != paragraph
211 }
212
213 fn diff(&self, other: &Rc<dyn ElementExt>) -> DiffModifies {
214 let Some(paragraph) = (other.as_ref() as &dyn Any).downcast_ref::<ParagraphElement>()
215 else {
216 return DiffModifies::all();
217 };
218
219 let mut diff = DiffModifies::empty();
220
221 if self.spans != paragraph.spans || self.contents != paragraph.contents {
222 diff.insert(DiffModifies::STYLE);
223 diff.insert(DiffModifies::LAYOUT);
224 }
225
226 if self.accessibility != paragraph.accessibility {
227 diff.insert(DiffModifies::ACCESSIBILITY);
228 }
229
230 if self.relative_layer != paragraph.relative_layer {
231 diff.insert(DiffModifies::LAYER);
232 }
233
234 if self.text_style_data != paragraph.text_style_data {
235 diff.insert(DiffModifies::STYLE);
236 }
237
238 if self.event_handlers != paragraph.event_handlers {
239 diff.insert(DiffModifies::EVENT_HANDLERS);
240 }
241
242 if self.cursor_index != paragraph.cursor_index
243 || self.highlights != paragraph.highlights
244 || self.cursor_mode != paragraph.cursor_mode
245 || self.cursor_style != paragraph.cursor_style
246 || self.cursor_style_data != paragraph.cursor_style_data
247 || self.vertical_align != paragraph.vertical_align
248 {
249 diff.insert(DiffModifies::STYLE);
250 }
251
252 if self.text_style_data != paragraph.text_style_data
253 || self.line_height != paragraph.line_height
254 || self.max_lines != paragraph.max_lines
255 {
256 diff.insert(DiffModifies::TEXT_STYLE);
257 diff.insert(DiffModifies::LAYOUT);
258 }
259
260 if self.layout != paragraph.layout {
261 diff.insert(DiffModifies::STYLE);
262 diff.insert(DiffModifies::LAYOUT);
263 }
264
265 diff
266 }
267
268 fn layout(&'_ self) -> Cow<'_, LayoutData> {
269 Cow::Borrowed(&self.layout)
270 }
271 fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
272 None
273 }
274
275 fn style(&'_ self) -> Cow<'_, StyleState> {
276 Cow::Owned(StyleState::default())
277 }
278
279 fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
280 Cow::Borrowed(&self.text_style_data)
281 }
282
283 fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
284 Cow::Borrowed(&self.accessibility)
285 }
286
287 fn layer(&self) -> Layer {
288 self.relative_layer
289 }
290
291 fn measure(&self, context: LayoutContext) -> Option<(Size2D, Rc<dyn Any>)> {
292 let cached_paragraph = CachedParagraph {
293 text_style_state: context.text_style_state,
294 spans: &self.spans,
295 max_lines: self.max_lines,
296 line_height: self.line_height,
297 width: context.area_size.width,
298 };
299 let paragraph = context
300 .text_cache
301 .utilize(context.node_id, &cached_paragraph)
302 .unwrap_or_else(|| {
303 let width = if self.max_lines == Some(1)
304 && context.text_style_state.text_align == TextAlign::default()
305 && context
306 .text_style_state
307 .text_overflow
308 .get_ellipsis()
309 .is_none()
310 {
311 f32::MAX
312 } else {
313 context.area_size.width + 1.0
314 };
315
316 let paragraph = self.build_paragraph(
317 context.text_style_state,
318 context.fallback_fonts,
319 context.scale_factor,
320 context.font_collection,
321 width,
322 &[],
323 );
324 context
325 .text_cache
326 .insert(context.node_id, &cached_paragraph, paragraph)
327 });
328
329 let size = Size2D::new(paragraph.longest_line(), paragraph.height()).max(Size2D::zero());
330
331 self.sk_paragraph
332 .0
333 .borrow_mut()
334 .replace(ParagraphHolderInner {
335 paragraph,
336 scale_factor: context.scale_factor,
337 });
338
339 Some((size, Rc::new(())))
340 }
341
342 fn should_hook_measurement(&self) -> bool {
343 true
344 }
345
346 fn should_measure_inner_children(&self) -> bool {
347 self.has_inline_content()
348 }
349
350 fn needs_post_measure(&self) -> bool {
351 self.has_inline_content()
352 }
353
354 fn post_measure(&self, context: PostMeasureContext) -> PostMeasure<NodeId> {
355 if context.children.is_empty() {
356 return PostMeasure::default();
357 }
358
359 let placeholders: Vec<Size2D> = context
360 .children
361 .iter()
362 .map(|child| {
363 context
364 .layout
365 .get(child)
366 .map(|node| node.area.size)
367 .unwrap()
368 })
369 .collect();
370
371 let width = self
372 .sk_paragraph
373 .0
374 .borrow()
375 .as_ref()
376 .map(|holder| holder.paragraph.max_width())
377 .unwrap();
378
379 let paragraph = self.build_paragraph(
380 context.text_style_state,
381 context.fallback_fonts,
382 context.scale_factor,
383 context.font_collection,
384 width,
385 &placeholders,
386 );
387 let rects = paragraph.get_rects_for_placeholders();
388 let paragraph_height = paragraph.height();
389 let content_size = Size2D::new(paragraph.longest_line(), paragraph_height);
391
392 self.sk_paragraph
393 .0
394 .borrow_mut()
395 .replace(ParagraphHolderInner {
396 paragraph: Rc::new(paragraph),
397 scale_factor: context.scale_factor,
398 });
399
400 let visible_area = context.node_layout.visible_area();
401 let vertical_offset = match self.vertical_align {
402 VerticalAlign::Start => 0.0,
403 VerticalAlign::Center => (visible_area.height() - paragraph_height).max(0.0) / 2.0,
404 };
405 let origin = visible_area.origin;
406
407 let mut offsets = Vec::new();
408 let mut hidden_children = Vec::new();
409 for (index, child_id) in context.children.iter().enumerate() {
410 let Some(current) = context.layout.get(child_id).map(|node| node.area.origin) else {
411 continue;
412 };
413 match rects.get(index) {
414 Some(rect) => {
415 let offset_x = origin.x + rect.rect.left - current.x;
416 let offset_y = origin.y + vertical_offset + rect.rect.top - current.y;
417 offsets.push((*child_id, Length::new(offset_x), Length::new(offset_y)));
418 }
419 None => hidden_children.push(*child_id),
421 }
422 }
423
424 PostMeasure {
425 content_size: Some(content_size),
426 offsets,
427 hidden_children,
428 }
429 }
430
431 fn events_handlers(&'_ self) -> Option<Cow<'_, FxHashMap<EventName, EventHandlerType>>> {
432 Some(Cow::Borrowed(&self.event_handlers))
433 }
434
435 fn render(&self, context: RenderContext) {
436 let paragraph = self.sk_paragraph.0.borrow();
437 let ParagraphHolderInner { paragraph, .. } = paragraph.as_ref().unwrap();
438 let visible_area = context.layout_node.visible_area();
439
440 let cursor_area = match self.cursor_mode {
441 CursorMode::Fit => visible_area,
442 CursorMode::Expanded => context.layout_node.area,
443 };
444
445 let paragraph_height = paragraph.height();
446 let area_height = visible_area.height();
447 let vertical_offset = match self.vertical_align {
448 VerticalAlign::Start => 0.0,
449 VerticalAlign::Center => (area_height - paragraph_height).max(0.0) / 2.0,
450 };
451
452 let cursor_vertical_offset = match self.cursor_mode {
453 CursorMode::Fit => vertical_offset,
454 CursorMode::Expanded => 0.0,
455 };
456 let cursor_vertical_size_offset = match self.cursor_mode {
457 CursorMode::Fit => 0.,
458 CursorMode::Expanded => vertical_offset * 2.,
459 };
460
461 for (from, to) in self.highlights.iter() {
463 if from == to {
464 continue;
465 }
466 let (from, to) = { if from < to { (from, to) } else { (to, from) } };
467 let rects = paragraph.get_rects_for_range(
468 *from..*to,
469 RectHeightStyle::Tight,
470 RectWidthStyle::Tight,
471 );
472
473 let mut highlights_paint = Paint::default();
474 highlights_paint.set_anti_alias(true);
475 highlights_paint.set_style(PaintStyle::Fill);
476 highlights_paint.set_color(self.cursor_style_data.highlight_color);
477
478 if rects.is_empty() && *from == 0 {
479 let avg_line_height =
480 paragraph.height() / paragraph.get_line_metrics().len().max(1) as f32;
481 let rect = SkRect::new(
482 cursor_area.min_x(),
483 cursor_area.min_y() + cursor_vertical_offset,
484 cursor_area.min_x() + 6.,
485 cursor_area.min_y() + avg_line_height + cursor_vertical_size_offset,
486 );
487
488 context.canvas.draw_rect(rect, &highlights_paint);
489 }
490
491 for rect in rects {
492 let rect = SkRect::new(
493 cursor_area.min_x() + rect.rect.left,
494 cursor_area.min_y() + rect.rect.top + cursor_vertical_offset,
495 cursor_area.min_x() + rect.rect.right.max(6.),
496 cursor_area.min_y() + rect.rect.bottom + cursor_vertical_size_offset,
497 );
498 context.canvas.draw_rect(rect, &highlights_paint);
499 }
500 }
501
502 let visible_highlights = self
504 .highlights
505 .iter()
506 .filter(|highlight| highlight.0 != highlight.1)
507 .count()
508 > 0;
509
510 if let Some(cursor_index) = self.cursor_index
512 && self.cursor_style == CursorStyle::Block
513 && let Some(cursor_rect) = paragraph
514 .get_rects_for_range(
515 cursor_index..cursor_index + 1,
516 RectHeightStyle::Tight,
517 RectWidthStyle::Tight,
518 )
519 .first()
520 .map(|text| text.rect)
521 .or_else(|| {
522 let text_len = paragraph
524 .get_glyph_position_at_coordinate((f32::MAX, f32::MAX))
525 .position as usize;
526 let last_rects = paragraph.get_rects_for_range(
527 text_len.saturating_sub(1)..text_len,
528 RectHeightStyle::Tight,
529 RectWidthStyle::Tight,
530 );
531
532 if let Some(last_rect) = last_rects.first() {
533 let mut caret = last_rect.rect;
534 caret.left = caret.right;
535 Some(caret)
536 } else {
537 let avg_line_height =
538 paragraph.height() / paragraph.get_line_metrics().len().max(1) as f32;
539 Some(SkRect::new(0., 0., 6., avg_line_height))
540 }
541 })
542 {
543 let width = (cursor_rect.right - cursor_rect.left).max(6.0);
544 let cursor_rect = SkRect::new(
545 cursor_area.min_x() + cursor_rect.left,
546 cursor_area.min_y() + cursor_rect.top + cursor_vertical_offset,
547 cursor_area.min_x() + cursor_rect.left + width,
548 cursor_area.min_y() + cursor_rect.bottom + cursor_vertical_size_offset,
549 );
550
551 let mut paint = Paint::default();
552 paint.set_anti_alias(true);
553 paint.set_style(PaintStyle::Fill);
554 paint.set_color(self.cursor_style_data.color);
555
556 context.canvas.draw_rect(cursor_rect, &paint);
557 }
558
559 paint_paragraph_with_fill(
561 paragraph,
562 context.canvas,
563 Point2D::new(visible_area.min_x(), visible_area.min_y() + vertical_offset),
564 &context.text_style_state.color,
565 );
566
567 if let Some(cursor_index) = self.cursor_index
569 && !visible_highlights
570 {
571 let cursor_rects = paragraph.get_rects_for_range(
572 cursor_index..cursor_index + 1,
573 RectHeightStyle::Tight,
574 RectWidthStyle::Tight,
575 );
576 if let Some(cursor_rect) = cursor_rects.first().map(|text| text.rect).or_else(|| {
577 let text_len = paragraph
579 .get_glyph_position_at_coordinate((f32::MAX, f32::MAX))
580 .position as usize;
581 let last_rects = paragraph.get_rects_for_range(
582 text_len.saturating_sub(1)..text_len,
583 RectHeightStyle::Tight,
584 RectWidthStyle::Tight,
585 );
586
587 if let Some(last_rect) = last_rects.first() {
588 let mut caret = last_rect.rect;
589 caret.left = caret.right;
590 Some(caret)
591 } else {
592 None
593 }
594 }) {
595 let paint_color = self.cursor_style_data.color;
596 match self.cursor_style {
597 CursorStyle::Underline => {
598 let thickness = 2.0;
599 let underline_rect = SkRect::new(
600 cursor_area.min_x() + cursor_rect.left,
601 cursor_area.min_y() + cursor_rect.bottom - thickness
602 + cursor_vertical_offset,
603 cursor_area.min_x() + cursor_rect.right,
604 cursor_area.min_y() + cursor_rect.bottom + cursor_vertical_size_offset,
605 );
606
607 let mut paint = Paint::default();
608 paint.set_anti_alias(true);
609 paint.set_style(PaintStyle::Fill);
610 paint.set_color(paint_color);
611
612 context.canvas.draw_rect(underline_rect, &paint);
613 }
614 CursorStyle::Line => {
615 let cursor_rect = SkRect::new(
616 cursor_area.min_x() + cursor_rect.left,
617 cursor_area.min_y() + cursor_rect.top + cursor_vertical_offset,
618 cursor_area.min_x() + cursor_rect.left + 2.,
619 cursor_area.min_y() + cursor_rect.bottom + cursor_vertical_size_offset,
620 );
621
622 let mut paint = Paint::default();
623 paint.set_anti_alias(true);
624 paint.set_style(PaintStyle::Fill);
625 paint.set_color(paint_color);
626
627 context.canvas.draw_rect(cursor_rect, &paint);
628 }
629 _ => {}
630 }
631 }
632 }
633 }
634}
635
636impl ParagraphElement {
637 fn has_inline_content(&self) -> bool {
638 self.contents
639 .iter()
640 .any(|content| matches!(content, ParagraphContent::Element))
641 }
642
643 fn build_paragraph(
646 &self,
647 text_style_state: &TextStyleState,
648 fallback_fonts: &[Cow<'static, str>],
649 scale_factor: f64,
650 font_collection: &FontCollection,
651 width: f32,
652 placeholders: &[Size2D],
653 ) -> SkParagraph {
654 let mut paragraph_style = ParagraphStyle::default();
655
656 if let Some(ellipsis) = text_style_state.text_overflow.get_ellipsis() {
657 paragraph_style.set_ellipsis(ellipsis);
658 }
659
660 paragraph_style.set_text_style(&base_text_style(
661 text_style_state,
662 fallback_fonts,
663 scale_factor,
664 self.line_height,
665 ));
666 paragraph_style.set_max_lines(self.max_lines);
667 paragraph_style.set_text_align(text_style_state.text_align.into());
668
669 let mut paragraph_builder = ParagraphBuilder::new(¶graph_style, font_collection);
670
671 let mut spans = self.spans.iter();
672 let mut placeholders = placeholders.iter();
673 for content in &self.contents {
674 match content {
675 ParagraphContent::Span => {
676 let Some(span) = spans.next() else { continue };
677 paragraph_builder.push_style(&span_text_style(
678 text_style_state,
679 fallback_fonts,
680 scale_factor,
681 span,
682 self.line_height,
683 ));
684 paragraph_builder.add_text(&span.text);
685 }
686 ParagraphContent::Element => {
687 let Some(size) = placeholders.next() else {
688 continue;
689 };
690 paragraph_builder.add_placeholder(&PlaceholderStyle::new(
691 size.width,
692 size.height,
693 PlaceholderAlignment::Middle,
694 TextBaseline::Alphabetic,
695 0.0,
696 ));
697 }
698 }
699 }
700
701 let mut paragraph = paragraph_builder.build();
702 paragraph.layout(width);
703 paragraph
704 }
705}
706
707impl From<Paragraph> for Element {
708 fn from(value: Paragraph) -> Self {
709 let elements = value
710 .children
711 .into_iter()
712 .map(|child| {
713 rect()
714 .position(Position::new_absolute())
715 .child(child)
716 .into_element()
717 })
718 .collect();
719
720 Element::Element {
721 key: value.key,
722 element: Rc::new(value.element),
723 elements,
724 }
725 }
726}
727
728fn base_text_style(
730 text_style_state: &TextStyleState,
731 fallback_fonts: &[Cow<'static, str>],
732 scale_factor: f64,
733 line_height: Option<f32>,
734) -> TextStyle {
735 let mut text_style = TextStyle::default();
736
737 let mut font_families = text_style_state.font_families.clone();
738 font_families.extend_from_slice(fallback_fonts);
739
740 text_style.set_color(text_style_state.color.as_color().unwrap_or(Color::WHITE));
741 text_style.set_font_size(f32::from(text_style_state.font_size) * scale_factor as f32);
742 text_style.set_font_families(&font_families);
743 text_style.set_font_style(FontStyle::new(
744 text_style_state.font_weight.into(),
745 text_style_state.font_width.into(),
746 text_style_state.font_slant.into(),
747 ));
748
749 if text_style_state.text_height.needs_custom_height() {
750 text_style.set_height_override(true);
751 text_style.set_half_leading(true);
752 }
753
754 if let Some(line_height) = line_height {
755 text_style.set_height_override(true);
756 text_style.set_height(line_height);
757 }
758
759 for text_shadow in text_style_state.text_shadows.iter() {
760 text_style.add_shadow((*text_shadow).into());
761 }
762
763 text_style
764}
765
766fn span_text_style(
768 text_style_state: &TextStyleState,
769 fallback_fonts: &[Cow<'static, str>],
770 scale_factor: f64,
771 span: &Span,
772 line_height: Option<f32>,
773) -> TextStyle {
774 let span_style = TextStyleState::from_data(text_style_state, &span.text_style_data);
775 let mut text_style = TextStyle::new();
776 let mut font_families = text_style_state.font_families.clone();
777 font_families.extend_from_slice(fallback_fonts);
778
779 for text_shadow in span_style.text_shadows.iter() {
780 text_style.add_shadow((*text_shadow).into());
781 }
782
783 text_style.set_color(span_style.color.as_color().unwrap_or(Color::WHITE));
784 text_style.set_font_size(f32::from(span_style.font_size) * scale_factor as f32);
785 text_style.set_font_families(&font_families);
786 text_style.set_font_style(FontStyle::new(
787 span_style.font_weight.into(),
788 span_style.font_width.into(),
789 span_style.font_slant.into(),
790 ));
791 text_style.set_decoration_type(span_style.text_decoration.into());
792 if let Some(line_height) = line_height {
793 text_style.set_height_override(true);
794 text_style.set_height(line_height);
795 }
796 text_style
797}
798
799pub(crate) fn paint_paragraph_with_fill(
802 paragraph: &SkParagraph,
803 canvas: &Canvas,
804 origin: Point2D,
805 fill: &Fill,
806) {
807 if matches!(fill, Fill::Color(_)) {
808 paragraph.paint(canvas, origin.to_tuple());
809 return;
810 }
811
812 let width = paragraph.longest_line();
813 let height = paragraph.height();
814 let area = Area::new(origin, Size2D::new(width, height));
815 let bounds_rect = SkRect::from_xywh(area.min_x(), area.min_y(), width, height);
816
817 let layer = canvas.save_layer(&SaveLayerRec::default().bounds(&bounds_rect));
818
819 paragraph.paint(canvas, origin.to_tuple());
820
821 let mut paint = Paint::default();
822 paint.set_anti_alias(true);
823 paint.set_style(PaintStyle::Fill);
824 paint.set_blend_mode(BlendMode::SrcIn);
825 fill.apply_to_paint(&mut paint, area);
826
827 canvas.draw_rect(bounds_rect, &paint);
828
829 canvas.restore_to_count(layer);
830}
831
832impl KeyExt for Paragraph {
833 fn write_key(&mut self) -> &mut DiffKey {
834 &mut self.key
835 }
836}
837
838impl EventHandlersExt for Paragraph {
839 fn get_event_handlers(&mut self) -> &mut FxHashMap<EventName, EventHandlerType> {
840 &mut self.element.event_handlers
841 }
842}
843
844impl MaybeExt for Paragraph {}
845
846impl LayerExt for Paragraph {
847 fn get_layer(&mut self) -> &mut Layer {
848 &mut self.element.relative_layer
849 }
850}
851
852pub struct Paragraph {
853 key: DiffKey,
854 element: ParagraphElement,
855 children: Vec<Element>,
856}
857
858impl LayoutExt for Paragraph {
859 fn get_layout(&mut self) -> &mut LayoutData {
860 &mut self.element.layout
861 }
862}
863
864impl ContainerExt for Paragraph {}
865
866impl ChildrenExt for Paragraph {
869 fn get_children(&mut self) -> &mut Vec<Element> {
870 &mut self.children
871 }
872
873 fn child<C: IntoElement>(mut self, child: C) -> Self {
874 self.element.contents.push(ParagraphContent::Element);
875 self.children.push(child.into_element());
876 self
877 }
878
879 fn children(self, children: impl IntoIterator<Item = Element>) -> Self {
880 children
881 .into_iter()
882 .fold(self, |paragraph, child| paragraph.child(child))
883 }
884
885 fn maybe_child<C: IntoElement>(self, child: Option<C>) -> Self {
886 match child {
887 Some(child) => self.child(child),
888 None => self,
889 }
890 }
891}
892
893impl AccessibilityExt for Paragraph {
894 fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
895 &mut self.element.accessibility
896 }
897}
898
899impl TextStyleExt for Paragraph {
900 fn get_text_style_data(&mut self) -> &mut TextStyleData {
901 &mut self.element.text_style_data
902 }
903}
904
905impl Paragraph {
906 pub fn try_downcast(element: &dyn ElementExt) -> Option<ParagraphElement> {
907 (element as &dyn Any)
908 .downcast_ref::<ParagraphElement>()
909 .cloned()
910 }
911
912 pub fn spans_iter(mut self, spans: impl Iterator<Item = Span<'static>>) -> Self {
914 for span in spans {
917 self.push_span(span);
918 }
919 self
920 }
921
922 pub fn span(mut self, span: impl Into<Span<'static>>) -> Self {
924 self.push_span(span.into());
927 self
928 }
929
930 fn push_span(&mut self, span: Span<'static>) {
931 self.element.contents.push(ParagraphContent::Span);
932 self.element.spans.push(span);
933 }
934
935 pub fn cursor_color(mut self, cursor_color: impl Into<Color>) -> Self {
937 self.element.cursor_style_data.color = cursor_color.into();
938 self
939 }
940
941 pub fn highlight_color(mut self, highlight_color: impl Into<Color>) -> Self {
943 self.element.cursor_style_data.highlight_color = highlight_color.into();
944 self
945 }
946
947 pub fn cursor_style(mut self, cursor_style: impl Into<CursorStyle>) -> Self {
949 self.element.cursor_style = cursor_style.into();
950 self
951 }
952
953 pub fn holder(mut self, holder: ParagraphHolder) -> Self {
955 self.element.sk_paragraph = holder;
956 self
957 }
958
959 pub fn cursor_index(mut self, cursor_index: impl Into<Option<usize>>) -> Self {
961 self.element.cursor_index = cursor_index.into();
962 self
963 }
964
965 pub fn highlights(mut self, highlights: impl Into<Option<Vec<(usize, usize)>>>) -> Self {
967 if let Some(highlights) = highlights.into() {
968 self.element.highlights = highlights;
969 }
970 self
971 }
972
973 pub fn max_lines(mut self, max_lines: impl Into<Option<usize>>) -> Self {
975 self.element.max_lines = max_lines.into();
976 self
977 }
978
979 pub fn line_height(mut self, line_height: impl Into<Option<f32>>) -> Self {
981 self.element.line_height = line_height.into();
982 self
983 }
984
985 pub fn cursor_mode(mut self, cursor_mode: impl Into<CursorMode>) -> Self {
989 self.element.cursor_mode = cursor_mode.into();
990 self
991 }
992
993 pub fn vertical_align(mut self, vertical_align: impl Into<VerticalAlign>) -> Self {
997 self.element.vertical_align = vertical_align.into();
998 self
999 }
1000}
1001
1002#[derive(Clone, PartialEq, Hash)]
1013pub struct Span<'a> {
1014 pub text_style_data: TextStyleData,
1015 pub text: Cow<'a, str>,
1016}
1017
1018impl From<&'static str> for Span<'static> {
1019 fn from(text: &'static str) -> Self {
1020 Span {
1021 text_style_data: TextStyleData::default(),
1022 text: text.into(),
1023 }
1024 }
1025}
1026
1027impl From<String> for Span<'static> {
1028 fn from(text: String) -> Self {
1029 Span {
1030 text_style_data: TextStyleData::default(),
1031 text: text.into(),
1032 }
1033 }
1034}
1035
1036impl<'a> Span<'a> {
1037 pub fn new(text: impl Into<Cow<'a, str>>) -> Self {
1039 Self {
1040 text: text.into(),
1041 text_style_data: TextStyleData::default(),
1042 }
1043 }
1044}
1045
1046impl<'a> TextStyleExt for Span<'a> {
1047 fn get_text_style_data(&mut self) -> &mut TextStyleData {
1048 &mut self.text_style_data
1049 }
1050}