1
2use api::{ExternalScrollId, PipelineId, PropertyBinding, PropertyBindingId, ReferenceFrameKind};
7use api::{APZScrollGeneration, HasScrollLinkedEffect, SampledScrollOffset};
8use api::{TransformStyle, StickyOffsetBounds, SpatialTreeItemKey};
9use api::units::*;
10use crate::internal_types::PipelineInstanceId;
11use crate::spatial_tree::{CoordinateSystem, SpatialNodeIndex, TransformUpdateState};
12use crate::spatial_tree::CoordinateSystemId;
13use euclid::{Vector2D, SideOffsets2D};
14use crate::scene::SceneProperties;
15use crate::util::{LayoutFastTransform, MatrixHelpers, ScaleOffset, TransformedRectKind, PointHelpers};
16
17#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
23#[cfg_attr(feature = "capture", derive(Serialize))]
24#[cfg_attr(feature = "replay", derive(Deserialize))]
25pub enum SpatialNodeUidKind {
26 Root,
28 InternalScrollFrame,
30 InternalReferenceFrame,
32 External {
34 key: SpatialTreeItemKey,
35 },
36}
37
38#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
40#[cfg_attr(feature = "capture", derive(Serialize))]
41#[cfg_attr(feature = "replay", derive(Deserialize))]
42pub struct SpatialNodeUid {
43 pub kind: SpatialNodeUidKind,
45 pub pipeline_id: PipelineId,
47 pub instance_id: PipelineInstanceId,
49}
50
51impl SpatialNodeUid {
52 pub fn root() -> Self {
53 SpatialNodeUid {
54 kind: SpatialNodeUidKind::Root,
55 pipeline_id: PipelineId::dummy(),
56 instance_id: PipelineInstanceId::new(0),
57 }
58 }
59
60 pub fn root_scroll_frame(
61 pipeline_id: PipelineId,
62 instance_id: PipelineInstanceId,
63 ) -> Self {
64 SpatialNodeUid {
65 kind: SpatialNodeUidKind::InternalScrollFrame,
66 pipeline_id,
67 instance_id,
68 }
69 }
70
71 pub fn root_reference_frame(
72 pipeline_id: PipelineId,
73 instance_id: PipelineInstanceId,
74 ) -> Self {
75 SpatialNodeUid {
76 kind: SpatialNodeUidKind::InternalReferenceFrame,
77 pipeline_id,
78 instance_id,
79 }
80 }
81
82 pub fn external(
83 key: SpatialTreeItemKey,
84 pipeline_id: PipelineId,
85 instance_id: PipelineInstanceId,
86 ) -> Self {
87 SpatialNodeUid {
88 kind: SpatialNodeUidKind::External {
89 key,
90 },
91 pipeline_id,
92 instance_id,
93 }
94 }
95}
96
97#[derive(Clone, PartialEq)]
101#[cfg_attr(feature = "capture", derive(Serialize))]
102#[cfg_attr(feature = "replay", derive(Deserialize))]
103pub struct SpatialNodeDescriptor {
104 pub node_type: SpatialNodeType,
106
107 pub pipeline_id: PipelineId,
109}
110
111#[derive(Clone, PartialEq)]
112#[cfg_attr(feature = "capture", derive(Serialize))]
113#[cfg_attr(feature = "replay", derive(Deserialize))]
114pub enum SpatialNodeType {
115 StickyFrame(StickyFrameInfo),
120
121 ScrollFrame(ScrollFrameInfo),
124
125 ReferenceFrame(ReferenceFrameInfo),
127}
128
129pub struct SpatialNodeInfo<'a> {
132 pub node_type: &'a SpatialNodeType,
134
135 pub parent: Option<SpatialNodeIndex>,
137
138 pub snapping_transform: Option<ScaleOffset>,
141}
142
143#[cfg_attr(feature = "capture", derive(Serialize))]
146#[cfg_attr(feature = "replay", derive(Deserialize))]
147#[derive(PartialEq)]
148pub struct SceneSpatialNode {
149 pub snapping_transform: Option<ScaleOffset>,
152
153 pub parent: Option<SpatialNodeIndex>,
155
156 pub descriptor: SpatialNodeDescriptor,
158
159 pub is_root_coord_system: bool,
162}
163
164impl SceneSpatialNode {
165 pub fn new_reference_frame(
166 parent_index: Option<SpatialNodeIndex>,
167 transform_style: TransformStyle,
168 source_transform: PropertyBinding<LayoutTransform>,
169 kind: ReferenceFrameKind,
170 origin_in_parent_reference_frame: LayoutVector2D,
171 pipeline_id: PipelineId,
172 is_root_coord_system: bool,
173 is_pipeline_root: bool,
174 ) -> Self {
175 let info = ReferenceFrameInfo {
176 transform_style,
177 source_transform,
178 kind,
179 origin_in_parent_reference_frame,
180 is_pipeline_root,
181 };
182 Self::new(
183 pipeline_id,
184 parent_index,
185 SpatialNodeType::ReferenceFrame(info),
186 is_root_coord_system,
187 )
188 }
189
190 pub fn new_scroll_frame(
191 pipeline_id: PipelineId,
192 parent_index: SpatialNodeIndex,
193 external_id: ExternalScrollId,
194 frame_rect: &LayoutRect,
195 content_size: &LayoutSize,
196 frame_kind: ScrollFrameKind,
197 external_scroll_offset: LayoutVector2D,
198 offset_generation: APZScrollGeneration,
199 has_scroll_linked_effect: HasScrollLinkedEffect,
200 is_root_coord_system: bool,
201 ) -> Self {
202 let node_type = SpatialNodeType::ScrollFrame(ScrollFrameInfo::new(
203 *frame_rect,
204 LayoutSize::new(
205 (content_size.width - frame_rect.width()).max(0.0),
206 (content_size.height - frame_rect.height()).max(0.0)
207 ),
208 external_id,
209 frame_kind,
210 external_scroll_offset,
211 offset_generation,
212 has_scroll_linked_effect,
213 )
214 );
215
216 Self::new(
217 pipeline_id,
218 Some(parent_index),
219 node_type,
220 is_root_coord_system,
221 )
222 }
223
224 pub fn new_sticky_frame(
225 parent_index: SpatialNodeIndex,
226 sticky_frame_info: StickyFrameInfo,
227 pipeline_id: PipelineId,
228 is_root_coord_system: bool,
229 ) -> Self {
230 Self::new(
231 pipeline_id,
232 Some(parent_index),
233 SpatialNodeType::StickyFrame(sticky_frame_info),
234 is_root_coord_system,
235 )
236 }
237
238 fn new(
239 pipeline_id: PipelineId,
240 parent_index: Option<SpatialNodeIndex>,
241 node_type: SpatialNodeType,
242 is_root_coord_system: bool,
243 ) -> Self {
244 SceneSpatialNode {
245 parent: parent_index,
246 descriptor: SpatialNodeDescriptor {
247 pipeline_id,
248 node_type,
249 },
250 snapping_transform: None,
251 is_root_coord_system,
252 }
253 }
254}
255
256#[cfg_attr(feature = "capture", derive(Serialize))]
258#[cfg_attr(feature = "replay", derive(Deserialize))]
259pub struct SpatialNode {
260 pub viewport_transform: ScaleOffset,
264
265 pub content_transform: ScaleOffset,
267
268 pub snapping_transform: Option<ScaleOffset>,
271
272 pub coordinate_system_id: CoordinateSystemId,
274
275 pub transform_kind: TransformedRectKind,
277
278 pub pipeline_id: PipelineId,
280
281 pub parent: Option<SpatialNodeIndex>,
283
284 pub children: Vec<SpatialNodeIndex>,
286
287 pub node_type: SpatialNodeType,
289
290 pub invertible: bool,
294
295 pub is_async_zooming: bool,
298
299 pub is_ancestor_or_self_zooming: bool,
303}
304
305fn snap_offset<OffsetUnits, ScaleUnits>(
312 offset: Vector2D<f32, OffsetUnits>,
313 scale: Vector2D<f32, ScaleUnits>,
314) -> Vector2D<f32, OffsetUnits> {
315 let world_offset = WorldPoint::new(offset.x * scale.x, offset.y * scale.y);
316 let snapped_world_offset = world_offset.snap();
317 Vector2D::new(
318 if scale.x != 0.0 { snapped_world_offset.x / scale.x } else { offset.x },
319 if scale.y != 0.0 { snapped_world_offset.y / scale.y } else { offset.y },
320 )
321}
322
323impl SpatialNode {
324 pub fn add_child(&mut self, child: SpatialNodeIndex) {
325 self.children.push(child);
326 }
327
328 pub fn set_scroll_offsets(&mut self, mut offsets: Vec<SampledScrollOffset>) -> bool {
329 debug_assert!(offsets.len() > 0);
330
331 let scrolling = match self.node_type {
332 SpatialNodeType::ScrollFrame(ref mut scrolling) => scrolling,
333 _ => {
334 warn!("Tried to scroll a non-scroll node.");
335 return false;
336 }
337 };
338
339 for element in offsets.iter_mut() {
340 element.offset = -element.offset - scrolling.external_scroll_offset;
341 }
342
343 if scrolling.offsets == offsets {
344 return false;
345 }
346
347 scrolling.offsets = offsets;
348 true
349 }
350
351 pub fn mark_uninvertible(
352 &mut self,
353 state: &TransformUpdateState,
354 ) {
355 self.invertible = false;
356 self.viewport_transform = ScaleOffset::identity();
357 self.content_transform = ScaleOffset::identity();
358 self.coordinate_system_id = state.current_coordinate_system_id;
359 }
360
361 pub fn update(
362 &mut self,
363 state_stack: &[TransformUpdateState],
364 coord_systems: &mut Vec<CoordinateSystem>,
365 scene_properties: &SceneProperties,
366 ) {
367 let state = state_stack.last().unwrap();
368
369 self.is_ancestor_or_self_zooming = self.is_async_zooming | state.is_ancestor_or_self_zooming;
370
371 if !state.invertible {
374 self.mark_uninvertible(state);
375 return;
376 }
377
378 self.update_transform(
379 state_stack,
380 coord_systems,
381 scene_properties,
382 );
383
384 if !self.invertible {
385 self.mark_uninvertible(state);
386 }
387 }
388
389 pub fn update_transform(
390 &mut self,
391 state_stack: &[TransformUpdateState],
392 coord_systems: &mut Vec<CoordinateSystem>,
393 scene_properties: &SceneProperties,
394 ) {
395 let state = state_stack.last().unwrap();
396
397 self.invertible = true;
399
400 match self.node_type {
401 SpatialNodeType::ReferenceFrame(ref mut info) => {
402 let mut cs_scale_offset = ScaleOffset::identity();
403 let mut coordinate_system_id = state.current_coordinate_system_id;
404
405 let source_transform = {
407 let source_transform = scene_properties.resolve_layout_transform(&info.source_transform);
408 if let ReferenceFrameKind::Transform { is_2d_scale_translation: true, .. } = info.kind {
409 assert!(source_transform.is_2d_scale_translation(), "Reference frame was marked as only having 2d scale or translation");
410 }
411
412 LayoutFastTransform::from(source_transform)
413 };
414
415 let source_transform = match info.kind {
418 ReferenceFrameKind::Perspective { scrolling_relative_to: Some(external_id) } => {
419 let mut scroll_offset = LayoutVector2D::zero();
420
421 for parent_state in state_stack.iter().rev() {
422 if let Some(parent_external_id) = parent_state.external_id {
423 if parent_external_id == external_id {
424 break;
425 }
426 }
427
428 scroll_offset += parent_state.scroll_offset;
429 }
430
431 source_transform
434 .pre_translate(scroll_offset)
435 .then_translate(-scroll_offset)
436 }
437 ReferenceFrameKind::Perspective { scrolling_relative_to: None } |
438 ReferenceFrameKind::Transform { .. } => source_transform,
439 };
440
441 let resolved_transform =
442 LayoutFastTransform::with_vector(info.origin_in_parent_reference_frame)
443 .pre_transform(&source_transform);
444
445 let relative_transform = resolved_transform
450 .then_translate(snap_offset(state.parent_accumulated_scroll_offset, state.coordinate_system_relative_scale_offset.scale))
451 .to_transform()
452 .with_destination::<LayoutPixel>();
453
454 let mut reset_cs_id = match info.transform_style {
455 TransformStyle::Preserve3D => !state.preserves_3d,
456 TransformStyle::Flat => state.preserves_3d,
457 };
458
459 if !reset_cs_id {
462 match ScaleOffset::from_transform(&relative_transform) {
465 Some(ref scale_offset) => {
466 let mut maybe_snapped = scale_offset.clone();
470 if let ReferenceFrameKind::Transform { should_snap: true, .. } = info.kind {
471 maybe_snapped.offset = snap_offset(
472 scale_offset.offset,
473 state.coordinate_system_relative_scale_offset.scale,
474 );
475 }
476 cs_scale_offset = maybe_snapped.then(&state.coordinate_system_relative_scale_offset);
477 }
478 None => reset_cs_id = true,
479 }
480 }
481 if reset_cs_id {
482 let transform = relative_transform.then(
485 &state.coordinate_system_relative_scale_offset.to_transform()
486 );
487
488 let coord_system = {
490 let parent_system = &coord_systems[state.current_coordinate_system_id.0 as usize];
491 let mut cur_transform = transform;
492 if parent_system.should_flatten {
493 cur_transform.flatten_z_output();
494 }
495 let world_transform = cur_transform.then(&parent_system.world_transform);
496 let determinant = world_transform.determinant();
497 self.invertible = determinant != 0.0 && !determinant.is_nan();
498
499 CoordinateSystem {
500 transform,
501 world_transform,
502 should_flatten: match (info.transform_style, info.kind) {
503 (TransformStyle::Flat, ReferenceFrameKind::Transform { .. }) => true,
504 (_, _) => false,
505 },
506 parent: Some(state.current_coordinate_system_id),
507 }
508 };
509 coordinate_system_id = CoordinateSystemId(coord_systems.len() as u32);
510 coord_systems.push(coord_system);
511 }
512
513 self.coordinate_system_id = coordinate_system_id;
517 self.viewport_transform = cs_scale_offset;
518 self.content_transform = cs_scale_offset;
519 }
520 SpatialNodeType::StickyFrame(ref mut info) => {
521 let animated_offset = if let Some(transform_binding) = info.transform {
522 let transform = scene_properties.resolve_layout_transform(&transform_binding);
523 match ScaleOffset::from_transform(&transform) {
524 Some(ref scale_offset) => {
525 debug_assert!(scale_offset.scale == Vector2D::new(1.0, 1.0),
526 "Can only animate a translation on sticky elements");
527 LayoutVector2D::from_untyped(scale_offset.offset)
528 }
529 None => {
530 debug_assert!(false, "Can only animate a translation on sticky elements");
531 LayoutVector2D::zero()
532 }
533 }
534 } else {
535 LayoutVector2D::zero()
536 };
537
538 let sticky_offset = Self::calculate_sticky_offset(
539 &state.nearest_scrolling_ancestor_offset,
540 &state.nearest_scrolling_ancestor_viewport,
541 info,
542 );
543
544 let accumulated_offset = state.parent_accumulated_scroll_offset + sticky_offset + animated_offset;
548 self.viewport_transform = state.coordinate_system_relative_scale_offset
549 .pre_offset(snap_offset(accumulated_offset, state.coordinate_system_relative_scale_offset.scale).to_untyped());
550 self.content_transform = self.viewport_transform;
551
552 info.current_offset = sticky_offset + animated_offset;
553
554 self.coordinate_system_id = state.current_coordinate_system_id;
555 }
556 SpatialNodeType::ScrollFrame(_) => {
557 let accumulated_offset = state.parent_accumulated_scroll_offset;
560 self.viewport_transform = state.coordinate_system_relative_scale_offset
561 .pre_offset(snap_offset(accumulated_offset, state.coordinate_system_relative_scale_offset.scale).to_untyped());
562
563 let added_offset = accumulated_offset + self.scroll_offset();
566 self.content_transform = state.coordinate_system_relative_scale_offset
567 .pre_offset(snap_offset(added_offset, state.coordinate_system_relative_scale_offset.scale).to_untyped());
568
569 self.coordinate_system_id = state.current_coordinate_system_id;
570 }
571 }
572
573 self.transform_kind = if self.coordinate_system_id.0 == 0 {
575 TransformedRectKind::AxisAligned
576 } else {
577 TransformedRectKind::Complex
578 };
579 }
580
581 fn calculate_sticky_offset(
582 viewport_scroll_offset: &LayoutVector2D,
583 viewport_rect: &LayoutRect,
584 info: &StickyFrameInfo
585 ) -> LayoutVector2D {
586 if info.margins.top.is_none() && info.margins.bottom.is_none() &&
587 info.margins.left.is_none() && info.margins.right.is_none() {
588 return LayoutVector2D::zero();
589 }
590
591 let mut sticky_rect = info.frame_rect.translate(*viewport_scroll_offset);
597
598 let mut sticky_offset = LayoutVector2D::zero();
599 if let Some(margin) = info.margins.top {
600 let top_viewport_edge = viewport_rect.min.y + margin;
601 if sticky_rect.min.y < top_viewport_edge {
602 sticky_offset.y = top_viewport_edge - sticky_rect.min.y;
605 } else if info.previously_applied_offset.y > 0.0 &&
606 sticky_rect.min.y > top_viewport_edge {
607 sticky_offset.y = top_viewport_edge - sticky_rect.min.y;
615 sticky_offset.y = sticky_offset.y.max(-info.previously_applied_offset.y);
616 }
617 }
618
619 if sticky_offset.y + info.previously_applied_offset.y <= 0.0 {
625 if let Some(margin) = info.margins.bottom {
626 sticky_rect.min.y += sticky_offset.y;
632 sticky_rect.max.y += sticky_offset.y;
633
634 let bottom_viewport_edge = viewport_rect.max.y - margin;
639 if sticky_rect.max.y > bottom_viewport_edge {
640 sticky_offset.y += bottom_viewport_edge - sticky_rect.max.y;
641 } else if info.previously_applied_offset.y < 0.0 &&
642 sticky_rect.max.y < bottom_viewport_edge {
643 sticky_offset.y += bottom_viewport_edge - sticky_rect.max.y;
644 sticky_offset.y = sticky_offset.y.min(-info.previously_applied_offset.y);
645 }
646 }
647 }
648
649 if let Some(margin) = info.margins.left {
651 let left_viewport_edge = viewport_rect.min.x + margin;
652 if sticky_rect.min.x < left_viewport_edge {
653 sticky_offset.x = left_viewport_edge - sticky_rect.min.x;
654 } else if info.previously_applied_offset.x > 0.0 &&
655 sticky_rect.min.x > left_viewport_edge {
656 sticky_offset.x = left_viewport_edge - sticky_rect.min.x;
657 sticky_offset.x = sticky_offset.x.max(-info.previously_applied_offset.x);
658 }
659 }
660
661 if sticky_offset.x + info.previously_applied_offset.x <= 0.0 {
662 if let Some(margin) = info.margins.right {
663 sticky_rect.min.x += sticky_offset.x;
664 sticky_rect.max.x += sticky_offset.x;
665 let right_viewport_edge = viewport_rect.max.x - margin;
666 if sticky_rect.max.x > right_viewport_edge {
667 sticky_offset.x += right_viewport_edge - sticky_rect.max.x;
668 } else if info.previously_applied_offset.x < 0.0 &&
669 sticky_rect.max.x < right_viewport_edge {
670 sticky_offset.x += right_viewport_edge - sticky_rect.max.x;
671 sticky_offset.x = sticky_offset.x.min(-info.previously_applied_offset.x);
672 }
673 }
674 }
675
676 let clamp_adjusted = |value: f32, adjust: f32, bounds: &StickyOffsetBounds| {
681 (value + adjust).max(bounds.min).min(bounds.max) - adjust
682 };
683 sticky_offset.y = clamp_adjusted(sticky_offset.y,
684 info.previously_applied_offset.y,
685 &info.vertical_offset_bounds);
686 sticky_offset.x = clamp_adjusted(sticky_offset.x,
687 info.previously_applied_offset.x,
688 &info.horizontal_offset_bounds);
689
690 sticky_offset
691 }
692
693 pub fn prepare_state_for_children(&self, state: &mut TransformUpdateState) {
694 state.current_coordinate_system_id = self.coordinate_system_id;
695 state.is_ancestor_or_self_zooming = self.is_ancestor_or_self_zooming;
696 state.invertible &= self.invertible;
697
698 match self.node_type {
703 SpatialNodeType::StickyFrame(ref info) => {
704 state.parent_accumulated_scroll_offset += info.current_offset;
708 state.nearest_scrolling_ancestor_offset += info.current_offset;
711 state.preserves_3d = false;
712 state.external_id = None;
713 state.scroll_offset = info.current_offset;
714 }
715 SpatialNodeType::ScrollFrame(ref scrolling) => {
716 state.parent_accumulated_scroll_offset += scrolling.offset();
717 state.nearest_scrolling_ancestor_offset = scrolling.offset();
718 state.nearest_scrolling_ancestor_viewport = scrolling.viewport_rect;
719 state.preserves_3d = false;
720 state.external_id = Some(scrolling.external_id);
721 state.scroll_offset = scrolling.offset() + scrolling.external_scroll_offset;
722 }
723 SpatialNodeType::ReferenceFrame(ref info) => {
724 state.external_id = None;
725 state.scroll_offset = LayoutVector2D::zero();
726 state.preserves_3d = info.transform_style == TransformStyle::Preserve3D;
727 state.parent_accumulated_scroll_offset = LayoutVector2D::zero();
728 state.coordinate_system_relative_scale_offset = self.content_transform;
729 let translation = -info.origin_in_parent_reference_frame;
730 state.nearest_scrolling_ancestor_viewport =
731 state.nearest_scrolling_ancestor_viewport
732 .translate(translation);
733 }
734 }
735 }
736
737 pub fn scroll_offset(&self) -> LayoutVector2D {
738 match self.node_type {
739 SpatialNodeType::ScrollFrame(ref scrolling) => scrolling.offset(),
740 _ => LayoutVector2D::zero(),
741 }
742 }
743
744 pub fn matches_external_id(&self, external_id: ExternalScrollId) -> bool {
745 match self.node_type {
746 SpatialNodeType::ScrollFrame(ref info) if info.external_id == external_id => true,
747 _ => false,
748 }
749 }
750
751 pub fn is_transform_bound_to_property(&self, id: PropertyBindingId) -> bool {
754 if let SpatialNodeType::ReferenceFrame(ref info) = self.node_type {
755 if let PropertyBinding::Binding(key, _) = info.source_transform {
756 id == key.id
757 } else {
758 false
759 }
760 } else {
761 false
762 }
763 }
764}
765
766#[derive(Copy, Clone, Debug, PartialEq)]
769#[cfg_attr(feature = "capture", derive(Serialize))]
770#[cfg_attr(feature = "replay", derive(Deserialize))]
771pub enum ScrollFrameKind {
772 PipelineRoot {
773 is_root_pipeline: bool,
774 },
775 Explicit,
776}
777
778#[derive(Clone, Debug, PartialEq)]
779#[cfg_attr(feature = "capture", derive(Serialize))]
780#[cfg_attr(feature = "replay", derive(Deserialize))]
781pub struct ScrollFrameInfo {
782 pub viewport_rect: LayoutRect,
785
786 pub scrollable_size: LayoutSize,
788
789 pub external_id: ExternalScrollId,
793
794 pub frame_kind: ScrollFrameKind,
802
803 pub external_scroll_offset: LayoutVector2D,
806
807 pub offsets: Vec<SampledScrollOffset>,
817
818 pub offset_generation: APZScrollGeneration,
822
823 pub has_scroll_linked_effect: HasScrollLinkedEffect,
826}
827
828impl ScrollFrameInfo {
830 pub fn new(
831 viewport_rect: LayoutRect,
832 scrollable_size: LayoutSize,
833 external_id: ExternalScrollId,
834 frame_kind: ScrollFrameKind,
835 external_scroll_offset: LayoutVector2D,
836 offset_generation: APZScrollGeneration,
837 has_scroll_linked_effect: HasScrollLinkedEffect,
838 ) -> ScrollFrameInfo {
839 ScrollFrameInfo {
840 viewport_rect,
841 scrollable_size,
842 external_id,
843 frame_kind,
844 external_scroll_offset,
845 offsets: vec![SampledScrollOffset{
846 offset: -external_scroll_offset,
854 generation: offset_generation.clone(),
855 }],
856 offset_generation,
857 has_scroll_linked_effect,
858 }
859 }
860
861 pub fn offset(&self) -> LayoutVector2D {
862 debug_assert!(self.offsets.len() > 0, "There should be at least one sampled offset!");
863
864 if self.has_scroll_linked_effect == HasScrollLinkedEffect::No {
865 return self.offsets.first().map_or(LayoutVector2D::zero(), |sampled| sampled.offset);
867 }
868
869 match self.offsets.iter().find(|sampled| sampled.generation == self.offset_generation) {
870 Some(sampled) => sampled.offset,
872 _ => self.offsets.first().map_or(LayoutVector2D::zero(), |sampled| sampled.offset),
876 }
877 }
878}
879
880#[derive(Copy, Clone, Debug, PartialEq)]
882#[cfg_attr(feature = "capture", derive(Serialize))]
883#[cfg_attr(feature = "replay", derive(Deserialize))]
884pub struct ReferenceFrameInfo {
885 pub source_transform: PropertyBinding<LayoutTransform>,
890 pub transform_style: TransformStyle,
891 pub kind: ReferenceFrameKind,
892
893 pub origin_in_parent_reference_frame: LayoutVector2D,
897
898 pub is_pipeline_root: bool,
901}
902
903#[derive(Clone, Debug, PartialEq)]
904#[cfg_attr(feature = "capture", derive(Serialize))]
905#[cfg_attr(feature = "replay", derive(Deserialize))]
906pub struct StickyFrameInfo {
907 pub margins: SideOffsets2D<Option<f32>, LayoutPixel>,
908 pub frame_rect: LayoutRect,
909 pub vertical_offset_bounds: StickyOffsetBounds,
910 pub horizontal_offset_bounds: StickyOffsetBounds,
911 pub previously_applied_offset: LayoutVector2D,
912 pub current_offset: LayoutVector2D,
913 pub transform: Option<PropertyBinding<LayoutTransform>>,
914}
915
916impl StickyFrameInfo {
917 pub fn new(
918 frame_rect: LayoutRect,
919 margins: SideOffsets2D<Option<f32>, LayoutPixel>,
920 vertical_offset_bounds: StickyOffsetBounds,
921 horizontal_offset_bounds: StickyOffsetBounds,
922 previously_applied_offset: LayoutVector2D,
923 transform: Option<PropertyBinding<LayoutTransform>>,
924 ) -> StickyFrameInfo {
925 StickyFrameInfo {
926 frame_rect,
927 margins,
928 vertical_offset_bounds,
929 horizontal_offset_bounds,
930 previously_applied_offset,
931 current_offset: LayoutVector2D::zero(),
932 transform,
933 }
934 }
935}
936
937#[test]
938fn test_cst_perspective_relative_scroll() {
939 use crate::spatial_tree::{SceneSpatialTree, SpatialTree};
949 use euclid::Angle;
950
951 let mut cst = SceneSpatialTree::new();
952 let pipeline_id = PipelineId::dummy();
953 let ext_scroll_id = ExternalScrollId(1, pipeline_id);
954 let transform = LayoutTransform::rotation(0.0, 0.0, 1.0, Angle::degrees(45.0));
955 let pid = PipelineInstanceId::new(0);
956
957 let root = cst.add_reference_frame(
958 cst.root_reference_frame_index(),
959 TransformStyle::Flat,
960 PropertyBinding::Value(LayoutTransform::identity()),
961 ReferenceFrameKind::Transform {
962 is_2d_scale_translation: false,
963 should_snap: false,
964 paired_with_perspective: false,
965 },
966 LayoutVector2D::zero(),
967 pipeline_id,
968 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0), PipelineId::dummy(), pid),
969 );
970
971 let scroll_frame_1 = cst.add_scroll_frame(
972 root,
973 ext_scroll_id,
974 pipeline_id,
975 &LayoutRect::from_size(LayoutSize::new(100.0, 100.0)),
976 &LayoutSize::new(100.0, 500.0),
977 ScrollFrameKind::Explicit,
978 LayoutVector2D::zero(),
979 APZScrollGeneration::default(),
980 HasScrollLinkedEffect::No,
981 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy(), pid),
982 );
983
984 let scroll_frame_2 = cst.add_scroll_frame(
985 scroll_frame_1,
986 ExternalScrollId(2, pipeline_id),
987 pipeline_id,
988 &LayoutRect::from_size(LayoutSize::new(100.0, 100.0)),
989 &LayoutSize::new(100.0, 500.0),
990 ScrollFrameKind::Explicit,
991 LayoutVector2D::new(0.0, 50.0),
992 APZScrollGeneration::default(),
993 HasScrollLinkedEffect::No,
994 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 3), PipelineId::dummy(), pid),
995 );
996
997 let ref_frame = cst.add_reference_frame(
998 scroll_frame_2,
999 TransformStyle::Preserve3D,
1000 PropertyBinding::Value(transform),
1001 ReferenceFrameKind::Perspective {
1002 scrolling_relative_to: Some(ext_scroll_id),
1003 },
1004 LayoutVector2D::zero(),
1005 pipeline_id,
1006 SpatialNodeUid::external(SpatialTreeItemKey::new(0, 4), PipelineId::dummy(), pid),
1007 );
1008
1009 let mut st = SpatialTree::new();
1010 st.apply_updates(cst.end_frame_and_get_pending_updates());
1011 st.update_tree(&SceneProperties::new());
1012
1013 let world_transform = st.get_world_transform(ref_frame).into_transform().cast_unit();
1014 let ref_transform = transform.then_translate(LayoutVector3D::new(0.0, -50.0, 0.0));
1015 assert!(world_transform.approx_eq(&ref_transform));
1016}
1017