1
2use api::{ExternalScrollId, PipelineId, PropertyBinding, PropertyBindingId, ReferenceFrameKind};
7use api::{APZScrollGeneration, HasScrollLinkedEffect, SampledScrollOffset};
8use api::{TransformStyle, StickyOffsetBounds};
9use api::units::*;
10use crate::spatial_tree::{CoordinateSystem, SpatialNodeIndex, TransformUpdateState};
11use crate::spatial_tree::CoordinateSystemId;
12use euclid::{Vector2D, SideOffsets2D};
13use crate::scene::SceneProperties;
14use crate::util::{LayoutFastTransform, MatrixHelpers, ScaleOffset, TransformedRectKind};
15use crate::util::{PointHelpers, VectorHelpers};
16
17#[derive(Clone, PartialEq)]
21#[cfg_attr(feature = "capture", derive(Serialize))]
22#[cfg_attr(feature = "replay", derive(Deserialize))]
23pub struct SpatialNodeDescriptor {
24 pub node_type: SpatialNodeType,
26
27 pub pipeline_id: PipelineId,
29}
30
31#[derive(Clone, PartialEq)]
32#[cfg_attr(feature = "capture", derive(Serialize))]
33#[cfg_attr(feature = "replay", derive(Deserialize))]
34pub enum SpatialNodeType {
35 StickyFrame(StickyFrameInfo),
40
41 ScrollFrame(ScrollFrameInfo),
44
45 ReferenceFrame(ReferenceFrameInfo),
47}
48
49pub struct SpatialNodeInfo<'a> {
52 pub node_type: &'a SpatialNodeType,
54
55 pub parent: Option<SpatialNodeIndex>,
57
58 pub snapping_transform: Option<ScaleOffset>,
61}
62
63#[cfg_attr(feature = "capture", derive(Serialize))]
66#[cfg_attr(feature = "replay", derive(Deserialize))]
67#[derive(PartialEq)]
68pub struct SceneSpatialNode {
69 pub snapping_transform: Option<ScaleOffset>,
72
73 pub parent: Option<SpatialNodeIndex>,
75
76 pub descriptor: SpatialNodeDescriptor,
78
79 pub is_root_coord_system: bool,
82}
83
84impl SceneSpatialNode {
85 pub fn new_reference_frame(
86 parent_index: Option<SpatialNodeIndex>,
87 transform_style: TransformStyle,
88 source_transform: PropertyBinding<LayoutTransform>,
89 kind: ReferenceFrameKind,
90 origin_in_parent_reference_frame: LayoutVector2D,
91 pipeline_id: PipelineId,
92 is_root_coord_system: bool,
93 is_pipeline_root: bool,
94 ) -> Self {
95 let info = ReferenceFrameInfo {
96 transform_style,
97 source_transform,
98 kind,
99 origin_in_parent_reference_frame,
100 is_pipeline_root,
101 };
102 Self::new(
103 pipeline_id,
104 parent_index,
105 SpatialNodeType::ReferenceFrame(info),
106 is_root_coord_system,
107 )
108 }
109
110 pub fn new_scroll_frame(
111 pipeline_id: PipelineId,
112 parent_index: SpatialNodeIndex,
113 external_id: ExternalScrollId,
114 frame_rect: &LayoutRect,
115 content_size: &LayoutSize,
116 frame_kind: ScrollFrameKind,
117 external_scroll_offset: LayoutVector2D,
118 offset_generation: APZScrollGeneration,
119 has_scroll_linked_effect: HasScrollLinkedEffect,
120 is_root_coord_system: bool,
121 ) -> Self {
122 let node_type = SpatialNodeType::ScrollFrame(ScrollFrameInfo::new(
123 *frame_rect,
124 LayoutSize::new(
125 (content_size.width - frame_rect.width()).max(0.0),
126 (content_size.height - frame_rect.height()).max(0.0)
127 ),
128 external_id,
129 frame_kind,
130 external_scroll_offset,
131 offset_generation,
132 has_scroll_linked_effect,
133 )
134 );
135
136 Self::new(
137 pipeline_id,
138 Some(parent_index),
139 node_type,
140 is_root_coord_system,
141 )
142 }
143
144 pub fn new_sticky_frame(
145 parent_index: SpatialNodeIndex,
146 sticky_frame_info: StickyFrameInfo,
147 pipeline_id: PipelineId,
148 is_root_coord_system: bool,
149 ) -> Self {
150 Self::new(
151 pipeline_id,
152 Some(parent_index),
153 SpatialNodeType::StickyFrame(sticky_frame_info),
154 is_root_coord_system,
155 )
156 }
157
158 fn new(
159 pipeline_id: PipelineId,
160 parent_index: Option<SpatialNodeIndex>,
161 node_type: SpatialNodeType,
162 is_root_coord_system: bool,
163 ) -> Self {
164 SceneSpatialNode {
165 parent: parent_index,
166 descriptor: SpatialNodeDescriptor {
167 pipeline_id,
168 node_type,
169 },
170 snapping_transform: None,
171 is_root_coord_system,
172 }
173 }
174}
175
176#[cfg_attr(feature = "capture", derive(Serialize))]
178#[cfg_attr(feature = "replay", derive(Deserialize))]
179pub struct SpatialNode {
180 pub viewport_transform: ScaleOffset,
184
185 pub content_transform: ScaleOffset,
187
188 pub snapping_transform: Option<ScaleOffset>,
191
192 pub coordinate_system_id: CoordinateSystemId,
194
195 pub transform_kind: TransformedRectKind,
197
198 pub pipeline_id: PipelineId,
200
201 pub parent: Option<SpatialNodeIndex>,
203
204 pub children: Vec<SpatialNodeIndex>,
206
207 pub node_type: SpatialNodeType,
209
210 pub invertible: bool,
214
215 pub is_async_zooming: bool,
218
219 pub is_ancestor_or_self_zooming: bool,
223}
224
225fn snap_offset<OffsetUnits, ScaleUnits>(
232 offset: Vector2D<f32, OffsetUnits>,
233 scale: Vector2D<f32, ScaleUnits>,
234) -> Vector2D<f32, OffsetUnits> {
235 let world_offset = WorldPoint::new(offset.x * scale.x, offset.y * scale.y);
236 let snapped_world_offset = world_offset.snap();
237 Vector2D::new(
238 if scale.x != 0.0 { snapped_world_offset.x / scale.x } else { offset.x },
239 if scale.y != 0.0 { snapped_world_offset.y / scale.y } else { offset.y },
240 )
241}
242
243impl SpatialNode {
244 pub fn add_child(&mut self, child: SpatialNodeIndex) {
245 self.children.push(child);
246 }
247
248 pub fn set_scroll_offsets(&mut self, mut offsets: Vec<SampledScrollOffset>) -> bool {
249 debug_assert!(offsets.len() > 0);
250
251 let scrolling = match self.node_type {
252 SpatialNodeType::ScrollFrame(ref mut scrolling) => scrolling,
253 _ => {
254 warn!("Tried to scroll a non-scroll node.");
255 return false;
256 }
257 };
258
259 for element in offsets.iter_mut() {
260 element.offset = -element.offset - scrolling.external_scroll_offset;
261
262 element.offset = element.offset.snap();
268 }
269
270 if scrolling.offsets == offsets {
271 return false;
272 }
273
274 scrolling.offsets = offsets;
275 true
276 }
277
278 pub fn mark_uninvertible(
279 &mut self,
280 state: &TransformUpdateState,
281 ) {
282 self.invertible = false;
283 self.viewport_transform = ScaleOffset::identity();
284 self.content_transform = ScaleOffset::identity();
285 self.coordinate_system_id = state.current_coordinate_system_id;
286 }
287
288 pub fn update(
289 &mut self,
290 state_stack: &[TransformUpdateState],
291 coord_systems: &mut Vec<CoordinateSystem>,
292 scene_properties: &SceneProperties,
293 ) {
294 let state = state_stack.last().unwrap();
295
296 self.is_ancestor_or_self_zooming = self.is_async_zooming | state.is_ancestor_or_self_zooming;
297
298 if !state.invertible {
301 self.mark_uninvertible(state);
302 return;
303 }
304
305 self.update_transform(
306 state_stack,
307 coord_systems,
308 scene_properties,
309 );
310
311 if !self.invertible {
312 self.mark_uninvertible(state);
313 }
314 }
315
316 pub fn update_transform(
317 &mut self,
318 state_stack: &[TransformUpdateState],
319 coord_systems: &mut Vec<CoordinateSystem>,
320 scene_properties: &SceneProperties,
321 ) {
322 let state = state_stack.last().unwrap();
323
324 self.invertible = true;
326
327 match self.node_type {
328 SpatialNodeType::ReferenceFrame(ref mut info) => {
329 let mut cs_scale_offset = ScaleOffset::identity();
330 let mut coordinate_system_id = state.current_coordinate_system_id;
331
332 let source_transform = {
334 let source_transform = scene_properties.resolve_layout_transform(&info.source_transform);
335 if let ReferenceFrameKind::Transform { is_2d_scale_translation: true, .. } = info.kind {
336 assert!(source_transform.is_2d_scale_translation(), "Reference frame was marked as only having 2d scale or translation");
337 }
338
339 LayoutFastTransform::from(source_transform)
340 };
341
342 let source_transform = match info.kind {
345 ReferenceFrameKind::Perspective { scrolling_relative_to: Some(external_id) } => {
346 let mut scroll_offset = LayoutVector2D::zero();
347
348 for parent_state in state_stack.iter().rev() {
349 if let Some(parent_external_id) = parent_state.external_id {
350 if parent_external_id == external_id {
351 break;
352 }
353 }
354
355 scroll_offset += parent_state.scroll_offset;
356 }
357
358 source_transform
361 .pre_translate(scroll_offset)
362 .then_translate(-scroll_offset)
363 }
364 ReferenceFrameKind::Perspective { scrolling_relative_to: None } |
365 ReferenceFrameKind::Transform { .. } => source_transform,
366 };
367
368 let parent_origin = match self.snapping_transform {
383 Some(..) => {
384 info.origin_in_parent_reference_frame
385 }
386 None => {
387 snap_offset(
388 info.origin_in_parent_reference_frame,
389 state.coordinate_system_relative_scale_offset.scale,
390 )
391 }
392 };
393
394 let resolved_transform =
395 LayoutFastTransform::with_vector(parent_origin)
396 .pre_transform(&source_transform);
397
398 let relative_transform = resolved_transform
403 .then_translate(snap_offset(state.parent_accumulated_scroll_offset, state.coordinate_system_relative_scale_offset.scale))
404 .to_transform()
405 .with_destination::<LayoutPixel>();
406
407 let mut reset_cs_id = match info.transform_style {
408 TransformStyle::Preserve3D => !state.preserves_3d,
409 TransformStyle::Flat => state.preserves_3d,
410 };
411
412 if !reset_cs_id {
415 match ScaleOffset::from_transform(&relative_transform) {
418 Some(ref scale_offset) => {
419 let mut maybe_snapped = scale_offset.clone();
423 if let ReferenceFrameKind::Transform { should_snap: true, .. } = info.kind {
424 maybe_snapped.offset = snap_offset(
425 scale_offset.offset,
426 state.coordinate_system_relative_scale_offset.scale,
427 );
428 }
429 cs_scale_offset = maybe_snapped.then(&state.coordinate_system_relative_scale_offset);
430 }
431 None => reset_cs_id = true,
432 }
433 }
434 if reset_cs_id {
435 let transform = relative_transform.then(
438 &state.coordinate_system_relative_scale_offset.to_transform()
439 );
440
441 let coord_system = {
443 let parent_system = &coord_systems[state.current_coordinate_system_id.0 as usize];
444 let mut cur_transform = transform;
445 if parent_system.should_flatten {
446 cur_transform.flatten_z_output();
447 }
448 let world_transform = cur_transform.then(&parent_system.world_transform);
449 let determinant = world_transform.determinant();
450 self.invertible = determinant != 0.0 && !determinant.is_nan();
451
452 CoordinateSystem {
453 transform,
454 world_transform,
455 should_flatten: match (info.transform_style, info.kind) {
456 (TransformStyle::Flat, ReferenceFrameKind::Transform { .. }) => true,
457 (_, _) => false,
458 },
459 parent: Some(state.current_coordinate_system_id),
460 }
461 };
462 coordinate_system_id = CoordinateSystemId(coord_systems.len() as u32);
463 coord_systems.push(coord_system);
464 }
465
466 self.coordinate_system_id = coordinate_system_id;
470 self.viewport_transform = cs_scale_offset;
471 self.content_transform = cs_scale_offset;
472 }
473 SpatialNodeType::StickyFrame(ref mut info) => {
474 let animated_offset = if let Some(transform_binding) = info.transform {
475 let transform = scene_properties.resolve_layout_transform(&transform_binding);
476 match ScaleOffset::from_transform(&transform) {
477 Some(ref scale_offset) => {
478 debug_assert!(scale_offset.scale == Vector2D::new(1.0, 1.0),
479 "Can only animate a translation on sticky elements");
480 LayoutVector2D::from_untyped(scale_offset.offset)
481 }
482 None => {
483 debug_assert!(false, "Can only animate a translation on sticky elements");
484 LayoutVector2D::zero()
485 }
486 }
487 } else {
488 LayoutVector2D::zero()
489 };
490
491 let sticky_offset = Self::calculate_sticky_offset(
492 &state.nearest_scrolling_ancestor_offset,
493 &state.nearest_scrolling_ancestor_viewport,
494 info,
495 );
496
497 let accumulated_offset = state.parent_accumulated_scroll_offset + sticky_offset + animated_offset;
501 self.viewport_transform = state.coordinate_system_relative_scale_offset
502 .pre_offset(snap_offset(accumulated_offset, state.coordinate_system_relative_scale_offset.scale).to_untyped());
503 self.content_transform = self.viewport_transform;
504
505 info.current_offset = sticky_offset + animated_offset;
506
507 self.coordinate_system_id = state.current_coordinate_system_id;
508 }
509 SpatialNodeType::ScrollFrame(_) => {
510 let accumulated_offset = state.parent_accumulated_scroll_offset;
513 self.viewport_transform = state.coordinate_system_relative_scale_offset
514 .pre_offset(snap_offset(accumulated_offset, state.coordinate_system_relative_scale_offset.scale).to_untyped());
515
516 let added_offset = accumulated_offset + self.scroll_offset();
519 self.content_transform = state.coordinate_system_relative_scale_offset
520 .pre_offset(snap_offset(added_offset, state.coordinate_system_relative_scale_offset.scale).to_untyped());
521
522 self.coordinate_system_id = state.current_coordinate_system_id;
523 }
524 }
525
526 self.transform_kind = if self.coordinate_system_id.0 == 0 {
528 TransformedRectKind::AxisAligned
529 } else {
530 TransformedRectKind::Complex
531 };
532 }
533
534 fn calculate_sticky_offset(
535 viewport_scroll_offset: &LayoutVector2D,
536 viewport_rect: &LayoutRect,
537 info: &StickyFrameInfo
538 ) -> LayoutVector2D {
539 if info.margins.top.is_none() && info.margins.bottom.is_none() &&
540 info.margins.left.is_none() && info.margins.right.is_none() {
541 return LayoutVector2D::zero();
542 }
543
544 let mut sticky_rect = info.frame_rect.translate(*viewport_scroll_offset);
550
551 let mut sticky_offset = LayoutVector2D::zero();
552 if let Some(margin) = info.margins.top {
553 let top_viewport_edge = viewport_rect.min.y + margin;
554 if sticky_rect.min.y < top_viewport_edge {
555 sticky_offset.y = top_viewport_edge - sticky_rect.min.y;
558 } else if info.previously_applied_offset.y > 0.0 &&
559 sticky_rect.min.y > top_viewport_edge {
560 sticky_offset.y = top_viewport_edge - sticky_rect.min.y;
568 sticky_offset.y = sticky_offset.y.max(-info.previously_applied_offset.y);
569 }
570 }
571
572 if sticky_offset.y + info.previously_applied_offset.y <= 0.0 {
578 if let Some(margin) = info.margins.bottom {
579 sticky_rect.min.y += sticky_offset.y;
585 sticky_rect.max.y += sticky_offset.y;
586
587 let bottom_viewport_edge = viewport_rect.max.y - margin;
592 if sticky_rect.max.y > bottom_viewport_edge {
593 sticky_offset.y += bottom_viewport_edge - sticky_rect.max.y;
594 } else if info.previously_applied_offset.y < 0.0 &&
595 sticky_rect.max.y < bottom_viewport_edge {
596 sticky_offset.y += bottom_viewport_edge - sticky_rect.max.y;
597 sticky_offset.y = sticky_offset.y.min(-info.previously_applied_offset.y);
598 }
599 }
600 }
601
602 if let Some(margin) = info.margins.left {
604 let left_viewport_edge = viewport_rect.min.x + margin;
605 if sticky_rect.min.x < left_viewport_edge {
606 sticky_offset.x = left_viewport_edge - sticky_rect.min.x;
607 } else if info.previously_applied_offset.x > 0.0 &&
608 sticky_rect.min.x > left_viewport_edge {
609 sticky_offset.x = left_viewport_edge - sticky_rect.min.x;
610 sticky_offset.x = sticky_offset.x.max(-info.previously_applied_offset.x);
611 }
612 }
613
614 if sticky_offset.x + info.previously_applied_offset.x <= 0.0 {
615 if let Some(margin) = info.margins.right {
616 sticky_rect.min.x += sticky_offset.x;
617 sticky_rect.max.x += sticky_offset.x;
618 let right_viewport_edge = viewport_rect.max.x - margin;
619 if sticky_rect.max.x > right_viewport_edge {
620 sticky_offset.x += right_viewport_edge - sticky_rect.max.x;
621 } else if info.previously_applied_offset.x < 0.0 &&
622 sticky_rect.max.x < right_viewport_edge {
623 sticky_offset.x += right_viewport_edge - sticky_rect.max.x;
624 sticky_offset.x = sticky_offset.x.min(-info.previously_applied_offset.x);
625 }
626 }
627 }
628
629 let clamp_adjusted = |value: f32, adjust: f32, bounds: &StickyOffsetBounds| {
634 (value + adjust).max(bounds.min).min(bounds.max) - adjust
635 };
636 sticky_offset.y = clamp_adjusted(sticky_offset.y,
637 info.previously_applied_offset.y,
638 &info.vertical_offset_bounds);
639 sticky_offset.x = clamp_adjusted(sticky_offset.x,
640 info.previously_applied_offset.x,
641 &info.horizontal_offset_bounds);
642
643 sticky_offset + info.previously_applied_offset
647 }
648
649 pub fn prepare_state_for_children(&self, state: &mut TransformUpdateState) {
650 state.current_coordinate_system_id = self.coordinate_system_id;
651 state.is_ancestor_or_self_zooming = self.is_ancestor_or_self_zooming;
652 state.invertible &= self.invertible;
653
654 match self.node_type {
659 SpatialNodeType::StickyFrame(ref info) => {
660 state.parent_accumulated_scroll_offset += info.current_offset;
664 state.nearest_scrolling_ancestor_offset += info.current_offset;
667 state.preserves_3d = false;
668 state.external_id = None;
669 state.scroll_offset = info.current_offset;
670 }
671 SpatialNodeType::ScrollFrame(ref scrolling) => {
672 state.parent_accumulated_scroll_offset += scrolling.offset();
673 state.nearest_scrolling_ancestor_offset = scrolling.offset();
674 state.nearest_scrolling_ancestor_viewport = scrolling.viewport_rect;
675 state.preserves_3d = false;
676 state.external_id = Some(scrolling.external_id);
677 state.scroll_offset = scrolling.offset() + scrolling.external_scroll_offset;
678 }
679 SpatialNodeType::ReferenceFrame(ref info) => {
680 state.external_id = None;
681 state.scroll_offset = LayoutVector2D::zero();
682 state.preserves_3d = info.transform_style == TransformStyle::Preserve3D;
683 state.parent_accumulated_scroll_offset = LayoutVector2D::zero();
684 state.coordinate_system_relative_scale_offset = self.content_transform;
685 let translation = -info.origin_in_parent_reference_frame;
686 state.nearest_scrolling_ancestor_viewport =
687 state.nearest_scrolling_ancestor_viewport
688 .translate(translation);
689 }
690 }
691 }
692
693 pub fn scroll_offset(&self) -> LayoutVector2D {
694 match self.node_type {
695 SpatialNodeType::ScrollFrame(ref scrolling) => scrolling.offset(),
696 _ => LayoutVector2D::zero(),
697 }
698 }
699
700 pub fn matches_external_id(&self, external_id: ExternalScrollId) -> bool {
701 match self.node_type {
702 SpatialNodeType::ScrollFrame(ref info) if info.external_id == external_id => true,
703 _ => false,
704 }
705 }
706
707 pub fn is_transform_bound_to_property(&self, id: PropertyBindingId) -> bool {
710 if let SpatialNodeType::ReferenceFrame(ref info) = self.node_type {
711 if let PropertyBinding::Binding(key, _) = info.source_transform {
712 id == key.id
713 } else {
714 false
715 }
716 } else {
717 false
718 }
719 }
720}
721
722#[derive(Copy, Clone, Debug, PartialEq)]
725#[cfg_attr(feature = "capture", derive(Serialize))]
726#[cfg_attr(feature = "replay", derive(Deserialize))]
727pub enum ScrollFrameKind {
728 PipelineRoot {
729 is_root_pipeline: bool,
730 },
731 Explicit,
732}
733
734#[derive(Clone, Debug, PartialEq)]
735#[cfg_attr(feature = "capture", derive(Serialize))]
736#[cfg_attr(feature = "replay", derive(Deserialize))]
737pub struct ScrollFrameInfo {
738 pub viewport_rect: LayoutRect,
741
742 pub scrollable_size: LayoutSize,
744
745 pub external_id: ExternalScrollId,
749
750 pub frame_kind: ScrollFrameKind,
758
759 pub external_scroll_offset: LayoutVector2D,
762
763 pub offsets: Vec<SampledScrollOffset>,
773
774 pub offset_generation: APZScrollGeneration,
778
779 pub has_scroll_linked_effect: HasScrollLinkedEffect,
782}
783
784impl ScrollFrameInfo {
786 pub fn new(
787 viewport_rect: LayoutRect,
788 scrollable_size: LayoutSize,
789 external_id: ExternalScrollId,
790 frame_kind: ScrollFrameKind,
791 external_scroll_offset: LayoutVector2D,
792 offset_generation: APZScrollGeneration,
793 has_scroll_linked_effect: HasScrollLinkedEffect,
794 ) -> ScrollFrameInfo {
795 ScrollFrameInfo {
796 viewport_rect,
797 scrollable_size,
798 external_id,
799 frame_kind,
800 external_scroll_offset,
801 offsets: vec![SampledScrollOffset{
802 offset: -external_scroll_offset,
810 generation: offset_generation.clone(),
811 }],
812 offset_generation,
813 has_scroll_linked_effect,
814 }
815 }
816
817 pub fn offset(&self) -> LayoutVector2D {
818 debug_assert!(self.offsets.len() > 0, "There should be at least one sampled offset!");
819
820 if self.has_scroll_linked_effect == HasScrollLinkedEffect::No {
821 return self.offsets.first().map_or(LayoutVector2D::zero(), |sampled| sampled.offset);
823 }
824
825 match self.offsets.iter().find(|sampled| sampled.generation == self.offset_generation) {
826 Some(sampled) => sampled.offset,
828 _ => self.offsets.first().map_or(LayoutVector2D::zero(), |sampled| sampled.offset),
832 }
833 }
834}
835
836#[derive(Copy, Clone, Debug, PartialEq)]
838#[cfg_attr(feature = "capture", derive(Serialize))]
839#[cfg_attr(feature = "replay", derive(Deserialize))]
840pub struct ReferenceFrameInfo {
841 pub source_transform: PropertyBinding<LayoutTransform>,
846 pub transform_style: TransformStyle,
847 pub kind: ReferenceFrameKind,
848
849 pub origin_in_parent_reference_frame: LayoutVector2D,
853
854 pub is_pipeline_root: bool,
857}
858
859#[derive(Clone, Debug, PartialEq)]
860#[cfg_attr(feature = "capture", derive(Serialize))]
861#[cfg_attr(feature = "replay", derive(Deserialize))]
862pub struct StickyFrameInfo {
863 pub margins: SideOffsets2D<Option<f32>, LayoutPixel>,
864 pub frame_rect: LayoutRect,
865 pub vertical_offset_bounds: StickyOffsetBounds,
866 pub horizontal_offset_bounds: StickyOffsetBounds,
867 pub previously_applied_offset: LayoutVector2D,
868 pub current_offset: LayoutVector2D,
869 pub transform: Option<PropertyBinding<LayoutTransform>>,
870}
871
872impl StickyFrameInfo {
873 pub fn new(
874 frame_rect: LayoutRect,
875 margins: SideOffsets2D<Option<f32>, LayoutPixel>,
876 vertical_offset_bounds: StickyOffsetBounds,
877 horizontal_offset_bounds: StickyOffsetBounds,
878 previously_applied_offset: LayoutVector2D,
879 transform: Option<PropertyBinding<LayoutTransform>>,
880 ) -> StickyFrameInfo {
881 StickyFrameInfo {
882 frame_rect,
883 margins,
884 vertical_offset_bounds,
885 horizontal_offset_bounds,
886 previously_applied_offset,
887 current_offset: LayoutVector2D::zero(),
888 transform,
889 }
890 }
891}
892
893#[test]
894fn test_cst_perspective_relative_scroll() {
895 use crate::spatial_tree::{SceneSpatialTree, SpatialTree};
905 use euclid::Angle;
906
907 let mut cst = SceneSpatialTree::new();
908 let pipeline_id = PipelineId::dummy();
909 let ext_scroll_id = ExternalScrollId(1, pipeline_id);
910 let transform = LayoutTransform::rotation(0.0, 0.0, 1.0, Angle::degrees(45.0));
911
912 let root = cst.add_reference_frame(
913 cst.root_reference_frame_index(),
914 TransformStyle::Flat,
915 PropertyBinding::Value(LayoutTransform::identity()),
916 ReferenceFrameKind::Transform {
917 is_2d_scale_translation: false,
918 should_snap: false,
919 paired_with_perspective: false,
920 },
921 LayoutVector2D::zero(),
922 pipeline_id,
923 false,
924 );
925
926 let scroll_frame_1 = cst.add_scroll_frame(
927 root,
928 ext_scroll_id,
929 pipeline_id,
930 &LayoutRect::from_size(LayoutSize::new(100.0, 100.0)),
931 &LayoutSize::new(100.0, 500.0),
932 ScrollFrameKind::Explicit,
933 LayoutVector2D::zero(),
934 APZScrollGeneration::default(),
935 HasScrollLinkedEffect::No,
936 );
937
938 let scroll_frame_2 = cst.add_scroll_frame(
939 scroll_frame_1,
940 ExternalScrollId(2, pipeline_id),
941 pipeline_id,
942 &LayoutRect::from_size(LayoutSize::new(100.0, 100.0)),
943 &LayoutSize::new(100.0, 500.0),
944 ScrollFrameKind::Explicit,
945 LayoutVector2D::new(0.0, 50.0),
946 APZScrollGeneration::default(),
947 HasScrollLinkedEffect::No,
948 );
949
950 let ref_frame = cst.add_reference_frame(
951 scroll_frame_2,
952 TransformStyle::Preserve3D,
953 PropertyBinding::Value(transform),
954 ReferenceFrameKind::Perspective {
955 scrolling_relative_to: Some(ext_scroll_id),
956 },
957 LayoutVector2D::zero(),
958 pipeline_id,
959 false,
960 );
961
962 let mut st = SpatialTree::new();
963 st.apply_updates(cst.end_frame_and_get_pending_updates());
964 st.update_tree(&SceneProperties::new());
965
966 let world_transform = st.get_world_transform(ref_frame).into_transform().cast_unit();
967 let ref_transform = transform.then_translate(LayoutVector3D::new(0.0, -50.0, 0.0));
968 assert!(world_transform.approx_eq(&ref_transform));
969}