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 is_transparent(&self) -> bool {
280 false
281 }
282
283 fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
284 Cow::Borrowed(&self.text_style_data)
285 }
286
287 fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
288 Cow::Borrowed(&self.accessibility)
289 }
290
291 fn layer(&self) -> Layer {
292 self.relative_layer
293 }
294
295 fn measure(&self, context: LayoutContext) -> Option<(Size2D, Rc<dyn Any>)> {
296 let cached_paragraph = CachedParagraph {
297 text_style_state: context.text_style_state,
298 spans: &self.spans,
299 max_lines: self.max_lines,
300 line_height: self.line_height,
301 width: context.area_size.width,
302 };
303 let paragraph = context
304 .text_cache
305 .utilize(context.node_id, &cached_paragraph)
306 .unwrap_or_else(|| {
307 let width = if self.max_lines == Some(1)
308 && context.text_style_state.text_align == TextAlign::default()
309 && context
310 .text_style_state
311 .text_overflow
312 .get_ellipsis()
313 .is_none()
314 {
315 f32::MAX
316 } else {
317 context.area_size.width + 1.0
318 };
319
320 let paragraph = self.build_paragraph(
321 context.text_style_state,
322 context.fallback_fonts,
323 context.scale_factor,
324 context.font_collection,
325 width,
326 &[],
327 );
328 context
329 .text_cache
330 .insert(context.node_id, &cached_paragraph, paragraph)
331 });
332
333 let size = Size2D::new(paragraph.longest_line(), paragraph.height()).max(Size2D::zero());
334
335 self.sk_paragraph
336 .0
337 .borrow_mut()
338 .replace(ParagraphHolderInner {
339 paragraph,
340 scale_factor: context.scale_factor,
341 });
342
343 Some((size, Rc::new(())))
344 }
345
346 fn should_hook_measurement(&self) -> bool {
347 true
348 }
349
350 fn should_measure_inner_children(&self) -> bool {
351 self.has_inline_content()
352 }
353
354 fn needs_post_measure(&self) -> bool {
355 self.has_inline_content()
356 }
357
358 fn post_measure(&self, context: PostMeasureContext) -> PostMeasure<NodeId> {
359 if context.children.is_empty() {
360 return PostMeasure::default();
361 }
362
363 let placeholders: Vec<Size2D> = context
364 .children
365 .iter()
366 .map(|child| {
367 context
368 .layout
369 .get(child)
370 .map(|node| node.area.size)
371 .unwrap()
372 })
373 .collect();
374
375 let width = self
376 .sk_paragraph
377 .0
378 .borrow()
379 .as_ref()
380 .map(|holder| holder.paragraph.max_width())
381 .unwrap();
382
383 let paragraph = self.build_paragraph(
384 context.text_style_state,
385 context.fallback_fonts,
386 context.scale_factor,
387 context.font_collection,
388 width,
389 &placeholders,
390 );
391 let rects = paragraph.get_rects_for_placeholders();
392 let paragraph_height = paragraph.height();
393 let content_size = Size2D::new(paragraph.longest_line(), paragraph_height);
395
396 self.sk_paragraph
397 .0
398 .borrow_mut()
399 .replace(ParagraphHolderInner {
400 paragraph: Rc::new(paragraph),
401 scale_factor: context.scale_factor,
402 });
403
404 let visible_area = context.node_layout.visible_area();
405 let vertical_offset = match self.vertical_align {
406 VerticalAlign::Start => 0.0,
407 VerticalAlign::Center => (visible_area.height() - paragraph_height).max(0.0) / 2.0,
408 };
409 let origin = visible_area.origin;
410
411 let mut offsets = Vec::new();
412 let mut hidden_children = Vec::new();
413 for (index, child_id) in context.children.iter().enumerate() {
414 let Some(current) = context.layout.get(child_id).map(|node| node.area.origin) else {
415 continue;
416 };
417 match rects.get(index) {
418 Some(rect) => {
419 let offset_x = origin.x + rect.rect.left - current.x;
420 let offset_y = origin.y + vertical_offset + rect.rect.top - current.y;
421 offsets.push((*child_id, Length::new(offset_x), Length::new(offset_y)));
422 }
423 None => hidden_children.push(*child_id),
425 }
426 }
427
428 PostMeasure {
429 content_size: Some(content_size),
430 offsets,
431 hidden_children,
432 }
433 }
434
435 fn events_handlers(&'_ self) -> Option<Cow<'_, FxHashMap<EventName, EventHandlerType>>> {
436 Some(Cow::Borrowed(&self.event_handlers))
437 }
438
439 fn render(&self, context: RenderContext) {
440 let paragraph = self.sk_paragraph.0.borrow();
441 let ParagraphHolderInner { paragraph, .. } = paragraph.as_ref().unwrap();
442 let visible_area = context.layout_node.visible_area();
443
444 let cursor_area = match self.cursor_mode {
445 CursorMode::Fit => visible_area,
446 CursorMode::Expanded => context.layout_node.area,
447 };
448
449 let paragraph_height = paragraph.height();
450 let area_height = visible_area.height();
451 let vertical_offset = match self.vertical_align {
452 VerticalAlign::Start => 0.0,
453 VerticalAlign::Center => (area_height - paragraph_height).max(0.0) / 2.0,
454 };
455
456 let cursor_vertical_offset = match self.cursor_mode {
457 CursorMode::Fit => vertical_offset,
458 CursorMode::Expanded => 0.0,
459 };
460 let cursor_vertical_size_offset = match self.cursor_mode {
461 CursorMode::Fit => 0.,
462 CursorMode::Expanded => vertical_offset * 2.,
463 };
464
465 for (from, to) in self.highlights.iter() {
467 if from == to {
468 continue;
469 }
470 let (from, to) = { if from < to { (from, to) } else { (to, from) } };
471 let rects = paragraph.get_rects_for_range(
472 *from..*to,
473 RectHeightStyle::Tight,
474 RectWidthStyle::Tight,
475 );
476
477 let mut highlights_paint = Paint::default();
478 highlights_paint.set_anti_alias(true);
479 highlights_paint.set_style(PaintStyle::Fill);
480 highlights_paint.set_color(self.cursor_style_data.highlight_color);
481
482 if rects.is_empty() && *from == 0 {
483 let avg_line_height =
484 paragraph.height() / paragraph.get_line_metrics().len().max(1) as f32;
485 let rect = SkRect::new(
486 cursor_area.min_x(),
487 cursor_area.min_y() + cursor_vertical_offset,
488 cursor_area.min_x() + 6.,
489 cursor_area.min_y() + avg_line_height + cursor_vertical_size_offset,
490 );
491
492 context.canvas.draw_rect(rect, &highlights_paint);
493 }
494
495 for rect in rects {
496 let rect = SkRect::new(
497 cursor_area.min_x() + rect.rect.left,
498 cursor_area.min_y() + rect.rect.top + cursor_vertical_offset,
499 cursor_area.min_x() + rect.rect.right.max(6.),
500 cursor_area.min_y() + rect.rect.bottom + cursor_vertical_size_offset,
501 );
502 context.canvas.draw_rect(rect, &highlights_paint);
503 }
504 }
505
506 let visible_highlights = self
508 .highlights
509 .iter()
510 .filter(|highlight| highlight.0 != highlight.1)
511 .count()
512 > 0;
513
514 if let Some(cursor_index) = self.cursor_index
516 && self.cursor_style == CursorStyle::Block
517 && let Some(cursor_rect) = paragraph
518 .get_rects_for_range(
519 cursor_index..cursor_index + 1,
520 RectHeightStyle::Tight,
521 RectWidthStyle::Tight,
522 )
523 .first()
524 .map(|text| text.rect)
525 .or_else(|| {
526 let text_len = paragraph
528 .get_glyph_position_at_coordinate((f32::MAX, f32::MAX))
529 .position as usize;
530 let last_rects = paragraph.get_rects_for_range(
531 text_len.saturating_sub(1)..text_len,
532 RectHeightStyle::Tight,
533 RectWidthStyle::Tight,
534 );
535
536 if let Some(last_rect) = last_rects.first() {
537 let mut caret = last_rect.rect;
538 caret.left = caret.right;
539 Some(caret)
540 } else {
541 let avg_line_height =
542 paragraph.height() / paragraph.get_line_metrics().len().max(1) as f32;
543 Some(SkRect::new(0., 0., 6., avg_line_height))
544 }
545 })
546 {
547 let width = (cursor_rect.right - cursor_rect.left).max(6.0);
548 let cursor_rect = SkRect::new(
549 cursor_area.min_x() + cursor_rect.left,
550 cursor_area.min_y() + cursor_rect.top + cursor_vertical_offset,
551 cursor_area.min_x() + cursor_rect.left + width,
552 cursor_area.min_y() + cursor_rect.bottom + cursor_vertical_size_offset,
553 );
554
555 let mut paint = Paint::default();
556 paint.set_anti_alias(true);
557 paint.set_style(PaintStyle::Fill);
558 paint.set_color(self.cursor_style_data.color);
559
560 context.canvas.draw_rect(cursor_rect, &paint);
561 }
562
563 paint_paragraph_with_fill(
565 paragraph,
566 context.canvas,
567 Point2D::new(visible_area.min_x(), visible_area.min_y() + vertical_offset),
568 &context.text_style_state.color,
569 );
570
571 if let Some(cursor_index) = self.cursor_index
573 && !visible_highlights
574 {
575 let cursor_rects = paragraph.get_rects_for_range(
576 cursor_index..cursor_index + 1,
577 RectHeightStyle::Tight,
578 RectWidthStyle::Tight,
579 );
580 if let Some(cursor_rect) = cursor_rects.first().map(|text| text.rect).or_else(|| {
581 let text_len = paragraph
583 .get_glyph_position_at_coordinate((f32::MAX, f32::MAX))
584 .position as usize;
585 let last_rects = paragraph.get_rects_for_range(
586 text_len.saturating_sub(1)..text_len,
587 RectHeightStyle::Tight,
588 RectWidthStyle::Tight,
589 );
590
591 if let Some(last_rect) = last_rects.first() {
592 let mut caret = last_rect.rect;
593 caret.left = caret.right;
594 Some(caret)
595 } else {
596 None
597 }
598 }) {
599 let paint_color = self.cursor_style_data.color;
600 match self.cursor_style {
601 CursorStyle::Underline => {
602 let thickness = 2.0;
603 let underline_rect = SkRect::new(
604 cursor_area.min_x() + cursor_rect.left,
605 cursor_area.min_y() + cursor_rect.bottom - thickness
606 + cursor_vertical_offset,
607 cursor_area.min_x() + cursor_rect.right,
608 cursor_area.min_y() + cursor_rect.bottom + cursor_vertical_size_offset,
609 );
610
611 let mut paint = Paint::default();
612 paint.set_anti_alias(true);
613 paint.set_style(PaintStyle::Fill);
614 paint.set_color(paint_color);
615
616 context.canvas.draw_rect(underline_rect, &paint);
617 }
618 CursorStyle::Line => {
619 let cursor_rect = SkRect::new(
620 cursor_area.min_x() + cursor_rect.left,
621 cursor_area.min_y() + cursor_rect.top + cursor_vertical_offset,
622 cursor_area.min_x() + cursor_rect.left + 2.,
623 cursor_area.min_y() + cursor_rect.bottom + cursor_vertical_size_offset,
624 );
625
626 let mut paint = Paint::default();
627 paint.set_anti_alias(true);
628 paint.set_style(PaintStyle::Fill);
629 paint.set_color(paint_color);
630
631 context.canvas.draw_rect(cursor_rect, &paint);
632 }
633 _ => {}
634 }
635 }
636 }
637 }
638}
639
640impl ParagraphElement {
641 fn has_inline_content(&self) -> bool {
642 self.contents
643 .iter()
644 .any(|content| matches!(content, ParagraphContent::Element))
645 }
646
647 fn build_paragraph(
650 &self,
651 text_style_state: &TextStyleState,
652 fallback_fonts: &[Cow<'static, str>],
653 scale_factor: f64,
654 font_collection: &FontCollection,
655 width: f32,
656 placeholders: &[Size2D],
657 ) -> SkParagraph {
658 let mut paragraph_style = ParagraphStyle::default();
659
660 if let Some(ellipsis) = text_style_state.text_overflow.get_ellipsis() {
661 paragraph_style.set_ellipsis(ellipsis);
662 }
663
664 paragraph_style.set_text_style(&base_text_style(
665 text_style_state,
666 fallback_fonts,
667 scale_factor,
668 self.line_height,
669 ));
670 paragraph_style.set_max_lines(self.max_lines);
671 paragraph_style.set_text_align(text_style_state.text_align.into());
672
673 let mut paragraph_builder = ParagraphBuilder::new(¶graph_style, font_collection);
674
675 let mut spans = self.spans.iter();
676 let mut placeholders = placeholders.iter();
677 for content in &self.contents {
678 match content {
679 ParagraphContent::Span => {
680 let Some(span) = spans.next() else { continue };
681 paragraph_builder.push_style(&span_text_style(
682 text_style_state,
683 fallback_fonts,
684 scale_factor,
685 span,
686 self.line_height,
687 ));
688 paragraph_builder.add_text(&span.text);
689 }
690 ParagraphContent::Element => {
691 let Some(size) = placeholders.next() else {
692 continue;
693 };
694 paragraph_builder.add_placeholder(&PlaceholderStyle::new(
695 size.width,
696 size.height,
697 PlaceholderAlignment::Middle,
698 TextBaseline::Alphabetic,
699 0.0,
700 ));
701 }
702 }
703 }
704
705 let mut paragraph = paragraph_builder.build();
706 paragraph.layout(width);
707 paragraph
708 }
709}
710
711impl From<Paragraph> for Element {
712 fn from(value: Paragraph) -> Self {
713 let elements = value
714 .children
715 .into_iter()
716 .map(|child| {
717 rect()
718 .position(Position::new_absolute())
719 .child(child)
720 .into_element()
721 })
722 .collect();
723
724 Element::Element {
725 key: value.key,
726 element: Rc::new(value.element),
727 elements,
728 }
729 }
730}
731
732fn base_text_style(
734 text_style_state: &TextStyleState,
735 fallback_fonts: &[Cow<'static, str>],
736 scale_factor: f64,
737 line_height: Option<f32>,
738) -> TextStyle {
739 let mut text_style = TextStyle::default();
740
741 let mut font_families = text_style_state.font_families.clone();
742 font_families.extend_from_slice(fallback_fonts);
743
744 text_style.set_color(text_style_state.color.as_color().unwrap_or(Color::WHITE));
745 text_style.set_font_size(f32::from(text_style_state.font_size) * scale_factor as f32);
746 text_style.set_font_families(&font_families);
747 text_style.set_font_style(FontStyle::new(
748 text_style_state.font_weight.into(),
749 text_style_state.font_width.into(),
750 text_style_state.font_slant.into(),
751 ));
752
753 if text_style_state.text_height.needs_custom_height() {
754 text_style.set_height_override(true);
755 text_style.set_half_leading(true);
756 }
757
758 if let Some(line_height) = line_height {
759 text_style.set_height_override(true);
760 text_style.set_height(line_height);
761 }
762
763 for text_shadow in text_style_state.text_shadows.iter() {
764 text_style.add_shadow((*text_shadow).into());
765 }
766
767 text_style
768}
769
770fn span_text_style(
772 text_style_state: &TextStyleState,
773 fallback_fonts: &[Cow<'static, str>],
774 scale_factor: f64,
775 span: &Span,
776 line_height: Option<f32>,
777) -> TextStyle {
778 let span_style = TextStyleState::from_data(text_style_state, &span.text_style_data);
779 let mut text_style = TextStyle::new();
780 let mut font_families = text_style_state.font_families.clone();
781 font_families.extend_from_slice(fallback_fonts);
782
783 for text_shadow in span_style.text_shadows.iter() {
784 text_style.add_shadow((*text_shadow).into());
785 }
786
787 text_style.set_color(span_style.color.as_color().unwrap_or(Color::WHITE));
788 text_style.set_font_size(f32::from(span_style.font_size) * scale_factor as f32);
789 text_style.set_font_families(&font_families);
790 text_style.set_font_style(FontStyle::new(
791 span_style.font_weight.into(),
792 span_style.font_width.into(),
793 span_style.font_slant.into(),
794 ));
795 text_style.set_decoration_type(span_style.text_decoration.into());
796 if let Some(line_height) = line_height {
797 text_style.set_height_override(true);
798 text_style.set_height(line_height);
799 }
800 text_style
801}
802
803pub(crate) fn paint_paragraph_with_fill(
806 paragraph: &SkParagraph,
807 canvas: &Canvas,
808 origin: Point2D,
809 fill: &Fill,
810) {
811 if matches!(fill, Fill::Color(_)) {
812 paragraph.paint(canvas, origin.to_tuple());
813 return;
814 }
815
816 let width = paragraph.longest_line();
817 let height = paragraph.height();
818 let area = Area::new(origin, Size2D::new(width, height));
819 let bounds_rect = SkRect::from_xywh(area.min_x(), area.min_y(), width, height);
820
821 let layer = canvas.save_layer(&SaveLayerRec::default().bounds(&bounds_rect));
822
823 paragraph.paint(canvas, origin.to_tuple());
824
825 let mut paint = Paint::default();
826 paint.set_anti_alias(true);
827 paint.set_style(PaintStyle::Fill);
828 paint.set_blend_mode(BlendMode::SrcIn);
829 fill.apply_to_paint(&mut paint, area);
830
831 canvas.draw_rect(bounds_rect, &paint);
832
833 canvas.restore_to_count(layer);
834}
835
836impl KeyExt for Paragraph {
837 fn write_key(&mut self) -> &mut DiffKey {
838 &mut self.key
839 }
840}
841
842impl EventHandlersExt for Paragraph {
843 fn get_event_handlers(&mut self) -> &mut FxHashMap<EventName, EventHandlerType> {
844 &mut self.element.event_handlers
845 }
846}
847
848impl MaybeExt for Paragraph {}
849
850impl LayerExt for Paragraph {
851 fn get_layer(&mut self) -> &mut Layer {
852 &mut self.element.relative_layer
853 }
854}
855
856pub struct Paragraph {
857 key: DiffKey,
858 element: ParagraphElement,
859 children: Vec<Element>,
860}
861
862impl LayoutExt for Paragraph {
863 fn get_layout(&mut self) -> &mut LayoutData {
864 &mut self.element.layout
865 }
866}
867
868impl ContainerExt for Paragraph {}
869
870impl ChildrenExt for Paragraph {
873 fn get_children(&mut self) -> &mut Vec<Element> {
874 &mut self.children
875 }
876
877 fn child<C: IntoElement>(mut self, child: C) -> Self {
878 self.element.contents.push(ParagraphContent::Element);
879 self.children.push(child.into_element());
880 self
881 }
882
883 fn children(self, children: impl IntoIterator<Item = Element>) -> Self {
884 children
885 .into_iter()
886 .fold(self, |paragraph, child| paragraph.child(child))
887 }
888
889 fn maybe_child<C: IntoElement>(self, child: Option<C>) -> Self {
890 match child {
891 Some(child) => self.child(child),
892 None => self,
893 }
894 }
895}
896
897impl AccessibilityExt for Paragraph {
898 fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
899 &mut self.element.accessibility
900 }
901}
902
903impl TextStyleExt for Paragraph {
904 fn get_text_style_data(&mut self) -> &mut TextStyleData {
905 &mut self.element.text_style_data
906 }
907}
908
909impl Paragraph {
910 pub fn try_downcast(element: &dyn ElementExt) -> Option<ParagraphElement> {
911 (element as &dyn Any)
912 .downcast_ref::<ParagraphElement>()
913 .cloned()
914 }
915
916 pub fn spans_iter(mut self, spans: impl Iterator<Item = Span<'static>>) -> Self {
918 for span in spans {
921 self.push_span(span);
922 }
923 self
924 }
925
926 pub fn span(mut self, span: impl Into<Span<'static>>) -> Self {
928 self.push_span(span.into());
931 self
932 }
933
934 fn push_span(&mut self, span: Span<'static>) {
935 self.element.contents.push(ParagraphContent::Span);
936 self.element.spans.push(span);
937 }
938
939 pub fn cursor_color(mut self, cursor_color: impl Into<Color>) -> Self {
941 self.element.cursor_style_data.color = cursor_color.into();
942 self
943 }
944
945 pub fn highlight_color(mut self, highlight_color: impl Into<Color>) -> Self {
947 self.element.cursor_style_data.highlight_color = highlight_color.into();
948 self
949 }
950
951 pub fn cursor_style(mut self, cursor_style: impl Into<CursorStyle>) -> Self {
953 self.element.cursor_style = cursor_style.into();
954 self
955 }
956
957 pub fn holder(mut self, holder: ParagraphHolder) -> Self {
959 self.element.sk_paragraph = holder;
960 self
961 }
962
963 pub fn cursor_index(mut self, cursor_index: impl Into<Option<usize>>) -> Self {
965 self.element.cursor_index = cursor_index.into();
966 self
967 }
968
969 pub fn highlights(mut self, highlights: impl Into<Option<Vec<(usize, usize)>>>) -> Self {
971 if let Some(highlights) = highlights.into() {
972 self.element.highlights = highlights;
973 }
974 self
975 }
976
977 pub fn max_lines(mut self, max_lines: impl Into<Option<usize>>) -> Self {
979 self.element.max_lines = max_lines.into();
980 self
981 }
982
983 pub fn line_height(mut self, line_height: impl Into<Option<f32>>) -> Self {
985 self.element.line_height = line_height.into();
986 self
987 }
988
989 pub fn cursor_mode(mut self, cursor_mode: impl Into<CursorMode>) -> Self {
993 self.element.cursor_mode = cursor_mode.into();
994 self
995 }
996
997 pub fn vertical_align(mut self, vertical_align: impl Into<VerticalAlign>) -> Self {
1001 self.element.vertical_align = vertical_align.into();
1002 self
1003 }
1004}
1005
1006#[derive(Clone, PartialEq, Hash)]
1017pub struct Span<'a> {
1018 pub text_style_data: TextStyleData,
1019 pub text: Cow<'a, str>,
1020}
1021
1022impl From<&'static str> for Span<'static> {
1023 fn from(text: &'static str) -> Self {
1024 Span {
1025 text_style_data: TextStyleData::default(),
1026 text: text.into(),
1027 }
1028 }
1029}
1030
1031impl From<String> for Span<'static> {
1032 fn from(text: String) -> Self {
1033 Span {
1034 text_style_data: TextStyleData::default(),
1035 text: text.into(),
1036 }
1037 }
1038}
1039
1040impl<'a> Span<'a> {
1041 pub fn new(text: impl Into<Cow<'a, str>>) -> Self {
1043 Self {
1044 text: text.into(),
1045 text_style_data: TextStyleData::default(),
1046 }
1047 }
1048}
1049
1050impl<'a> TextStyleExt for Span<'a> {
1051 fn get_text_style_data(&mut self) -> &mut TextStyleData {
1052 &mut self.text_style_data
1053 }
1054}