1use std::any::{Any, TypeId};
7use std::fmt;
8use std::rc::Rc;
9
10use inset_embedder::{
11 BoxHeightStyle, BoxWidthStyle, ClipOp, FontCollection, Matrix4, Offset, PlaceholderAlignment,
12 Rect, Size, TextAlign, TextBaseline, TextBox, TextDirection, TextHeightBehavior, TextPosition,
13 TextRange, TextSelection,
14};
15use inset_foundation::{App, Handle};
16use inset_painting::{
17 ClipContext, InlineSpanRef, PaintingBinding, PlaceholderDimensions, PlaceholderSpan,
18 RenderComparison, TextOverflow, TextPainter, TextScaler, TextWidthBasis,
19};
20
21use crate::box_::{AnyRenderBox, BoxConstraints, BoxHitTestResult, RenderBox, RenderBoxData};
22use crate::layout_helper::{ChildBaselineGetter, ChildLayoutHelper, ChildLayouter};
23use crate::object::{
24 AnyRenderObject, Constraints, ContainerParentData, ContainerParentDataMixin,
25 ContainerRenderObjectData, ContainerRenderObjectMixin, ParentData, RenderHandle, RenderObject,
26 RenderObjectData,
27};
28use crate::painting_context::PaintingContext;
29use crate::pipeline_owner::PipelineOwner;
30use crate::{RelayoutWhenSystemFontsChangeData, RelayoutWhenSystemFontsChangeMixin};
31
32const K_ELLIPSIS: &str = "\u{2026}";
33
34#[derive(Debug, Default)]
37pub struct TextParentData {
38 container_parent_data: ContainerParentData<AnyRenderBox>,
39 offset: Option<Offset>,
40
41 pub span: Option<Rc<dyn PlaceholderSpan>>,
46}
47
48impl TextParentData {
49 pub fn offset(&self) -> Option<Offset> {
54 self.offset
55 }
56}
57
58impl ParentData for TextParentData {
59 fn detach(&mut self) {
60 self.span = None;
61 self.offset = None;
62 ContainerParentDataMixin::detach(self);
63 }
64
65 fn provide(&self, id: TypeId) -> Option<&dyn Any> {
66 (id == TypeId::of::<TextParentData>()).then_some(self)
67 }
68
69 fn provide_mut(&mut self, id: TypeId) -> Option<&mut dyn Any> {
70 (id == TypeId::of::<TextParentData>()).then_some(self)
71 }
72}
73
74impl ContainerParentDataMixin for TextParentData {
75 type ChildType = AnyRenderBox;
76
77 fn container_parent_data(&self) -> &ContainerParentData<AnyRenderBox> {
78 &self.container_parent_data
79 }
80
81 fn container_parent_data_mut(&mut self) -> &mut ContainerParentData<AnyRenderBox> {
82 &mut self.container_parent_data
83 }
84}
85
86impl fmt::Display for TextParentData {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 write!(f, "widget: {:?}, ", self.span)?;
89 match self.offset {
90 None => f.write_str("not laid out"),
91 Some(offset) => write!(f, "offset: {offset:?}"),
92 }
93 }
94}
95
96pub trait RenderInlineChildrenContainerDefaults:
129 RenderBox + ContainerRenderObjectMixin<ChildType = AnyRenderBox, ParentDataType = TextParentData>
130{
131 fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
133 if !child.parent_data_is::<TextParentData>(app) {
134 child.set_parent_data(app, TextParentData::default());
135 }
136 }
137
138 fn layout_inline_children(
156 self: RenderHandle<Self>,
157 app: &mut App,
158 max_width: f64,
159 layout_child: ChildLayouter,
160 get_child_baseline: ChildBaselineGetter,
161 ) -> Vec<PlaceholderDimensions> {
162 let constraints = BoxConstraints::new().max_width(max_width);
163 let mut dimensions = Vec::new();
164 let mut child = self.first_child(app);
165 while let Some(current) = child {
166 dimensions.push(layout_child_inline(
167 app,
168 current,
169 constraints,
170 layout_child,
171 get_child_baseline,
172 ));
173 child = self.child_after(app, current);
174 }
175 dimensions
176 }
177
178 fn position_inline_children(self: RenderHandle<Self>, app: &mut App, boxes: &[TextBox]) {
192 let mut child = self.first_child(app);
193 for text_box in boxes {
194 let Some(current) = child else {
195 debug_assert!(
196 false,
197 "Invalid number of boxes provided to positionInlineChildren. The number of \
198 boxes ({}) exceeds the number of child render objects ({}).",
199 boxes.len(),
200 self.child_count(app)
201 );
202 return;
203 };
204 text_parent_data_mut(app, current).offset =
205 Some(Offset::new(text_box.left, text_box.top));
206 child = self.child_after(app, current);
207 }
208 while let Some(current) = child {
209 text_parent_data_mut(app, current).offset = None;
210 child = self.child_after(app, current);
211 }
212 }
213
214 fn default_apply_paint_transform(
220 self: RenderHandle<Self>,
221 app: &App,
222 child: AnyRenderObject,
223 transform: &mut Matrix4,
224 ) {
225 match child.parent_data_of::<TextParentData>(app).offset {
226 None => *transform = crate::object::zero_matrix(),
227 Some(offset) => crate::object::translate(transform, offset.dx(), offset.dy()),
228 }
229 }
230
231 fn paint_inline_children(
236 self: RenderHandle<Self>,
237 app: &mut App,
238 context: &mut PaintingContext,
239 offset: Offset,
240 ) {
241 let mut child = self.first_child(app);
242 while let Some(current) = child {
243 let Some(child_offset) = text_parent_data(app, current).offset else {
244 return;
245 };
246 context.paint_child(app, current.as_object(), child_offset + offset);
247 child = self.child_after(app, current);
248 }
249 }
250
251 fn hit_test_inline_children(
256 self: RenderHandle<Self>,
257 app: &mut App,
258 result: &mut BoxHitTestResult<'_>,
259 position: Offset,
260 ) -> bool {
261 let mut child = self.first_child(app);
262 while let Some(current) = child {
263 let Some(child_offset) = text_parent_data(app, current).offset else {
264 return false;
265 };
266 let is_hit = result.add_with_paint_offset(
267 Some(child_offset),
268 position,
269 |result, transformed| current.hit_test(app, result, transformed),
270 );
271 if is_hit {
272 return true;
273 }
274 child = self.child_after(app, current);
275 }
276 false
277 }
278}
279
280fn layout_child_inline(
283 app: &mut App,
284 child: AnyRenderBox,
285 child_constraints: BoxConstraints,
286 layout_child: ChildLayouter,
287 get_baseline: ChildBaselineGetter,
288) -> PlaceholderDimensions {
289 let span = text_parent_data(app, child).span.clone();
290 debug_assert!(span.is_some());
291 let Some(span) = span else {
292 return PlaceholderDimensions::EMPTY;
293 };
294 let size = layout_child(app, child, child_constraints);
295 let baseline_offset = match span.alignment() {
296 PlaceholderAlignment::AboveBaseline
297 | PlaceholderAlignment::BelowBaseline
298 | PlaceholderAlignment::Bottom
299 | PlaceholderAlignment::Middle
300 | PlaceholderAlignment::Top => None,
301 PlaceholderAlignment::Baseline => get_baseline(
302 app,
303 child,
304 child_constraints,
305 span.baseline()
306 .expect("a baseline-aligned placeholder names its baseline"),
307 ),
308 };
309 PlaceholderDimensions {
310 size,
311 alignment: span.alignment(),
312 baseline: span.baseline(),
313 baseline_offset,
314 }
315}
316
317fn text_parent_data(app: &App, child: AnyRenderBox) -> &TextParentData {
318 child.as_object().parent_data_of::<TextParentData>(app)
319}
320
321fn text_parent_data_mut(app: &mut App, child: AnyRenderBox) -> &mut TextParentData {
322 child.as_object().parent_data_of_mut::<TextParentData>(app)
323}
324
325pub struct RenderParagraph {
332 render_object: RenderObjectData,
333 render_box: RenderBoxData,
334 container: ContainerRenderObjectData<AnyRenderBox>,
335 text_painter: TextPainter,
336 text_intrinsics: Option<TextPainter>,
337 fonts_override: Option<Handle<FontCollection>>,
338 soft_wrap: bool,
339 overflow: TextOverflow,
340 device_pixel_ratio: f64,
341 needs_clipping: bool,
342 placeholder_dimensions: Option<Vec<PlaceholderDimensions>>,
348 system_fonts: RelayoutWhenSystemFontsChangeData,
349}
350
351impl RenderParagraph {
352 pub fn new(
354 app: &mut App,
355 text: InlineSpanRef,
356 text_direction: TextDirection,
357 fonts: Option<Handle<FontCollection>>,
358 ) -> RenderHandle<Self> {
359 debug_assert!(text.debug_assert_is_valid());
360 let mut text_painter = TextPainter::new();
361 text_painter.set_text(Some(text));
362 text_painter.set_text_direction(Some(text_direction));
363 RenderHandle::new_box(
364 app,
365 RenderParagraph {
366 render_object: RenderObjectData::new(),
367 render_box: RenderBoxData::new(),
368 container: ContainerRenderObjectData::new(),
369 text_painter,
370 text_intrinsics: None,
371 fonts_override: fonts,
372 soft_wrap: true,
373 overflow: TextOverflow::Clip,
374 device_pixel_ratio: 1.0,
375 needs_clipping: false,
376 placeholder_dimensions: None,
377 system_fonts: RelayoutWhenSystemFontsChangeData::new(),
378 },
379 )
380 }
381
382 fn painter(self: RenderHandle<Self>, app: &App) -> &TextPainter {
383 &self.get(app).text_painter
384 }
385
386 fn painter_mut(self: RenderHandle<Self>, app: &mut App) -> &mut TextPainter {
387 &mut self.get_mut(app).text_painter
388 }
389
390 pub fn fonts(self: RenderHandle<Self>, app: &mut App) -> Handle<FontCollection> {
392 match self.get(app).fonts_override {
393 Some(fonts) => fonts,
394 None => PaintingBinding::instance(app).fonts(app),
395 }
396 }
397
398 fn painter_with_fonts(
400 self: RenderHandle<Self>,
401 app: &mut App,
402 ) -> (&mut TextPainter, &mut FontCollection) {
403 let fonts = self.fonts(app);
404 let (this, fonts) = app.get_disjoint_mut(self.handle(), fonts);
405 (&mut this.text_painter, fonts)
406 }
407
408 pub fn text(self: RenderHandle<Self>, app: &App) -> InlineSpanRef {
410 self.painter(app)
411 .text()
412 .cloned()
413 .expect("a paragraph always has text")
414 }
415
416 pub fn set_text(self: RenderHandle<Self>, app: &mut App, value: InlineSpanRef) {
418 match self.text(app).compare_to(&*value) {
419 RenderComparison::Identical => {}
420 RenderComparison::Metadata => {
421 self.painter_mut(app).set_text(Some(value));
422 }
423 RenderComparison::Paint => {
424 self.painter_mut(app).set_text(Some(value));
425 self.mark_needs_paint(app);
426 }
427 RenderComparison::Layout => {
428 self.painter_mut(app).set_text(Some(value));
429 self.mark_needs_layout(app);
430 }
431 }
432 }
433
434 pub fn text_align(self: RenderHandle<Self>, app: &App) -> TextAlign {
436 self.painter(app).text_align()
437 }
438
439 pub fn set_text_align(self: RenderHandle<Self>, app: &mut App, value: TextAlign) {
441 if self.painter(app).text_align() == value {
442 return;
443 }
444 self.painter_mut(app).set_text_align(value);
445 self.mark_needs_paint(app);
446 }
447
448 pub fn text_direction(self: RenderHandle<Self>, app: &App) -> TextDirection {
459 self.painter(app)
460 .text_direction()
461 .expect("a paragraph always has a direction")
462 }
463
464 pub fn set_text_direction(self: RenderHandle<Self>, app: &mut App, value: TextDirection) {
466 if self.painter(app).text_direction() == Some(value) {
467 return;
468 }
469 self.painter_mut(app).set_text_direction(Some(value));
470 self.mark_needs_layout(app);
471 }
472
473 pub fn soft_wrap(self: RenderHandle<Self>, app: &App) -> bool {
481 self.get(app).soft_wrap
482 }
483
484 pub fn set_soft_wrap(self: RenderHandle<Self>, app: &mut App, value: bool) {
486 if self.get(app).soft_wrap == value {
487 return;
488 }
489 self.get_mut(app).soft_wrap = value;
490 self.mark_needs_layout(app);
491 }
492
493 pub fn overflow(self: RenderHandle<Self>, app: &App) -> TextOverflow {
495 self.get(app).overflow
496 }
497
498 pub fn set_overflow(self: RenderHandle<Self>, app: &mut App, value: TextOverflow) {
500 if self.get(app).overflow == value {
501 return;
502 }
503 self.get_mut(app).overflow = value;
504 let ellipsis = (value == TextOverflow::Ellipsis).then(|| K_ELLIPSIS.to_owned());
505 self.painter_mut(app).set_ellipsis(ellipsis);
506 self.mark_needs_layout(app);
507 }
508
509 pub fn text_scaler(self: RenderHandle<Self>, app: &App) -> TextScaler {
511 self.painter(app).text_scaler().clone()
512 }
513
514 pub fn set_text_scaler(self: RenderHandle<Self>, app: &mut App, value: TextScaler) {
516 if *self.painter(app).text_scaler() == value {
517 return;
518 }
519 self.painter_mut(app).set_text_scaler(value);
520 self.mark_needs_layout(app);
521 }
522
523 pub fn device_pixel_ratio(self: RenderHandle<Self>, app: &App) -> f64 {
527 self.get(app).device_pixel_ratio
528 }
529
530 pub fn set_device_pixel_ratio(self: RenderHandle<Self>, app: &mut App, value: f64) {
532 if self.get(app).device_pixel_ratio == value {
533 return;
534 }
535 self.get_mut(app).device_pixel_ratio = value;
536 }
537
538 pub fn max_lines(self: RenderHandle<Self>, app: &App) -> Option<i32> {
543 self.painter(app).max_lines()
544 }
545
546 pub fn set_max_lines(self: RenderHandle<Self>, app: &mut App, value: Option<i32>) {
549 debug_assert!(value.is_none_or(|value| value > 0));
550 if self.painter(app).max_lines() == value {
551 return;
552 }
553 self.painter_mut(app).set_max_lines(value);
554 self.mark_needs_layout(app);
555 }
556
557 pub fn text_width_basis(self: RenderHandle<Self>, app: &App) -> TextWidthBasis {
559 self.painter(app).text_width_basis()
560 }
561
562 pub fn set_text_width_basis(self: RenderHandle<Self>, app: &mut App, value: TextWidthBasis) {
564 if self.painter(app).text_width_basis() == value {
565 return;
566 }
567 self.painter_mut(app).set_text_width_basis(value);
568 self.mark_needs_layout(app);
569 }
570
571 pub fn text_height_behavior(self: RenderHandle<Self>, app: &App) -> Option<TextHeightBehavior> {
573 self.painter(app).text_height_behavior()
574 }
575
576 pub fn set_text_height_behavior(
578 self: RenderHandle<Self>,
579 app: &mut App,
580 value: Option<TextHeightBehavior>,
581 ) {
582 if self.painter(app).text_height_behavior() == value {
583 return;
584 }
585 self.painter_mut(app).set_text_height_behavior(value);
586 self.mark_needs_layout(app);
587 }
588
589 pub fn preferred_line_height(self: RenderHandle<Self>, app: &mut App) -> f64 {
591 let (painter, fonts) = self.painter_with_fonts(app);
592 painter.preferred_line_height(fonts)
593 }
594
595 fn text_intrinsics_with_fonts(
601 self: RenderHandle<Self>,
602 app: &mut App,
603 ) -> (&mut TextPainter, &mut FontCollection) {
604 let fonts = self.fonts(app);
605 let (this, fonts) = app.get_disjoint_mut(self.handle(), fonts);
606 let painter = &this.text_painter;
607 let intrinsics = this.text_intrinsics.get_or_insert_with(TextPainter::new);
608 intrinsics.set_text(painter.text().cloned());
609 intrinsics.set_text_align(painter.text_align());
610 intrinsics.set_text_direction(painter.text_direction());
611 intrinsics.set_text_scaler(painter.text_scaler().clone());
612 intrinsics.set_max_lines(painter.max_lines());
613 intrinsics.set_ellipsis(painter.ellipsis().map(str::to_owned));
614 intrinsics.set_text_width_basis(painter.text_width_basis());
615 intrinsics.set_text_height_behavior(painter.text_height_behavior());
616 (intrinsics, fonts)
617 }
618
619 fn compute_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
621 let placeholder_dimensions = self.layout_inline_children(
622 app,
623 width,
624 ChildLayoutHelper::dry_layout_child,
625 ChildLayoutHelper::get_dry_baseline,
626 );
627 let max_width = self.adjust_max_width(app, width);
628 let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
629 intrinsics.set_placeholder_dimensions(Some(placeholder_dimensions));
630 intrinsics.layout(fonts, width, max_width);
631 intrinsics.height()
632 }
633
634 fn adjust_max_width(self: RenderHandle<Self>, app: &App, max_width: f64) -> f64 {
635 if self.get(app).soft_wrap || self.get(app).overflow == TextOverflow::Ellipsis {
636 max_width
637 } else {
638 f64::INFINITY
639 }
640 }
641
642 fn layout_text_with_constraints(
643 self: RenderHandle<Self>,
644 app: &mut App,
645 constraints: BoxConstraints,
646 ) {
647 let max_width = self.adjust_max_width(app, constraints.max_width);
648 let placeholder_dimensions = self.get(app).placeholder_dimensions.clone();
649 let (painter, fonts) = self.painter_with_fonts(app);
650 painter.set_placeholder_dimensions(placeholder_dimensions);
651 painter.layout(fonts, constraints.min_width, max_width);
652 }
653
654 pub fn get_offset_for_caret(
658 self: RenderHandle<Self>,
659 app: &mut App,
660 position: TextPosition,
661 caret_prototype: Rect,
662 ) -> Offset {
663 debug_assert!(!self.debug_needs_layout(app));
664 let constraints = self.constraints(app);
665 self.layout_text_with_constraints(app, constraints);
666 let (painter, fonts) = self.painter_with_fonts(app);
667 painter.get_offset_for_caret(fonts, position, caret_prototype)
668 }
669
670 pub fn get_full_height_for_caret(
674 self: RenderHandle<Self>,
675 app: &mut App,
676 position: TextPosition,
677 ) -> f64 {
678 debug_assert!(!self.debug_needs_layout(app));
679 let constraints = self.constraints(app);
680 self.layout_text_with_constraints(app, constraints);
681 let (painter, fonts) = self.painter_with_fonts(app);
682 painter.get_full_height_for_caret(fonts, position, Rect::ZERO)
683 }
684
685 pub fn get_boxes_for_selection(
696 self: RenderHandle<Self>,
697 app: &mut App,
698 selection: TextSelection,
699 box_height_style: BoxHeightStyle,
700 box_width_style: BoxWidthStyle,
701 ) -> Vec<TextBox> {
702 debug_assert!(!self.debug_needs_layout(app));
703 let constraints = self.constraints(app);
704 self.layout_text_with_constraints(app, constraints);
705 self.painter(app)
706 .get_boxes_for_selection(selection, box_height_style, box_width_style)
707 }
708
709 pub fn get_position_for_offset(
713 self: RenderHandle<Self>,
714 app: &mut App,
715 offset: Offset,
716 ) -> TextPosition {
717 debug_assert!(!self.debug_needs_layout(app));
718 let constraints = self.constraints(app);
719 self.layout_text_with_constraints(app, constraints);
720 self.painter(app).get_position_for_offset(offset)
721 }
722
723 pub fn get_word_boundary(
732 self: RenderHandle<Self>,
733 app: &mut App,
734 position: TextPosition,
735 ) -> TextRange {
736 debug_assert!(!self.debug_needs_layout(app));
737 let constraints = self.constraints(app);
738 self.layout_text_with_constraints(app, constraints);
739 self.painter(app).get_word_boundary(position)
740 }
741
742 pub fn text_size(self: RenderHandle<Self>, app: &App) -> Size {
753 debug_assert!(!self.debug_needs_layout(app));
754 self.painter(app).size()
755 }
756
757 pub fn did_exceed_max_lines(self: RenderHandle<Self>, app: &App) -> bool {
764 debug_assert!(!self.debug_needs_layout(app));
765 self.painter(app).did_exceed_max_lines()
766 }
767}
768
769impl RelayoutWhenSystemFontsChangeMixin for RenderParagraph {
770 fn relayout_when_system_fonts_change_data(
771 self: RenderHandle<Self>,
772 app: &App,
773 ) -> &RelayoutWhenSystemFontsChangeData {
774 &self.get(app).system_fonts
775 }
776
777 fn relayout_when_system_fonts_change_data_mut(
778 self: RenderHandle<Self>,
779 app: &mut App,
780 ) -> &mut RelayoutWhenSystemFontsChangeData {
781 &mut self.get_mut(app).system_fonts
782 }
783
784 fn system_fonts_did_change(self: RenderHandle<Self>, app: &mut App) {
785 self.as_render_object(app).mark_needs_layout(app);
786 self.painter_mut(app).mark_needs_layout();
787 }
788}
789
790impl ContainerRenderObjectMixin for RenderParagraph {
791 type ChildType = AnyRenderBox;
792 type ParentDataType = TextParentData;
793
794 fn container_data(
795 self: RenderHandle<Self>,
796 app: &App,
797 ) -> &ContainerRenderObjectData<AnyRenderBox> {
798 &self.get(app).container
799 }
800
801 fn container_data_mut(
802 self: RenderHandle<Self>,
803 app: &mut App,
804 ) -> &mut ContainerRenderObjectData<AnyRenderBox> {
805 &mut self.get_mut(app).container
806 }
807}
808
809impl RenderInlineChildrenContainerDefaults for RenderParagraph {}
810
811impl RenderObject for RenderParagraph {
812 crate::render_object_accessors!();
813
814 fn visit_children(
815 self: RenderHandle<Self>,
816 app: &App,
817 visitor: &mut dyn FnMut(AnyRenderObject),
818 ) {
819 ContainerRenderObjectMixin::visit_children(self, app, visitor)
820 }
821
822 fn did_attach(self: RenderHandle<Self>, app: &mut App, owner: Handle<PipelineOwner>) {
823 ContainerRenderObjectMixin::did_attach(self, app, owner);
824 RelayoutWhenSystemFontsChangeMixin::attach_system_fonts(self, app);
825 }
826
827 fn did_detach(self: RenderHandle<Self>, app: &mut App) {
828 RelayoutWhenSystemFontsChangeMixin::detach_system_fonts(self, app);
829 ContainerRenderObjectMixin::did_detach(self, app);
830 }
831
832 fn redepth_children(self: RenderHandle<Self>, app: &mut App) {
833 ContainerRenderObjectMixin::redepth_children(self, app)
834 }
835
836 fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
837 let constraints = self.constraints(app);
838 let placeholder_dimensions = self.layout_inline_children(
839 app,
840 constraints.max_width,
841 ChildLayoutHelper::layout_child,
842 ChildLayoutHelper::get_baseline,
843 );
844 self.get_mut(app).placeholder_dimensions = Some(placeholder_dimensions);
845 self.layout_text_with_constraints(app, constraints);
846 let boxes = self
847 .painter_mut(app)
848 .inline_placeholder_boxes()
849 .expect("laid out just now");
850 self.position_inline_children(app, &boxes);
851 let text_size = self.painter(app).size();
852 let size = constraints.constrain(text_size);
853 self.set_size(app, size);
854
855 let did_overflow_height =
856 size.height() < text_size.height() || self.painter(app).did_exceed_max_lines();
857 let did_overflow_width = size.width() < text_size.width();
858 let has_visual_overflow = did_overflow_width || did_overflow_height;
864 let needs_clipping = has_visual_overflow
865 && match self.get(app).overflow {
866 TextOverflow::Visible => false,
867 TextOverflow::Clip | TextOverflow::Ellipsis | TextOverflow::Fade => true,
869 };
870 self.get_mut(app).needs_clipping = needs_clipping;
871 }
872
873 fn paint(
874 self: RenderHandle<Self>,
875 app: &mut App,
876 context: &mut PaintingContext,
877 offset: Offset,
878 ) {
879 let constraints = self.constraints(app);
890 self.layout_text_with_constraints(app, constraints);
891
892 let needs_clipping = self.get(app).needs_clipping;
893 if needs_clipping {
894 let bounds = offset & self.size(app);
895 context.canvas().save();
896 context.canvas().clip_rect(bounds, ClipOp::Intersect);
897 }
898 let (painter, fonts) = self.painter_with_fonts(app);
899 painter.paint(fonts, context.canvas(), offset);
900
901 self.paint_inline_children(app, context, offset);
902
903 if needs_clipping {
904 context.canvas().restore();
905 }
906 }
907}
908
909impl RenderBox for RenderParagraph {
910 crate::render_box_accessors!();
911
912 fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
913 RenderInlineChildrenContainerDefaults::setup_parent_data(self, app, child);
914 }
915
916 fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, _height: f64) -> f64 {
917 let placeholder_dimensions = self.layout_inline_children(
918 app,
919 f64::INFINITY,
920 |app, child, _| Size::new(child.get_min_intrinsic_width(app, f64::INFINITY), 0.0),
921 ChildLayoutHelper::get_dry_baseline,
922 );
923 let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
924 intrinsics.set_placeholder_dimensions(Some(placeholder_dimensions));
925 intrinsics.layout(fonts, 0.0, f64::INFINITY);
926 intrinsics.min_intrinsic_width()
927 }
928
929 fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, _height: f64) -> f64 {
930 let placeholder_dimensions = self.layout_inline_children(
931 app,
932 f64::INFINITY,
933 |app, child, _| Size::new(child.get_max_intrinsic_width(app, f64::INFINITY), 0.0),
936 ChildLayoutHelper::get_dry_baseline,
937 );
938 let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
939 intrinsics.set_placeholder_dimensions(Some(placeholder_dimensions));
940 intrinsics.layout(fonts, 0.0, f64::INFINITY);
941 intrinsics.max_intrinsic_width()
942 }
943
944 fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
945 self.compute_intrinsic_height(app, width)
946 }
947
948 fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
949 self.compute_intrinsic_height(app, width)
950 }
951
952 fn compute_dry_layout(
953 self: RenderHandle<Self>,
954 app: &mut App,
955 constraints: BoxConstraints,
956 ) -> Size {
957 let placeholder_dimensions = self.layout_inline_children(
958 app,
959 constraints.max_width,
960 ChildLayoutHelper::dry_layout_child,
961 ChildLayoutHelper::get_dry_baseline,
962 );
963 let max_width = self.adjust_max_width(app, constraints.max_width);
964 let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
965 intrinsics.set_placeholder_dimensions(Some(placeholder_dimensions));
966 intrinsics.layout(fonts, constraints.min_width, max_width);
967 let size = intrinsics.size();
968 constraints.constrain(size)
969 }
970
971 fn compute_distance_to_actual_baseline(
972 self: RenderHandle<Self>,
973 app: &mut App,
974 _baseline: TextBaseline,
975 ) -> Option<f64> {
976 debug_assert!(!self.as_object().debug_needs_layout(app));
977 let constraints = self.constraints(app);
978 debug_assert!(constraints.debug_assert_is_valid(false));
979 self.layout_text_with_constraints(app, constraints);
980 Some(
984 self.painter(app)
985 .compute_distance_to_actual_baseline(TextBaseline::Alphabetic),
986 )
987 }
988
989 fn compute_dry_baseline(
990 self: RenderHandle<Self>,
991 app: &mut App,
992 constraints: BoxConstraints,
993 _baseline: TextBaseline,
994 ) -> Option<f64> {
995 debug_assert!(constraints.debug_assert_is_valid(false));
996 let placeholder_dimensions = self.layout_inline_children(
997 app,
998 constraints.max_width,
999 ChildLayoutHelper::dry_layout_child,
1000 ChildLayoutHelper::get_dry_baseline,
1001 );
1002 let max_width = self.adjust_max_width(app, constraints.max_width);
1003 let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
1004 intrinsics.set_placeholder_dimensions(Some(placeholder_dimensions));
1005 intrinsics.layout(fonts, constraints.min_width, max_width);
1006 Some(intrinsics.compute_distance_to_actual_baseline(TextBaseline::Alphabetic))
1007 }
1008
1009 fn apply_paint_transform(
1010 self: RenderHandle<Self>,
1011 app: &App,
1012 child: AnyRenderObject,
1013 transform: &mut Matrix4,
1014 ) {
1015 self.default_apply_paint_transform(app, child, transform);
1016 }
1017
1018 fn hit_test_self(self: RenderHandle<Self>, _app: &App, _position: Offset) -> bool {
1019 true
1020 }
1021
1022 fn hit_test_children(
1023 self: RenderHandle<Self>,
1024 app: &mut App,
1025 result: &mut BoxHitTestResult<'_>,
1026 position: Offset,
1027 ) -> bool {
1028 let painter = self.painter(app);
1029 let glyph = painter.get_closest_glyph_for_offset(position);
1030 let span_hit = glyph
1036 .filter(|glyph| glyph.grapheme_cluster_layout_bounds.contains(position))
1037 .and_then(|glyph| {
1038 painter
1039 .text()
1040 .expect("a paragraph always has text")
1041 .clone()
1042 .get_span_for_position(TextPosition::new(
1043 glyph.grapheme_cluster_code_unit_range.start,
1044 ))
1045 .and_then(|span| span.to_hit_test_target())
1046 });
1047 match span_hit {
1048 Some(span) => {
1049 result.add(span.into());
1050 true
1051 }
1052 None => self.hit_test_inline_children(app, result, position),
1053 }
1054 }
1055}
1056
1057#[cfg(test)]
1058mod tests {
1059 use inset_embedder::Color;
1060 use inset_foundation::AppCell;
1061 use inset_gestures::HitTestResult;
1062 use inset_painting::{TextSpan, TextStyle};
1063
1064 use super::*;
1065 use crate::layer::{ContainerLayer, ErasedLayer, OffsetLayer};
1066 use crate::pipeline_owner::PipelineOwner;
1067 use crate::proxy_box::RenderRepaintBoundary;
1068
1069 fn install_fonts(app: &mut App) {
1071 let binding = PaintingBinding::instance(app);
1072 if !binding.has_fonts(app) {
1073 binding.install_fonts(app, |fonts| {
1074 fonts.add_source(valo_system_fonts::SystemFonts::load());
1075 });
1076 }
1077 }
1078
1079 fn span(text: &str) -> InlineSpanRef {
1080 TextSpan::new()
1081 .text(text)
1082 .style(TextStyle::new().font_size(20.0))
1083 .into_span()
1084 }
1085
1086 fn laid_out(
1087 app: &mut App,
1088 text: &str,
1089 constraints: BoxConstraints,
1090 ) -> (
1091 RenderHandle<RenderParagraph>,
1092 RenderHandle<RenderRepaintBoundary>,
1093 ) {
1094 laid_out_span(app, span(text), constraints)
1095 }
1096
1097 fn laid_out_span(
1098 app: &mut App,
1099 text: InlineSpanRef,
1100 constraints: BoxConstraints,
1101 ) -> (
1102 RenderHandle<RenderParagraph>,
1103 RenderHandle<RenderRepaintBoundary>,
1104 ) {
1105 install_fonts(app);
1106 let paragraph = RenderParagraph::new(app, text, TextDirection::Ltr, None);
1107 let root = RenderRepaintBoundary::new(app, Some(paragraph.as_box()));
1108 let owner = PipelineOwner::new(app, None, None);
1109 owner.set_root_node(app, Some(root.as_object()));
1110 root.schedule_initial_layout(app);
1111 root.layout(app, constraints, false);
1112 let paint_root = OffsetLayer::new(app, Offset::ZERO);
1113 paint_root.as_layer().attach(app, root.as_object().id());
1114 root.as_object()
1115 .schedule_initial_paint(app, paint_root.as_container_layer());
1116 (paragraph, root)
1117 }
1118
1119 #[test]
1122 fn paragraph_intrinsics_come_from_the_text() {
1123 let cell = AppCell::new();
1124 let mut app = cell.borrow_mut();
1125 install_fonts(&mut app);
1126 let paragraph = RenderParagraph::new(
1127 &mut app,
1128 span("one two three four"),
1129 TextDirection::Ltr,
1130 None,
1131 );
1132 let box_ = paragraph.as_box();
1133 let min_width = box_.get_min_intrinsic_width(&mut app, f64::INFINITY);
1134 let max_width = box_.get_max_intrinsic_width(&mut app, f64::INFINITY);
1135 assert!(min_width > 0.0);
1136 assert!(
1137 min_width < max_width,
1138 "the widest word is narrower than the whole text"
1139 );
1140
1141 let one_line = box_.get_min_intrinsic_height(&mut app, max_width);
1142 let wrapped = box_.get_min_intrinsic_height(&mut app, max_width / 2.0);
1143 assert!(wrapped > one_line, "half the width takes more lines");
1144 assert_eq!(box_.get_max_intrinsic_height(&mut app, max_width), one_line);
1145 }
1146
1147 #[test]
1150 fn paragraph_dry_layout_and_baseline_match_its_layout() {
1151 let cell = AppCell::new();
1152 let mut app = cell.borrow_mut();
1153 let constraints = BoxConstraints::new().max_width(200.0).max_height(1000.0);
1154 install_fonts(&mut app);
1155 let paragraph =
1156 RenderParagraph::new(&mut app, span("one two three"), TextDirection::Ltr, None);
1157 let dry = paragraph.as_box().get_dry_layout(&mut app, constraints);
1158 let dry_baseline =
1159 paragraph
1160 .as_box()
1161 .get_dry_baseline(&mut app, constraints, TextBaseline::Alphabetic);
1162
1163 let (paragraph, _root) = laid_out(&mut app, "one two three", constraints);
1164 assert_eq!(dry, paragraph.size(&app));
1165 let baseline =
1166 paragraph
1167 .as_box()
1168 .get_distance_to_baseline(&mut app, TextBaseline::Alphabetic, true);
1169 assert_eq!(dry_baseline, baseline);
1170 assert!(baseline.is_some_and(|baseline| baseline > 0.0));
1171 }
1172
1173 #[test]
1174 fn a_paragraph_sizes_itself_to_its_text_within_loose_constraints() {
1175 let cell = AppCell::new();
1176 let mut app = cell.borrow_mut();
1177 let loose = BoxConstraints::new().max_width(1000.0).max_height(1000.0);
1178 let (paragraph, _root) = laid_out(&mut app, "Hello", loose);
1179 let size = paragraph.size(&app);
1180 assert!(size.width() > 0.0 && size.width() < 1000.0);
1181 assert_eq!(size, paragraph.text_size(&app));
1182 assert!(!paragraph.did_exceed_max_lines(&app));
1183 }
1184
1185 #[test]
1186 fn narrow_constraints_wrap_the_text() {
1187 let cell = AppCell::new();
1188 let mut app = cell.borrow_mut();
1189 let loose = BoxConstraints::new().max_width(1000.0).max_height(1000.0);
1190 let (wide, _root) = laid_out(&mut app, "one two three four", loose);
1191 let natural = wide.size(&app);
1192 let narrow = BoxConstraints::new()
1193 .max_width(natural.width() / 2.0)
1194 .max_height(1000.0);
1195 let (wrapped, _root) = laid_out(&mut app, "one two three four", narrow);
1196 assert!(wrapped.size(&app).height() > natural.height());
1197 assert!(wrapped.size(&app).width() <= natural.width() / 2.0);
1198 }
1199
1200 #[test]
1201 fn soft_wrap_off_lets_the_text_overflow_and_clips_it() {
1202 let cell = AppCell::new();
1203 let mut app = cell.borrow_mut();
1204 let loose = BoxConstraints::new().max_width(1000.0).max_height(1000.0);
1205 let (wide, _root) = laid_out(&mut app, "one two three four", loose);
1206 let natural = wide.size(&app);
1207 install_fonts(&mut app);
1208 let paragraph = RenderParagraph::new(
1209 &mut app,
1210 span("one two three four"),
1211 TextDirection::Ltr,
1212 None,
1213 );
1214 paragraph.set_soft_wrap(&mut app, false);
1215 let root = RenderRepaintBoundary::new(&mut app, Some(paragraph.as_box()));
1216 let owner = PipelineOwner::new(&mut app, None, None);
1217 owner.set_root_node(&mut app, Some(root.as_object()));
1218 root.schedule_initial_layout(&mut app);
1219 let narrow = BoxConstraints::new()
1220 .max_width(natural.width() / 2.0)
1221 .max_height(1000.0);
1222 root.layout(&mut app, narrow, false);
1223 assert_eq!(paragraph.size(&app).width(), natural.width() / 2.0);
1224 assert_eq!(
1225 paragraph.text_size(&app).width(),
1226 natural.width(),
1227 "laid out unwrapped"
1228 );
1229 assert!(paragraph.get(&app).needs_clipping);
1230
1231 paragraph.set_overflow(&mut app, TextOverflow::Visible);
1232 owner.flush_layout(&mut app);
1233 assert!(!paragraph.get(&app).needs_clipping);
1234 }
1235
1236 #[test]
1237 fn ellipsis_overflow_reports_exceeded_lines() {
1238 let cell = AppCell::new();
1239 let mut app = cell.borrow_mut();
1240 let loose = BoxConstraints::new().max_width(1000.0).max_height(1000.0);
1241 let (wide, _root) = laid_out(&mut app, "one two three four", loose);
1242 let natural = wide.size(&app);
1243 install_fonts(&mut app);
1244 let paragraph = RenderParagraph::new(
1245 &mut app,
1246 span("one two three four"),
1247 TextDirection::Ltr,
1248 None,
1249 );
1250 paragraph.set_max_lines(&mut app, Some(1));
1251 paragraph.set_overflow(&mut app, TextOverflow::Ellipsis);
1252 let root = RenderRepaintBoundary::new(&mut app, Some(paragraph.as_box()));
1253 let owner = PipelineOwner::new(&mut app, None, None);
1254 owner.set_root_node(&mut app, Some(root.as_object()));
1255 root.schedule_initial_layout(&mut app);
1256 root.layout(
1257 &mut app,
1258 BoxConstraints::new()
1259 .max_width(natural.width() / 2.0)
1260 .max_height(1000.0),
1261 false,
1262 );
1263 assert!(paragraph.did_exceed_max_lines(&app));
1264 assert_eq!(paragraph.size(&app).height(), natural.height());
1265 }
1266
1267 #[test]
1268 fn setting_text_marks_layout_or_paint_by_the_comparison() {
1269 let cell = AppCell::new();
1270 let mut app = cell.borrow_mut();
1271 let loose = BoxConstraints::new().max_width(1000.0).max_height(1000.0);
1272 let (paragraph, root) = laid_out(&mut app, "Hello", loose);
1273 let owner = root.owner(&app).unwrap();
1274 owner.flush_compositing_bits(&mut app);
1275 owner.flush_paint(&mut app);
1276 assert!(!paragraph.debug_needs_layout(&app));
1277
1278 let recolored = TextSpan::new()
1279 .text("Hello")
1280 .style(
1281 TextStyle::new()
1282 .font_size(20.0)
1283 .color(Color::from_argb(255, 255, 0, 0)),
1284 )
1285 .into_span();
1286 paragraph.set_text(&mut app, recolored);
1287 assert!(
1288 !paragraph.debug_needs_layout(&app),
1289 "a color change is paint only"
1290 );
1291 assert!(paragraph.as_object().debug_needs_paint(&app));
1292
1293 paragraph.set_text(&mut app, span("Hello there"));
1294 assert!(paragraph.debug_needs_layout(&app));
1295 }
1296
1297 #[test]
1298 fn painting_records_glyphs_and_hit_testing_lands_on_the_span_and_the_paragraph() {
1299 let cell = AppCell::new();
1300 let mut app = cell.borrow_mut();
1301 let loose = BoxConstraints::new().max_width(1000.0).max_height(1000.0);
1302 let (paragraph, root) = laid_out(&mut app, "Hello", loose);
1303 let owner = root.owner(&app).unwrap();
1304 owner.flush_compositing_bits(&mut app);
1305 owner.flush_paint(&mut app);
1306 assert!(!paragraph.as_object().debug_needs_paint(&app));
1307
1308 let mut result = HitTestResult::new();
1309 let hit = paragraph.hit_test(
1310 &mut app,
1311 &mut BoxHitTestResult::wrap(&mut result),
1312 Offset::new(2.0, 2.0),
1313 );
1314 assert!(hit);
1315 assert_eq!(result.path().len(), 2, "the span, then the paragraph");
1316 let position = paragraph.get_position_for_offset(&mut app, Offset::new(0.0, 1.0));
1317 assert_eq!(position.offset, 0);
1318 assert_eq!(
1319 paragraph.get_word_boundary(&mut app, TextPosition::new(1)),
1320 TextRange::new(0, 5)
1321 );
1322 }
1323
1324 fn boxes_of(
1328 app: &mut App,
1329 paragraph: RenderHandle<RenderParagraph>,
1330 start: i32,
1331 end: i32,
1332 ) -> Vec<TextBox> {
1333 paragraph.get_boxes_for_selection(
1334 app,
1335 TextSelection::new(start, end),
1336 BoxHeightStyle::Tight,
1337 BoxWidthStyle::Tight,
1338 )
1339 }
1340
1341 fn center_of(text_box: &TextBox) -> Offset {
1342 Offset::new(
1343 (text_box.left + text_box.right) / 2.0,
1344 (text_box.top + text_box.bottom) / 2.0,
1345 )
1346 }
1347
1348 fn spans_hit(
1350 app: &mut App,
1351 paragraph: RenderHandle<RenderParagraph>,
1352 position: Offset,
1353 ) -> (bool, Vec<String>) {
1354 let mut result = HitTestResult::new();
1355 let hit = paragraph.hit_test(app, &mut BoxHitTestResult::wrap(&mut result), position);
1356 let spans = result
1357 .path()
1358 .iter()
1359 .filter_map(|entry| {
1360 let target: &dyn Any = entry.target();
1361 target.downcast_ref::<TextSpan>()?.text.clone()
1362 })
1363 .collect();
1364 (hit, spans)
1365 }
1366
1367 #[test]
1370 fn a_hit_on_a_glyph_adds_its_span_and_a_hit_off_the_glyphs_adds_none() {
1371 let cell = AppCell::new();
1372 let mut app = cell.borrow_mut();
1373 let text = TextSpan::new()
1374 .style(TextStyle::new().font_size(10.0))
1375 .children(vec![
1376 TextSpan::new().text("A".repeat(10)).into_span(),
1377 TextSpan::new()
1378 .text("BC")
1379 .style(TextStyle::new().letter_spacing(26.0))
1380 .into_span(),
1381 ])
1382 .into_span();
1383 let (paragraph, _root) =
1384 laid_out_span(&mut app, text, BoxConstraints::tight_for(Some(300.0), None));
1385
1386 let first_a = center_of(&boxes_of(&mut app, paragraph, 0, 1)[0]);
1387 let last_a = center_of(&boxes_of(&mut app, paragraph, 9, 10)[0]);
1388 let b = boxes_of(&mut app, paragraph, 10, 11)[0];
1389 let c = center_of(&boxes_of(&mut app, paragraph, 11, 12)[0]);
1390 for position in [first_a, last_a] {
1391 assert_eq!(
1392 spans_hit(&mut app, paragraph, position),
1393 (true, vec!["A".repeat(10)])
1394 );
1395 }
1396 let between_b_and_c = Offset::new(b.right - 1.0, center_of(&b).dy());
1398 for position in [center_of(&b), between_b_and_c, c] {
1399 assert_eq!(
1400 spans_hit(&mut app, paragraph, position),
1401 (true, vec!["BC".to_owned()])
1402 );
1403 }
1404
1405 let after_c = Offset::new(299.0, c.dy());
1406 assert_eq!(
1407 spans_hit(&mut app, paragraph, after_c),
1408 (true, Vec::new()),
1409 "the paragraph hits itself, and no span is under the pointer"
1410 );
1411 assert_eq!(
1412 spans_hit(&mut app, paragraph, Offset::new(9999.0, 9999.0)),
1413 (false, Vec::new())
1414 );
1415 }
1416
1417 struct RootHitTester {
1420 root: RenderHandle<RenderRepaintBoundary>,
1421 }
1422
1423 impl inset_gestures::GestureBindingOverridesObject for RootHitTester {
1424 fn hit_test_in_view(
1425 self: Handle<Self>,
1426 app: &mut App,
1427 result: &mut HitTestResult,
1428 position: Offset,
1429 _view_id: inset_embedder::ViewId,
1430 ) {
1431 let root = app.get(self).root;
1432 root.hit_test(app, &mut BoxHitTestResult::wrap(result), position);
1433 }
1434
1435 fn will_dispatch_event(
1436 self: Handle<Self>,
1437 _app: &mut App,
1438 _event: &inset_gestures::PointerEvent,
1439 _hit_test_result: Option<&HitTestResult>,
1440 ) {
1441 }
1442 }
1443
1444 fn tap_at(app: &mut App, position: Offset) {
1445 use inset_gestures::{GestureBinding, PointerDownEvent, PointerEvent, PointerUpEvent};
1446 let binding = GestureBinding::instance(app);
1447 binding.handle_pointer_event(
1448 app,
1449 PointerEvent::Down(PointerDownEvent {
1450 position,
1451 ..PointerDownEvent::default()
1452 }),
1453 );
1454 app.drain_microtasks();
1455 binding.handle_pointer_event(
1456 app,
1457 PointerEvent::Up(PointerUpEvent {
1458 position,
1459 ..PointerUpEvent::default()
1460 }),
1461 );
1462 app.drain_microtasks();
1463 }
1464
1465 #[test]
1468 fn a_tap_on_a_span_runs_its_recognizer_and_a_tap_beside_it_does_not() {
1469 use inset_gestures::{GestureBinding, GestureRecognizerLeaf, TapGestureRecognizer};
1470
1471 let cell = AppCell::new();
1472 let mut app = cell.borrow_mut();
1473 let taps = Rc::new(std::cell::Cell::new(0));
1474 let recognizer = TapGestureRecognizer::new(&mut app);
1475 let counter = Rc::clone(&taps);
1476 recognizer.set_on_tap(
1477 &mut app,
1478 Some(inset_foundation::Listener::new(move |_app| {
1479 counter.set(counter.get() + 1)
1480 })),
1481 );
1482 let text = TextSpan::new()
1483 .style(TextStyle::new().font_size(20.0))
1484 .children(vec![
1485 TextSpan::new().text("Tap ").into_span(),
1486 TextSpan::new()
1487 .text("here")
1488 .recognizer(recognizer.as_recognizer())
1489 .into_span(),
1490 TextSpan::new().text(" please").into_span(),
1491 ])
1492 .into_span();
1493 let (paragraph, root) = laid_out_span(
1494 &mut app,
1495 text,
1496 BoxConstraints::new().max_width(1000.0).max_height(1000.0),
1497 );
1498 let hit_tester = app.create(RootHitTester { root });
1499 GestureBinding::instance(&mut app).set_overrides(&mut app, Rc::new(hit_tester));
1500
1501 let here = center_of(&boxes_of(&mut app, paragraph, 4, 8)[0]);
1502 tap_at(&mut app, here);
1503 assert_eq!(taps.get(), 1);
1504
1505 let beside = center_of(&boxes_of(&mut app, paragraph, 0, 3)[0]);
1506 tap_at(&mut app, beside);
1507 assert_eq!(taps.get(), 1, "the plain span has no recognizer");
1508 recognizer.as_recognizer().dispose(&mut app);
1509 }
1510
1511 #[test]
1514 fn the_mouse_shows_the_cursor_of_the_span_under_it() {
1515 use inset_gestures::{
1516 GestureRecognizerLeaf, PointerAddedEvent, PointerEvent, PointerHoverEvent,
1517 TapGestureRecognizer,
1518 };
1519 use inset_services::{MouseCursorRef, SystemMouseCursors};
1520
1521 use crate::mouse_tracker::MouseTracker;
1522
1523 let cell = AppCell::new();
1524 let mut app = cell.borrow_mut();
1525 let recognizer = TapGestureRecognizer::new(&mut app);
1526 let text = TextSpan::new()
1527 .style(TextStyle::new().font_size(20.0))
1528 .children(vec![
1529 TextSpan::new().text("plain ").into_span(),
1530 TextSpan::new()
1531 .text("link")
1532 .recognizer(recognizer.as_recognizer())
1533 .into_span(),
1534 TextSpan::new()
1535 .text(" forbidden")
1536 .mouse_cursor(SystemMouseCursors::FORBIDDEN)
1537 .into_span(),
1538 ])
1539 .into_span();
1540 let (paragraph, root) = laid_out_span(
1541 &mut app,
1542 text,
1543 BoxConstraints::new().max_width(1000.0).max_height(1000.0),
1544 );
1545 let tracker = MouseTracker::new(
1546 &mut app,
1547 Rc::new(move |app: &mut App, position: Offset, _view| {
1548 let mut result = HitTestResult::new();
1549 root.hit_test(app, &mut BoxHitTestResult::wrap(&mut result), position);
1550 result
1551 }),
1552 );
1553 let plain = center_of(&boxes_of(&mut app, paragraph, 0, 5)[0]);
1554 let link = center_of(&boxes_of(&mut app, paragraph, 6, 10)[0]);
1555 let forbidden = center_of(&boxes_of(&mut app, paragraph, 11, 20)[0]);
1556 tracker.update_with_event(
1557 &mut app,
1558 &PointerEvent::Added(PointerAddedEvent {
1559 kind: inset_embedder::PointerDeviceKind::Mouse,
1560 position: plain,
1561 ..PointerAddedEvent::default()
1562 }),
1563 None,
1564 );
1565 let cursor_at = |app: &mut App, position: Offset| -> MouseCursorRef {
1566 tracker.update_with_event(
1567 app,
1568 &PointerEvent::Hover(PointerHoverEvent {
1569 kind: inset_embedder::PointerDeviceKind::Mouse,
1570 position,
1571 ..PointerHoverEvent::default()
1572 }),
1573 None,
1574 );
1575 tracker.debug_device_active_cursor(app, 0).unwrap()
1576 };
1577 let basic: MouseCursorRef = SystemMouseCursors::BASIC.into();
1578 let click: MouseCursorRef = SystemMouseCursors::CLICK.into();
1579 let forbidden_cursor: MouseCursorRef = SystemMouseCursors::FORBIDDEN.into();
1580 assert!(*cursor_at(&mut app, link) == *click);
1581 assert!(*cursor_at(&mut app, plain) == *basic, "a plain span defers");
1582 assert!(*cursor_at(&mut app, forbidden) == *forbidden_cursor);
1583 recognizer.as_recognizer().dispose(&mut app);
1584 }
1585
1586 #[test]
1589 fn a_wrapped_span_is_entered_once_across_its_lines() {
1590 use std::cell::RefCell;
1591
1592 use inset_gestures::{
1593 PointerAddedEvent, PointerEnterEvent, PointerEvent, PointerExitEvent, PointerHoverEvent,
1594 };
1595
1596 use crate::mouse_tracker::MouseTracker;
1597
1598 let cell = AppCell::new();
1599 let mut app = cell.borrow_mut();
1600 let log: Rc<RefCell<Vec<&str>>> = Rc::default();
1601 let enter_log = Rc::clone(&log);
1602 let exit_log = Rc::clone(&log);
1603 let words = "one two three four five six seven eight";
1604 let text = TextSpan::new()
1605 .text(words)
1606 .style(TextStyle::new().font_size(20.0))
1607 .on_enter(Rc::new(move |_app: &mut App, _event: PointerEnterEvent| {
1608 enter_log.borrow_mut().push("enter")
1609 }))
1610 .on_exit(Rc::new(move |_app: &mut App, _event: PointerExitEvent| {
1611 exit_log.borrow_mut().push("exit")
1612 }))
1613 .into_span();
1614 let (paragraph, root) = laid_out_span(
1615 &mut app,
1616 text,
1617 BoxConstraints::new().max_width(120.0).max_height(1000.0),
1618 );
1619 let boxes = boxes_of(&mut app, paragraph, 0, 3);
1620 let first_line = center_of(&boxes[0]);
1621 let last = words.len() as i32;
1622 let last_box = *boxes_of(&mut app, paragraph, last - 1, last)
1623 .last()
1624 .unwrap();
1625 assert!(last_box.top > boxes[0].bottom - 1.0, "the span wraps");
1626 let last_line = center_of(&last_box);
1627 let tracker = MouseTracker::new(
1628 &mut app,
1629 Rc::new(move |app: &mut App, position: Offset, _view| {
1630 let mut result = HitTestResult::new();
1631 root.hit_test(app, &mut BoxHitTestResult::wrap(&mut result), position);
1632 result
1633 }),
1634 );
1635 let mouse = |position: Offset| {
1636 PointerEvent::Hover(PointerHoverEvent {
1637 kind: inset_embedder::PointerDeviceKind::Mouse,
1638 position,
1639 ..PointerHoverEvent::default()
1640 })
1641 };
1642 tracker.update_with_event(
1643 &mut app,
1644 &PointerEvent::Added(PointerAddedEvent {
1645 kind: inset_embedder::PointerDeviceKind::Mouse,
1646 position: Offset::new(5000.0, 5000.0),
1647 ..PointerAddedEvent::default()
1648 }),
1649 None,
1650 );
1651 tracker.update_with_event(&mut app, &mouse(first_line), None);
1652 tracker.update_with_event(&mut app, &mouse(last_line), None);
1653 assert_eq!(*log.borrow(), ["enter"]);
1654 tracker.update_with_event(&mut app, &mouse(Offset::new(5000.0, 5000.0)), None);
1655 assert_eq!(*log.borrow(), ["enter", "exit"]);
1656 }
1657}