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 torin::prelude::{
34 Area,
35 Length,
36 Point2D,
37 Position,
38 PostMeasure,
39 Size2D,
40};
41use unicode_segmentation::UnicodeSegmentation;
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 EventHandlers,
58 IntoElement,
59 LayoutContext,
60 PostMeasureContext,
61 RenderContext,
62 },
63 elements::rect::rect,
64 layers::Layer,
65 node_id::NodeId,
66 prelude::{
67 AccessibilityExt,
68 ChildrenExt,
69 Color,
70 ContainerExt,
71 ContainerPositionExt,
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::default()
104}
105
106pub struct ParagraphHolderInner {
107 pub paragraph: Rc<SkParagraph>,
108 pub scale_factor: f64,
109}
110
111#[derive(Clone)]
114pub struct ParagraphHolder(pub Rc<RefCell<Option<ParagraphHolderInner>>>);
115
116impl PartialEq for ParagraphHolder {
117 fn eq(&self, other: &Self) -> bool {
118 Rc::ptr_eq(&self.0, &other.0)
119 }
120}
121
122impl Debug for ParagraphHolder {
123 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124 f.write_str("ParagraphHolder")
125 }
126}
127
128impl Default for ParagraphHolder {
129 fn default() -> Self {
130 Self(Rc::new(RefCell::new(None)))
131 }
132}
133
134#[derive(PartialEq, Clone)]
136pub enum ParagraphContent {
137 Span,
138 Element,
139}
140
141#[derive(PartialEq, Clone)]
142pub struct ParagraphElement {
143 pub layout: LayoutData,
144 pub spans: Vec<Span<'static>>,
145 pub contents: Vec<ParagraphContent>,
146 pub accessibility: AccessibilityData,
147 pub text_style_data: TextStyleData,
148 pub cursor_style_data: CursorStyleData,
149 pub event_handlers: EventHandlers,
150 pub sk_paragraph: ParagraphHolder,
151 pub cursor_index: Option<usize>,
152 pub highlights: Vec<(usize, usize)>,
153 pub max_lines: Option<usize>,
154 pub line_height: Option<f32>,
155 pub relative_layer: Layer,
156 pub cursor_style: CursorStyle,
157 pub cursor_mode: CursorMode,
158 pub vertical_align: VerticalAlign,
159}
160
161impl Default for ParagraphElement {
162 fn default() -> Self {
163 let mut accessibility = AccessibilityData::default();
164 accessibility.builder.set_role(accesskit::Role::Paragraph);
165 Self {
166 layout: Default::default(),
167 spans: Default::default(),
168 contents: Default::default(),
169 accessibility,
170 text_style_data: Default::default(),
171 cursor_style_data: Default::default(),
172 event_handlers: Default::default(),
173 sk_paragraph: Default::default(),
174 cursor_index: Default::default(),
175 highlights: Default::default(),
176 max_lines: Default::default(),
177 line_height: Default::default(),
178 relative_layer: Default::default(),
179 cursor_style: CursorStyle::default(),
180 cursor_mode: CursorMode::default(),
181 vertical_align: VerticalAlign::default(),
182 }
183 }
184}
185
186impl Display for ParagraphElement {
187 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188 f.write_str(
189 &self
190 .spans
191 .iter()
192 .map(|s| s.text.clone())
193 .collect::<Vec<_>>()
194 .join("\n"),
195 )
196 }
197}
198
199impl ElementExt for ParagraphElement {
200 fn changed(&self, other: &Rc<dyn ElementExt>) -> bool {
201 let Some(paragraph) = (other.as_ref() as &dyn Any).downcast_ref::<ParagraphElement>()
202 else {
203 return false;
204 };
205 self != paragraph
206 }
207
208 fn diff(&self, other: &Rc<dyn ElementExt>) -> DiffModifies {
209 let Some(paragraph) = (other.as_ref() as &dyn Any).downcast_ref::<ParagraphElement>()
210 else {
211 return DiffModifies::all();
212 };
213
214 let mut diff = DiffModifies::empty();
215
216 if self.spans != paragraph.spans || self.contents != paragraph.contents {
217 diff.insert(DiffModifies::STYLE);
218 diff.insert(DiffModifies::LAYOUT);
219 }
220
221 if self.accessibility != paragraph.accessibility {
222 diff.insert(DiffModifies::ACCESSIBILITY);
223 }
224
225 if self.relative_layer != paragraph.relative_layer {
226 diff.insert(DiffModifies::LAYER);
227 }
228
229 if self.text_style_data != paragraph.text_style_data {
230 diff.insert(DiffModifies::STYLE);
231 }
232
233 if self.event_handlers != paragraph.event_handlers {
234 diff.insert(DiffModifies::EVENT_HANDLERS);
235 }
236
237 if self.cursor_index != paragraph.cursor_index
238 || self.highlights != paragraph.highlights
239 || self.cursor_mode != paragraph.cursor_mode
240 || self.cursor_style != paragraph.cursor_style
241 || self.cursor_style_data != paragraph.cursor_style_data
242 || self.vertical_align != paragraph.vertical_align
243 {
244 diff.insert(DiffModifies::STYLE);
245 }
246
247 if self.text_style_data != paragraph.text_style_data
248 || self.line_height != paragraph.line_height
249 || self.max_lines != paragraph.max_lines
250 {
251 diff.insert(DiffModifies::TEXT_STYLE);
252 diff.insert(DiffModifies::LAYOUT);
253 }
254
255 if self.layout != paragraph.layout {
256 diff.insert(DiffModifies::STYLE);
257 diff.insert(DiffModifies::LAYOUT);
258 }
259
260 diff
261 }
262
263 fn layout(&'_ self) -> Cow<'_, LayoutData> {
264 Cow::Borrowed(&self.layout)
265 }
266 fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
267 None
268 }
269
270 fn style(&'_ self) -> Cow<'_, StyleState> {
271 Cow::Owned(StyleState::default())
272 }
273
274 fn is_transparent(&self) -> bool {
275 false
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()).max(Size2D::zero());
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<'_, EventHandlers>> {
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 let to_cursor_area = |rect: SkRect| {
461 SkRect::new(
462 cursor_area.min_x() + rect.left,
463 cursor_area.min_y() + rect.top + cursor_vertical_offset,
464 cursor_area.min_x() + rect.right,
465 cursor_area.min_y() + rect.bottom + cursor_vertical_size_offset,
466 )
467 };
468
469 for (from, to) in self.highlights.iter() {
471 if from == to {
472 continue;
473 }
474 let (from, to) = { if from < to { (from, to) } else { (to, from) } };
475 let rects = paragraph.get_rects_for_range(
476 *from..*to,
477 RectHeightStyle::Tight,
478 RectWidthStyle::Tight,
479 );
480
481 let mut highlights_paint = Paint::default();
482 highlights_paint.set_anti_alias(true);
483 highlights_paint.set_style(PaintStyle::Fill);
484 highlights_paint.set_color(self.cursor_style_data.highlight_color);
485
486 if rects.is_empty() && *from == 0 {
487 let caret_rect = cursor_character_rect(
488 paragraph,
489 &self.text(),
490 *from,
491 context.text_style_state.text_align,
492 );
493 context
494 .canvas
495 .draw_rect(to_cursor_area(caret_rect), &highlights_paint);
496 }
497
498 for rect in rects {
499 let mut rect = rect.rect;
500 rect.right = rect.right.max(6.);
501 context
502 .canvas
503 .draw_rect(to_cursor_area(rect), &highlights_paint);
504 }
505 }
506
507 let visible_highlights = self
508 .highlights
509 .iter()
510 .any(|highlight| highlight.0 != highlight.1);
511
512 let mut cursor_paint = Paint::default();
513 cursor_paint.set_anti_alias(true);
514 cursor_paint.set_style(PaintStyle::Fill);
515 cursor_paint.set_color(self.cursor_style_data.color);
516
517 if let Some(cursor_index) = self.cursor_index
519 && self.cursor_style == CursorStyle::Block
520 {
521 let mut cursor_rect = cursor_character_rect(
522 paragraph,
523 &self.text(),
524 cursor_index,
525 context.text_style_state.text_align,
526 );
527 let width = (cursor_rect.right - cursor_rect.left).max(6.0);
528 cursor_rect.right = cursor_rect.left + width;
529 context
530 .canvas
531 .draw_rect(to_cursor_area(cursor_rect), &cursor_paint);
532 }
533
534 paint_paragraph_with_fill(
536 paragraph,
537 context.canvas,
538 Point2D::new(visible_area.min_x(), visible_area.min_y() + vertical_offset),
539 &context.text_style_state.color,
540 );
541
542 if let Some(cursor_index) = self.cursor_index
544 && !visible_highlights
545 && self.cursor_style != CursorStyle::Block
546 {
547 let mut cursor_rect = cursor_character_rect(
548 paragraph,
549 &self.text(),
550 cursor_index,
551 context.text_style_state.text_align,
552 );
553 match self.cursor_style {
554 CursorStyle::Underline => cursor_rect.top = cursor_rect.bottom - 2.,
555 _ => cursor_rect.right = cursor_rect.left + 2.,
556 }
557 context
558 .canvas
559 .draw_rect(to_cursor_area(cursor_rect), &cursor_paint);
560 }
561 }
562}
563
564impl ParagraphElement {
565 fn has_inline_content(&self) -> bool {
566 self.contents
567 .iter()
568 .any(|content| matches!(content, ParagraphContent::Element))
569 }
570
571 fn text(&self) -> String {
573 let mut text = String::new();
574 let mut spans = self.spans.iter();
575 for content in &self.contents {
576 match content {
577 ParagraphContent::Span => {
578 if let Some(span) = spans.next() {
579 text.push_str(&span.text);
580 }
581 }
582 ParagraphContent::Element => text.push('\u{FFFC}'),
583 }
584 }
585 text
586 }
587
588 fn build_paragraph(
591 &self,
592 text_style_state: &TextStyleState,
593 fallback_fonts: &[Cow<'static, str>],
594 scale_factor: f64,
595 font_collection: &FontCollection,
596 width: f32,
597 placeholders: &[Size2D],
598 ) -> SkParagraph {
599 let mut paragraph_style = ParagraphStyle::default();
600
601 if let Some(ellipsis) = text_style_state.text_overflow.get_ellipsis() {
602 paragraph_style.set_ellipsis(ellipsis);
603 }
604
605 paragraph_style.set_text_style(&base_text_style(
606 text_style_state,
607 fallback_fonts,
608 scale_factor,
609 self.line_height,
610 ));
611 paragraph_style.set_max_lines(self.max_lines);
612 paragraph_style.set_text_align(text_style_state.text_align.into());
613
614 let mut paragraph_builder = ParagraphBuilder::new(¶graph_style, font_collection);
615
616 let mut spans = self.spans.iter();
617 let mut placeholders = placeholders.iter();
618 for content in &self.contents {
619 match content {
620 ParagraphContent::Span => {
621 let Some(span) = spans.next() else { continue };
622 paragraph_builder.push_style(&span_text_style(
623 text_style_state,
624 fallback_fonts,
625 scale_factor,
626 span,
627 self.line_height,
628 ));
629 paragraph_builder.add_text(&span.text);
630 }
631 ParagraphContent::Element => {
632 let Some(size) = placeholders.next() else {
633 continue;
634 };
635 paragraph_builder.add_placeholder(&PlaceholderStyle::new(
636 size.width,
637 size.height,
638 PlaceholderAlignment::Middle,
639 TextBaseline::Alphabetic,
640 0.0,
641 ));
642 }
643 }
644 }
645
646 let mut paragraph = paragraph_builder.build();
647 paragraph.layout(width);
648 paragraph
649 }
650}
651
652fn cursor_character_rect(
655 paragraph: &SkParagraph,
656 text: &str,
657 cursor_index: usize,
658 text_align: TextAlign,
659) -> SkRect {
660 let mut cluster = 0..0;
661 for grapheme in text.graphemes(true) {
662 cluster = cluster.end..cluster.end + grapheme.encode_utf16().count();
663 if cluster.end > cursor_index {
664 break;
665 }
666 }
667
668 if !cluster.is_empty() {
669 let rects = paragraph.get_rects_for_range(
670 cluster.clone(),
671 RectHeightStyle::Tight,
672 RectWidthStyle::Tight,
673 );
674 if let Some(rect) = rects.first() {
675 let mut rect = rect.rect;
676 if cluster.end <= cursor_index {
677 rect.left = rect.right;
678 }
679 return rect;
680 }
681 }
682
683 if let Some(line) = paragraph.get_line_metrics_at(0) {
684 let left = line.left as f32;
685 return SkRect::new(left, 0., left + 6., line.height as f32);
686 }
687
688 let left = match text_align {
689 TextAlign::Center => paragraph.max_width() / 2.,
690 TextAlign::Right | TextAlign::End => paragraph.max_width() - 6.,
691 _ => 0.,
692 };
693 SkRect::new(left, 0., left + 6., paragraph.height())
694}
695
696impl From<Paragraph> for Element {
697 fn from(value: Paragraph) -> Self {
698 let elements = value
699 .children
700 .into_iter()
701 .map(|child| {
702 rect()
703 .position(Position::new_absolute())
704 .child(child)
705 .into_element()
706 })
707 .collect();
708
709 Element::Element {
710 key: value.key,
711 element: Rc::new(value.element),
712 elements,
713 }
714 }
715}
716
717fn base_text_style(
719 text_style_state: &TextStyleState,
720 fallback_fonts: &[Cow<'static, str>],
721 scale_factor: f64,
722 line_height: Option<f32>,
723) -> TextStyle {
724 let mut text_style = TextStyle::default();
725
726 let mut font_families = text_style_state.font_families.clone();
727 font_families.extend_from_slice(fallback_fonts);
728
729 text_style.set_color(text_style_state.color.as_color().unwrap_or(Color::WHITE));
730 text_style.set_font_size(f32::from(text_style_state.font_size) * scale_factor as f32);
731 text_style.set_font_families(&font_families);
732 text_style.set_font_style(FontStyle::new(
733 text_style_state.font_weight.into(),
734 text_style_state.font_width.into(),
735 text_style_state.font_slant.into(),
736 ));
737
738 if text_style_state.text_height.needs_custom_height() {
739 text_style.set_height_override(true);
740 text_style.set_half_leading(true);
741 }
742
743 if let Some(line_height) = line_height {
744 text_style.set_height_override(true);
745 text_style.set_height(line_height);
746 }
747
748 for text_shadow in text_style_state.text_shadows.iter() {
749 text_style.add_shadow((*text_shadow).into());
750 }
751
752 text_style
753}
754
755fn span_text_style(
757 text_style_state: &TextStyleState,
758 fallback_fonts: &[Cow<'static, str>],
759 scale_factor: f64,
760 span: &Span,
761 line_height: Option<f32>,
762) -> TextStyle {
763 let span_style = TextStyleState::from_data(text_style_state, &span.text_style_data);
764 let mut text_style = TextStyle::new();
765 let mut font_families = text_style_state.font_families.clone();
766 font_families.extend_from_slice(fallback_fonts);
767
768 for text_shadow in span_style.text_shadows.iter() {
769 text_style.add_shadow((*text_shadow).into());
770 }
771
772 text_style.set_color(span_style.color.as_color().unwrap_or(Color::WHITE));
773 text_style.set_font_size(f32::from(span_style.font_size) * scale_factor as f32);
774 text_style.set_font_families(&font_families);
775 text_style.set_font_style(FontStyle::new(
776 span_style.font_weight.into(),
777 span_style.font_width.into(),
778 span_style.font_slant.into(),
779 ));
780 text_style.set_decoration_type(span_style.text_decoration.into());
781 if let Some(line_height) = line_height {
782 text_style.set_height_override(true);
783 text_style.set_height(line_height);
784 }
785 text_style
786}
787
788pub(crate) fn paint_paragraph_with_fill(
791 paragraph: &SkParagraph,
792 canvas: &Canvas,
793 origin: Point2D,
794 fill: &Fill,
795) {
796 if matches!(fill, Fill::Color(_)) {
797 paragraph.paint(canvas, origin.to_tuple());
798 return;
799 }
800
801 let width = paragraph.longest_line();
802 let height = paragraph.height();
803 let area = Area::new(origin, Size2D::new(width, height));
804 let bounds_rect = SkRect::from_xywh(area.min_x(), area.min_y(), width, height);
805
806 let layer = canvas.save_layer(&SaveLayerRec::default().bounds(&bounds_rect));
807
808 paragraph.paint(canvas, origin.to_tuple());
809
810 let mut paint = Paint::default();
811 paint.set_anti_alias(true);
812 paint.set_style(PaintStyle::Fill);
813 paint.set_blend_mode(BlendMode::SrcIn);
814 fill.apply_to_paint(&mut paint, area);
815
816 canvas.draw_rect(bounds_rect, &paint);
817
818 canvas.restore_to_count(layer);
819}
820
821impl KeyExt for Paragraph {
822 fn write_key(&mut self) -> &mut DiffKey {
823 &mut self.key
824 }
825}
826
827impl EventHandlersExt for Paragraph {
828 fn get_event_handlers(&mut self) -> &mut EventHandlers {
829 &mut self.element.event_handlers
830 }
831}
832
833impl MaybeExt for Paragraph {}
834
835impl LayerExt for Paragraph {
836 fn get_layer(&mut self) -> &mut Layer {
837 &mut self.element.relative_layer
838 }
839}
840
841#[derive(Default)]
842pub struct Paragraph {
843 key: DiffKey,
844 element: ParagraphElement,
845 children: Vec<Element>,
846}
847
848impl LayoutExt for Paragraph {
849 fn get_layout(&mut self) -> &mut LayoutData {
850 &mut self.element.layout
851 }
852}
853
854impl ContainerExt for Paragraph {}
855
856impl ChildrenExt for Paragraph {
859 fn get_children(&mut self) -> &mut Vec<Element> {
860 &mut self.children
861 }
862
863 fn child<C: IntoElement>(mut self, child: C) -> Self {
864 self.element.contents.push(ParagraphContent::Element);
865 self.children.push(child.into_element());
866 self
867 }
868
869 fn children(self, children: impl IntoIterator<Item = impl IntoElement>) -> Self {
870 children
871 .into_iter()
872 .fold(self, |paragraph, child| paragraph.child(child))
873 }
874
875 fn maybe_child<C: IntoElement>(self, child: Option<C>) -> Self {
876 match child {
877 Some(child) => self.child(child),
878 None => self,
879 }
880 }
881}
882
883impl AccessibilityExt for Paragraph {
884 fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
885 &mut self.element.accessibility
886 }
887}
888
889impl TextStyleExt for Paragraph {
890 fn get_text_style_data(&mut self) -> &mut TextStyleData {
891 &mut self.element.text_style_data
892 }
893}
894
895impl Paragraph {
896 pub fn try_downcast(element: &dyn ElementExt) -> Option<ParagraphElement> {
897 (element as &dyn Any)
898 .downcast_ref::<ParagraphElement>()
899 .cloned()
900 }
901
902 pub fn spans_iter(mut self, spans: impl Iterator<Item = Span<'static>>) -> Self {
904 for span in spans {
907 self.push_span(span);
908 }
909 self
910 }
911
912 pub fn span(mut self, span: impl Into<Span<'static>>) -> Self {
914 self.push_span(span.into());
917 self
918 }
919
920 fn push_span(&mut self, span: Span<'static>) {
921 self.element.contents.push(ParagraphContent::Span);
922 self.element.spans.push(span);
923 }
924
925 pub fn cursor_style_data(mut self, cursor_style_data: CursorStyleData) -> Self {
927 self.element.cursor_style_data = cursor_style_data;
928 self
929 }
930
931 pub fn cursor_color(mut self, cursor_color: impl Into<Color>) -> Self {
933 self.element.cursor_style_data.color = cursor_color.into();
934 self
935 }
936
937 pub fn highlight_color(mut self, highlight_color: impl Into<Color>) -> Self {
939 self.element.cursor_style_data.highlight_color = highlight_color.into();
940 self
941 }
942
943 pub fn cursor_style(mut self, cursor_style: impl Into<CursorStyle>) -> Self {
945 self.element.cursor_style = cursor_style.into();
946 self
947 }
948
949 pub fn holder(mut self, holder: ParagraphHolder) -> Self {
951 self.element.sk_paragraph = holder;
952 self
953 }
954
955 pub fn cursor_index(mut self, cursor_index: impl Into<Option<usize>>) -> Self {
957 self.element.cursor_index = cursor_index.into();
958 self
959 }
960
961 pub fn highlights(mut self, highlights: impl Into<Option<Vec<(usize, usize)>>>) -> Self {
963 if let Some(highlights) = highlights.into() {
964 self.element.highlights = highlights;
965 }
966 self
967 }
968
969 pub fn max_lines(mut self, max_lines: impl Into<Option<usize>>) -> Self {
971 self.element.max_lines = max_lines.into();
972 self
973 }
974
975 pub fn line_height(mut self, line_height: impl Into<Option<f32>>) -> Self {
977 self.element.line_height = line_height.into();
978 self
979 }
980
981 pub fn cursor_mode(mut self, cursor_mode: impl Into<CursorMode>) -> Self {
985 self.element.cursor_mode = cursor_mode.into();
986 self
987 }
988
989 pub fn vertical_align(mut self, vertical_align: impl Into<VerticalAlign>) -> Self {
993 self.element.vertical_align = vertical_align.into();
994 self
995 }
996}
997
998#[derive(Clone, PartialEq, Hash)]
1009pub struct Span<'a> {
1010 pub text_style_data: TextStyleData,
1011 pub text: Cow<'a, str>,
1012}
1013
1014impl From<&'static str> for Span<'static> {
1015 fn from(text: &'static str) -> Self {
1016 Span {
1017 text_style_data: TextStyleData::default(),
1018 text: text.into(),
1019 }
1020 }
1021}
1022
1023impl From<String> for Span<'static> {
1024 fn from(text: String) -> Self {
1025 Span {
1026 text_style_data: TextStyleData::default(),
1027 text: text.into(),
1028 }
1029 }
1030}
1031
1032impl<'a> Span<'a> {
1033 pub fn new(text: impl Into<Cow<'a, str>>) -> Self {
1035 Self {
1036 text: text.into(),
1037 text_style_data: TextStyleData::default(),
1038 }
1039 }
1040}
1041
1042impl<'a> TextStyleExt for Span<'a> {
1043 fn get_text_style_data(&mut self) -> &mut TextStyleData {
1044 &mut self.text_style_data
1045 }
1046}