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