1use api::{ExternalScrollId, PropertyBinding, ReferenceFrameKind, TransformStyle};
6use api::{PipelineId, ScrollClamping, ScrollNodeState, ScrollSensitivity};
7use api::units::*;
8use euclid::Transform3D;
9use crate::gpu_types::TransformPalette;
10use crate::internal_types::{FastHashMap, FastHashSet};
11use crate::print_tree::{PrintableTree, PrintTree, PrintTreePrinter};
12use crate::scene::SceneProperties;
13use crate::spatial_node::{ScrollFrameInfo, SpatialNode, SpatialNodeType, StickyFrameInfo, ScrollFrameKind};
14use std::{ops, u32};
15use crate::util::{FastTransform, LayoutToWorldFastTransform, MatrixHelpers, ScaleOffset, scale_factors};
16
17pub type ScrollStates = FastHashMap<ExternalScrollId, ScrollFrameInfo>;
18
19#[derive(Debug, Copy, Clone, PartialEq)]
24#[cfg_attr(feature = "capture", derive(Serialize))]
25#[cfg_attr(feature = "replay", derive(Deserialize))]
26pub struct CoordinateSystemId(pub u32);
27
28#[derive(Debug, Copy, Clone, PartialEq)]
29pub struct StaticCoordinateSystemId(pub u32);
30
31impl StaticCoordinateSystemId {
32 pub const ROOT: StaticCoordinateSystemId = StaticCoordinateSystemId(0);
33}
34
35#[derive(Debug)]
38pub struct CoordinateSystem {
39 pub transform: LayoutTransform,
40 pub world_transform: LayoutToWorldTransform,
41 pub should_flatten: bool,
42 pub parent: Option<CoordinateSystemId>,
43}
44
45impl CoordinateSystem {
46 fn root() -> Self {
47 CoordinateSystem {
48 transform: LayoutTransform::identity(),
49 world_transform: LayoutToWorldTransform::identity(),
50 should_flatten: false,
51 parent: None,
52 }
53 }
54}
55
56#[derive(Debug, Copy, Clone, Eq, Hash, MallocSizeOf, PartialEq, PartialOrd, Ord)]
57#[cfg_attr(feature = "capture", derive(Serialize))]
58#[cfg_attr(feature = "replay", derive(Deserialize))]
59pub struct SpatialNodeIndex(pub u32);
60
61impl SpatialNodeIndex {
62 pub const INVALID: SpatialNodeIndex = SpatialNodeIndex(u32::MAX);
63}
64
65pub const ROOT_SPATIAL_NODE_INDEX: SpatialNodeIndex = SpatialNodeIndex(0);
67const TOPMOST_SCROLL_NODE_INDEX: SpatialNodeIndex = SpatialNodeIndex(1);
68
69const MIN_SCROLLABLE_AMOUNT: f32 = 0.01;
73
74const MIN_SCROLL_ROOT_SIZE: f32 = 128.0;
76
77impl SpatialNodeIndex {
78 pub fn new(index: usize) -> Self {
79 debug_assert!(index < ::std::u32::MAX as usize);
80 SpatialNodeIndex(index as u32)
81 }
82}
83
84impl CoordinateSystemId {
85 pub fn root() -> Self {
86 CoordinateSystemId(0)
87 }
88}
89
90#[derive(Debug, Copy, Clone, PartialEq)]
91pub enum VisibleFace {
92 Front,
93 Back,
94}
95
96impl Default for VisibleFace {
97 fn default() -> Self {
98 VisibleFace::Front
99 }
100}
101
102impl ops::Not for VisibleFace {
103 type Output = Self;
104 fn not(self) -> Self {
105 match self {
106 VisibleFace::Front => VisibleFace::Back,
107 VisibleFace::Back => VisibleFace::Front,
108 }
109 }
110}
111
112pub struct SpatialTree {
113 pub spatial_nodes: Vec<SpatialNode>,
116
117 coord_systems: Vec<CoordinateSystem>,
121
122 pub pending_scroll_offsets: FastHashMap<ExternalScrollId, (LayoutPoint, ScrollClamping)>,
123
124 pub pipelines_to_discard: FastHashSet<PipelineId>,
127
128 nodes_to_update: Vec<(SpatialNodeIndex, TransformUpdateState)>,
130
131 next_static_coord_system_id: u32,
133}
134
135#[derive(Clone)]
136pub struct TransformUpdateState {
137 pub parent_reference_frame_transform: LayoutToWorldFastTransform,
138 pub parent_accumulated_scroll_offset: LayoutVector2D,
139 pub nearest_scrolling_ancestor_offset: LayoutVector2D,
140 pub nearest_scrolling_ancestor_viewport: LayoutRect,
141
142 pub current_coordinate_system_id: CoordinateSystemId,
147
148 pub coordinate_system_relative_scale_offset: ScaleOffset,
150
151 pub invertible: bool,
155
156 pub preserves_3d: bool,
158}
159
160
161#[derive(Debug, Clone)]
164pub enum CoordinateSpaceMapping<Src, Dst> {
165 Local,
166 ScaleOffset(ScaleOffset),
167 Transform(Transform3D<f32, Src, Dst>),
168}
169
170impl<Src, Dst> CoordinateSpaceMapping<Src, Dst> {
171 pub fn into_transform(self) -> Transform3D<f32, Src, Dst> {
172 match self {
173 CoordinateSpaceMapping::Local => Transform3D::identity(),
174 CoordinateSpaceMapping::ScaleOffset(scale_offset) => scale_offset.to_transform(),
175 CoordinateSpaceMapping::Transform(transform) => transform,
176 }
177 }
178
179 pub fn into_fast_transform(self) -> FastTransform<Src, Dst> {
180 match self {
181 CoordinateSpaceMapping::Local => FastTransform::identity(),
182 CoordinateSpaceMapping::ScaleOffset(scale_offset) => FastTransform::with_scale_offset(scale_offset),
183 CoordinateSpaceMapping::Transform(transform) => FastTransform::with_transform(transform),
184 }
185 }
186
187 pub fn is_perspective(&self) -> bool {
188 match *self {
189 CoordinateSpaceMapping::Local |
190 CoordinateSpaceMapping::ScaleOffset(_) => false,
191 CoordinateSpaceMapping::Transform(ref transform) => transform.has_perspective_component(),
192 }
193 }
194
195 pub fn is_2d_axis_aligned(&self) -> bool {
196 match *self {
197 CoordinateSpaceMapping::Local |
198 CoordinateSpaceMapping::ScaleOffset(_) => true,
199 CoordinateSpaceMapping::Transform(ref transform) => transform.preserves_2d_axis_alignment(),
200 }
201 }
202
203 pub fn scale_factors(&self) -> (f32, f32) {
204 match *self {
205 CoordinateSpaceMapping::Local => (1.0, 1.0),
206 CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => (scale_offset.scale.x.abs(), scale_offset.scale.y.abs()),
207 CoordinateSpaceMapping::Transform(ref transform) => scale_factors(transform),
208 }
209 }
210
211 pub fn inverse(&self) -> Option<CoordinateSpaceMapping<Dst, Src>> {
212 match *self {
213 CoordinateSpaceMapping::Local => Some(CoordinateSpaceMapping::Local),
214 CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => {
215 Some(CoordinateSpaceMapping::ScaleOffset(scale_offset.inverse()))
216 }
217 CoordinateSpaceMapping::Transform(ref transform) => {
218 transform.inverse().map(CoordinateSpaceMapping::Transform)
219 }
220 }
221 }
222}
223
224enum TransformScroll {
225 Scrolled,
226 Unscrolled,
227}
228
229impl SpatialTree {
230 pub fn new() -> Self {
231 SpatialTree {
232 spatial_nodes: Vec::new(),
233 coord_systems: Vec::new(),
234 pending_scroll_offsets: FastHashMap::default(),
235 pipelines_to_discard: FastHashSet::default(),
236 nodes_to_update: Vec::new(),
237 next_static_coord_system_id: 0,
238 }
239 }
240
241 pub fn external_scroll_offset(&self, node_index: SpatialNodeIndex) -> LayoutVector2D {
244 let mut offset = LayoutVector2D::zero();
245 let mut current_node = Some(node_index);
246
247 while let Some(node_index) = current_node {
248 let node = &self.spatial_nodes[node_index.0 as usize];
249
250 match node.node_type {
251 SpatialNodeType::ScrollFrame(ref scrolling) => {
252 offset += scrolling.external_scroll_offset;
253 }
254 SpatialNodeType::StickyFrame(..) => {
255 }
257 SpatialNodeType::ReferenceFrame(..) => {
258 break;
261 }
262 }
263
264 current_node = node.parent;
265 }
266
267 offset
268 }
269
270 pub fn get_relative_transform(
273 &self,
274 child_index: SpatialNodeIndex,
275 parent_index: SpatialNodeIndex,
276 ) -> CoordinateSpaceMapping<LayoutPixel, LayoutPixel> {
277 self.get_relative_transform_with_face(child_index, parent_index, None)
278 }
279
280 pub fn get_relative_transform_with_face(
285 &self,
286 child_index: SpatialNodeIndex,
287 parent_index: SpatialNodeIndex,
288 mut visible_face: Option<&mut VisibleFace>,
289 ) -> CoordinateSpaceMapping<LayoutPixel, LayoutPixel> {
290 if child_index == parent_index {
291 return CoordinateSpaceMapping::Local;
292 }
293
294 let child = &self.spatial_nodes[child_index.0 as usize];
295 let parent = &self.spatial_nodes[parent_index.0 as usize];
296
297 if child.coordinate_system_id == parent.coordinate_system_id {
298 let scale_offset = parent.content_transform
299 .inverse()
300 .accumulate(&child.content_transform);
301 return CoordinateSpaceMapping::ScaleOffset(scale_offset);
302 }
303
304 if child_index.0 < parent_index.0 {
305 warn!("Unexpected transform queried from {:?} to {:?}, please call the graphics team!", child_index, parent_index);
306 let child_cs = &self.coord_systems[child.coordinate_system_id.0 as usize];
307 let child_transform = child.content_transform
308 .to_transform::<LayoutPixel, LayoutPixel>()
309 .then(&child_cs.world_transform);
310 let parent_cs = &self.coord_systems[parent.coordinate_system_id.0 as usize];
311 let parent_transform = parent.content_transform
312 .to_transform()
313 .then(&parent_cs.world_transform);
314
315 let result = parent_transform
316 .inverse()
317 .unwrap_or_default()
318 .then(&child_transform)
319 .with_source::<LayoutPixel>()
320 .with_destination::<LayoutPixel>();
321
322 if let Some(face) = visible_face {
323 if result.is_backface_visible() {
324 *face = VisibleFace::Back;
325 }
326 }
327 return CoordinateSpaceMapping::Transform(result);
328 }
329
330 let mut coordinate_system_id = child.coordinate_system_id;
331 let mut transform = child.content_transform.to_transform();
332
333 while coordinate_system_id != parent.coordinate_system_id {
338 let coord_system = &self.coord_systems[coordinate_system_id.0 as usize];
339
340 if coord_system.should_flatten {
341 if let Some(ref mut face) = visible_face {
342 if transform.is_backface_visible() {
343 **face = VisibleFace::Back;
344 }
345 }
346 transform.flatten_z_output();
347 }
348
349 coordinate_system_id = coord_system.parent.expect("invalid parent!");
350 transform = transform.then(&coord_system.transform);
351 }
352
353 transform = transform.then(
354 &parent.content_transform
355 .inverse()
356 .to_transform(),
357 );
358 if let Some(face) = visible_face {
359 if transform.is_backface_visible() {
360 *face = VisibleFace::Back;
361 }
362 }
363
364 CoordinateSpaceMapping::Transform(transform)
365 }
366
367 pub fn is_relative_transform_complex(
368 &self,
369 child_index: SpatialNodeIndex,
370 parent_index: SpatialNodeIndex,
371 ) -> bool {
372 if child_index == parent_index {
373 return false;
374 }
375
376 let child = &self.spatial_nodes[child_index.0 as usize];
377 let parent = &self.spatial_nodes[parent_index.0 as usize];
378
379 child.coordinate_system_id != parent.coordinate_system_id
380 }
381
382 fn get_world_transform_impl(
383 &self,
384 index: SpatialNodeIndex,
385 scroll: TransformScroll,
386 ) -> CoordinateSpaceMapping<LayoutPixel, WorldPixel> {
387 let child = &self.spatial_nodes[index.0 as usize];
388
389 if child.coordinate_system_id.0 == 0 {
390 if index == ROOT_SPATIAL_NODE_INDEX {
391 CoordinateSpaceMapping::Local
392 } else {
393 CoordinateSpaceMapping::ScaleOffset(child.content_transform)
394 }
395 } else {
396 let system = &self.coord_systems[child.coordinate_system_id.0 as usize];
397 let scale_offset = match scroll {
398 TransformScroll::Scrolled => &child.content_transform,
399 TransformScroll::Unscrolled => &child.viewport_transform,
400 };
401 let transform = scale_offset
402 .to_transform()
403 .then(&system.world_transform);
404
405 CoordinateSpaceMapping::Transform(transform)
406 }
407 }
408
409 pub fn get_world_transform(
411 &self,
412 index: SpatialNodeIndex,
413 ) -> CoordinateSpaceMapping<LayoutPixel, WorldPixel> {
414 self.get_world_transform_impl(index, TransformScroll::Scrolled)
415 }
416
417 pub fn get_world_viewport_transform(
420 &self,
421 index: SpatialNodeIndex,
422 ) -> CoordinateSpaceMapping<LayoutPixel, WorldPixel> {
423 self.get_world_transform_impl(index, TransformScroll::Unscrolled)
424 }
425
426 pub fn root_reference_frame_index(&self) -> SpatialNodeIndex {
429 debug_assert!(!self.spatial_nodes.is_empty());
431 ROOT_SPATIAL_NODE_INDEX
432 }
433
434 pub fn topmost_scroll_node_index(&self) -> SpatialNodeIndex {
437 debug_assert!(self.spatial_nodes.len() >= 1);
439 TOPMOST_SCROLL_NODE_INDEX
440 }
441
442 pub fn get_scroll_node_state(&self) -> Vec<ScrollNodeState> {
443 let mut result = vec![];
444 for node in &self.spatial_nodes {
445 if let SpatialNodeType::ScrollFrame(info) = node.node_type {
446 result.push(ScrollNodeState {
447 id: info.external_id,
448 scroll_offset: info.offset - info.external_scroll_offset,
449 })
450 }
451 }
452 result
453 }
454
455 pub fn drain(&mut self) -> ScrollStates {
456 let mut scroll_states = FastHashMap::default();
457 for old_node in &mut self.spatial_nodes.drain(..) {
458 if self.pipelines_to_discard.contains(&old_node.pipeline_id) {
459 continue;
460 }
461
462 match old_node.node_type {
463 SpatialNodeType::ScrollFrame(info) => {
464 scroll_states.insert(info.external_id, info);
465 }
466 _ => {}
467 }
468 }
469
470 self.coord_systems.clear();
471 self.pipelines_to_discard.clear();
472 scroll_states
473 }
474
475 pub fn scroll_node(
476 &mut self,
477 origin: LayoutPoint,
478 id: ExternalScrollId,
479 clamp: ScrollClamping
480 ) -> bool {
481 for node in &mut self.spatial_nodes {
482 if node.matches_external_id(id) {
483 return node.set_scroll_origin(&origin, clamp);
484 }
485 }
486
487 self.pending_scroll_offsets.insert(id, (origin, clamp));
488 false
489 }
490
491 pub fn update_tree(
492 &mut self,
493 scene_properties: &SceneProperties,
494 ) {
495 if self.spatial_nodes.is_empty() {
496 return;
497 }
498
499 profile_scope!("update_tree");
500 self.coord_systems.clear();
501 self.coord_systems.push(CoordinateSystem::root());
502
503 let root_node_index = self.root_reference_frame_index();
504 let state = TransformUpdateState {
505 parent_reference_frame_transform: LayoutVector2D::zero().into(),
506 parent_accumulated_scroll_offset: LayoutVector2D::zero(),
507 nearest_scrolling_ancestor_offset: LayoutVector2D::zero(),
508 nearest_scrolling_ancestor_viewport: LayoutRect::zero(),
509 current_coordinate_system_id: CoordinateSystemId::root(),
510 coordinate_system_relative_scale_offset: ScaleOffset::identity(),
511 invertible: true,
512 preserves_3d: false,
513 };
514 debug_assert!(self.nodes_to_update.is_empty());
515 self.nodes_to_update.push((root_node_index, state));
516
517 while let Some((node_index, mut state)) = self.nodes_to_update.pop() {
518 let (previous, following) = self.spatial_nodes.split_at_mut(node_index.0 as usize);
519 let node = match following.get_mut(0) {
520 Some(node) => node,
521 None => continue,
522 };
523
524 node.update(&mut state, &mut self.coord_systems, scene_properties, &*previous);
525
526 if !node.children.is_empty() {
527 node.prepare_state_for_children(&mut state);
528 self.nodes_to_update.extend(node.children
529 .iter()
530 .rev()
531 .map(|child_index| (*child_index, state.clone()))
532 );
533 }
534 }
535 }
536
537 pub fn build_transform_palette(&self) -> TransformPalette {
538 profile_scope!("build_transform_palette");
539 let mut palette = TransformPalette::new(self.spatial_nodes.len());
540 for i in 0 .. self.spatial_nodes.len() {
542 let index = SpatialNodeIndex(i as u32);
543 let world_transform = self.get_world_transform(index).into_transform();
544 palette.set_world_transform(index, world_transform);
545 }
546 palette
547 }
548
549 pub fn finalize_and_apply_pending_scroll_offsets(&mut self, old_states: ScrollStates) {
550 for node in &mut self.spatial_nodes {
551 let external_id = match node.node_type {
552 SpatialNodeType::ScrollFrame(ScrollFrameInfo { external_id, ..}) => external_id,
553 _ => continue,
554 };
555
556 if let Some(scrolling_state) = old_states.get(&external_id) {
557 node.apply_old_scrolling_state(scrolling_state);
558 }
559
560 if let Some((offset, clamping)) = self.pending_scroll_offsets.remove(&external_id) {
561 node.set_scroll_origin(&offset, clamping);
562 }
563 }
564 }
565
566 pub fn get_static_coordinate_system_id(&self, node_index: SpatialNodeIndex) -> StaticCoordinateSystemId {
568 self.spatial_nodes[node_index.0 as usize].static_coordinate_system_id
569 }
570
571 pub fn add_scroll_frame(
572 &mut self,
573 parent_index: SpatialNodeIndex,
574 external_id: ExternalScrollId,
575 pipeline_id: PipelineId,
576 frame_rect: &LayoutRect,
577 content_size: &LayoutSize,
578 scroll_sensitivity: ScrollSensitivity,
579 frame_kind: ScrollFrameKind,
580 external_scroll_offset: LayoutVector2D,
581 ) -> SpatialNodeIndex {
582 let static_coordinate_system_id = self.get_static_coordinate_system_id(parent_index);
584
585 let node = SpatialNode::new_scroll_frame(
586 pipeline_id,
587 parent_index,
588 external_id,
589 frame_rect,
590 content_size,
591 scroll_sensitivity,
592 frame_kind,
593 external_scroll_offset,
594 static_coordinate_system_id,
595 );
596 self.add_spatial_node(node)
597 }
598
599 pub fn add_reference_frame(
600 &mut self,
601 parent_index: Option<SpatialNodeIndex>,
602 transform_style: TransformStyle,
603 source_transform: PropertyBinding<LayoutTransform>,
604 kind: ReferenceFrameKind,
605 origin_in_parent_reference_frame: LayoutVector2D,
606 pipeline_id: PipelineId,
607 ) -> SpatialNodeIndex {
608
609 let new_static_coord_system = match parent_index {
611 Some(..) => {
612 match kind {
613 ReferenceFrameKind::Transform { is_2d_scale_translation: true, .. } => {
614 false
616 }
617 ReferenceFrameKind::Transform { is_2d_scale_translation: false, .. } | ReferenceFrameKind::Perspective { .. } => {
618 match source_transform {
622 PropertyBinding::Value(m) => {
623 !m.is_2d_scale_translation()
624 }
625 PropertyBinding::Binding(..) => {
626 true
628 }
629 }
630 }
631 }
632 }
633 None => {
634 true
636 }
637 };
638
639 let static_coordinate_system_id = if new_static_coord_system {
640 let id = StaticCoordinateSystemId(self.next_static_coord_system_id);
641 self.next_static_coord_system_id += 1;
642 id
643 } else {
644 self.get_static_coordinate_system_id(parent_index.unwrap())
645 };
646
647 let node = SpatialNode::new_reference_frame(
648 parent_index,
649 transform_style,
650 source_transform,
651 kind,
652 origin_in_parent_reference_frame,
653 pipeline_id,
654 static_coordinate_system_id,
655 );
656 self.add_spatial_node(node)
657 }
658
659 pub fn add_sticky_frame(
660 &mut self,
661 parent_index: SpatialNodeIndex,
662 sticky_frame_info: StickyFrameInfo,
663 pipeline_id: PipelineId,
664 ) -> SpatialNodeIndex {
665 let static_coordinate_system_id = self.get_static_coordinate_system_id(parent_index);
667
668 let node = SpatialNode::new_sticky_frame(
669 parent_index,
670 sticky_frame_info,
671 pipeline_id,
672 static_coordinate_system_id,
673 );
674 self.add_spatial_node(node)
675 }
676
677 pub fn add_spatial_node(&mut self, mut node: SpatialNode) -> SpatialNodeIndex {
678 let index = SpatialNodeIndex::new(self.spatial_nodes.len());
679
680 if let Some(parent_index) = node.parent {
682 let parent_node = &mut self.spatial_nodes[parent_index.0 as usize];
683 parent_node.add_child(index);
684 node.update_snapping(Some(parent_node));
685 } else {
686 node.update_snapping(None);
687 }
688
689 self.spatial_nodes.push(node);
690 index
691 }
692
693 pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
694 self.pipelines_to_discard.insert(pipeline_id);
695 }
696
697 pub fn is_ancestor(
699 &self,
700 maybe_parent: SpatialNodeIndex,
701 maybe_child: SpatialNodeIndex,
702 ) -> bool {
703 if maybe_parent == maybe_child {
705 return false;
706 }
707
708 let mut current_node = maybe_child;
709
710 while current_node != ROOT_SPATIAL_NODE_INDEX {
711 let node = &self.spatial_nodes[current_node.0 as usize];
712 current_node = node.parent.expect("bug: no parent");
713
714 if current_node == maybe_parent {
715 return true;
716 }
717 }
718
719 false
720 }
721
722 pub fn find_scroll_root(
726 &self,
727 spatial_node_index: SpatialNodeIndex,
728 ) -> SpatialNodeIndex {
729 let mut real_scroll_root = ROOT_SPATIAL_NODE_INDEX;
730 let mut outermost_scroll_root = ROOT_SPATIAL_NODE_INDEX;
731 let mut node_index = spatial_node_index;
732
733 while node_index != ROOT_SPATIAL_NODE_INDEX {
734 let node = &self.spatial_nodes[node_index.0 as usize];
735 match node.node_type {
736 SpatialNodeType::ReferenceFrame(ref info) => {
737 match info.kind {
738 ReferenceFrameKind::Transform { is_2d_scale_translation: true, .. } => {
739 }
741 ReferenceFrameKind::Transform { is_2d_scale_translation: false, .. } |
742 ReferenceFrameKind::Perspective { .. } => {
743 real_scroll_root = ROOT_SPATIAL_NODE_INDEX;
746 outermost_scroll_root = ROOT_SPATIAL_NODE_INDEX;
747 }
748 }
749 }
750 SpatialNodeType::StickyFrame(..) => {}
751 SpatialNodeType::ScrollFrame(ref info) => {
752 match info.frame_kind {
753 ScrollFrameKind::PipelineRoot { is_root_pipeline } => {
754 if is_root_pipeline {
756 break;
757 }
758 }
759 ScrollFrameKind::Explicit => {
760 outermost_scroll_root = node_index;
763
764 if info.scrollable_size.width > MIN_SCROLLABLE_AMOUNT ||
769 info.scrollable_size.height > MIN_SCROLLABLE_AMOUNT {
770 if info.viewport_rect.width() > MIN_SCROLL_ROOT_SIZE &&
778 info.viewport_rect.height() > MIN_SCROLL_ROOT_SIZE {
779 real_scroll_root = node_index;
782 }
783 }
784 }
785 }
786 }
787 }
788 node_index = node.parent.expect("unable to find parent node");
789 }
790
791 if real_scroll_root == ROOT_SPATIAL_NODE_INDEX {
797 outermost_scroll_root
798 } else {
799 real_scroll_root
800 }
801 }
802
803 fn print_node<T: PrintTreePrinter>(
804 &self,
805 index: SpatialNodeIndex,
806 pt: &mut T,
807 ) {
808 let node = &self.spatial_nodes[index.0 as usize];
809 match node.node_type {
810 SpatialNodeType::StickyFrame(ref sticky_frame_info) => {
811 pt.new_level(format!("StickyFrame"));
812 pt.add_item(format!("sticky info: {:?}", sticky_frame_info));
813 }
814 SpatialNodeType::ScrollFrame(scrolling_info) => {
815 pt.new_level(format!("ScrollFrame"));
816 pt.add_item(format!("viewport: {:?}", scrolling_info.viewport_rect));
817 pt.add_item(format!("scrollable_size: {:?}", scrolling_info.scrollable_size));
818 pt.add_item(format!("scroll offset: {:?}", scrolling_info.offset));
819 pt.add_item(format!("external_scroll_offset: {:?}", scrolling_info.external_scroll_offset));
820 pt.add_item(format!("kind: {:?}", scrolling_info.frame_kind));
821 }
822 SpatialNodeType::ReferenceFrame(ref info) => {
823 pt.new_level(format!("ReferenceFrame"));
824 pt.add_item(format!("kind: {:?}", info.kind));
825 pt.add_item(format!("transform_style: {:?}", info.transform_style));
826 pt.add_item(format!("source_transform: {:?}", info.source_transform));
827 pt.add_item(format!("origin_in_parent_reference_frame: {:?}", info.origin_in_parent_reference_frame));
828 }
829 }
830
831 pt.add_item(format!("index: {:?}", index));
832 pt.add_item(format!("content_transform: {:?}", node.content_transform));
833 pt.add_item(format!("viewport_transform: {:?}", node.viewport_transform));
834 pt.add_item(format!("snapping_transform: {:?}", node.snapping_transform));
835 pt.add_item(format!("coordinate_system_id: {:?}", node.coordinate_system_id));
836 pt.add_item(format!("static_coordinate_system_id: {:?}", node.static_coordinate_system_id));
837
838 for child_index in &node.children {
839 self.print_node(*child_index, pt);
840 }
841
842 pt.end_level();
843 }
844
845 pub fn get_local_visible_face(&self, node_index: SpatialNodeIndex) -> VisibleFace {
847 let node = &self.spatial_nodes[node_index.0 as usize];
848 let mut face = VisibleFace::Front;
849 if let Some(parent_index) = node.parent {
850 self.get_relative_transform_with_face(node_index, parent_index, Some(&mut face));
851 }
852 face
853 }
854
855 #[allow(dead_code)]
856 pub fn print(&self) {
857 if !self.spatial_nodes.is_empty() {
858 let mut buf = Vec::<u8>::new();
859 {
860 let mut pt = PrintTree::new_with_sink("spatial tree", &mut buf);
861 self.print_with(&mut pt);
862 }
863 debug!("{}", std::str::from_utf8(&buf).unwrap_or("(Tree printer emitted non-utf8)"));
866 }
867 }
868}
869
870impl PrintableTree for SpatialTree {
871 fn print_with<T: PrintTreePrinter>(&self, pt: &mut T) {
872 if !self.spatial_nodes.is_empty() {
873 self.print_node(self.root_reference_frame_index(), pt);
874 }
875 }
876}
877
878#[cfg(test)]
879fn add_reference_frame(
880 cst: &mut SpatialTree,
881 parent: Option<SpatialNodeIndex>,
882 transform: LayoutTransform,
883 origin_in_parent_reference_frame: LayoutVector2D,
884) -> SpatialNodeIndex {
885 cst.add_reference_frame(
886 parent,
887 TransformStyle::Preserve3D,
888 PropertyBinding::Value(transform),
889 ReferenceFrameKind::Transform {
890 is_2d_scale_translation: false,
891 should_snap: false,
892 },
893 origin_in_parent_reference_frame,
894 PipelineId::dummy(),
895 )
896}
897
898#[cfg(test)]
899fn test_pt(
900 px: f32,
901 py: f32,
902 cst: &SpatialTree,
903 child: SpatialNodeIndex,
904 parent: SpatialNodeIndex,
905 expected_x: f32,
906 expected_y: f32,
907) {
908 use euclid::approxeq::ApproxEq;
909 const EPSILON: f32 = 0.0001;
910
911 let p = LayoutPoint::new(px, py);
912 let m = cst.get_relative_transform(child, parent).into_transform();
913 let pt = m.transform_point2d(p).unwrap();
914 assert!(pt.x.approx_eq_eps(&expected_x, &EPSILON) &&
915 pt.y.approx_eq_eps(&expected_y, &EPSILON),
916 "p: {:?} -> {:?}\nm={:?}",
917 p, pt, m,
918 );
919}
920
921#[test]
922fn test_cst_simple_translation() {
923 let mut cst = SpatialTree::new();
926
927 let root = add_reference_frame(
928 &mut cst,
929 None,
930 LayoutTransform::identity(),
931 LayoutVector2D::zero(),
932 );
933
934 let child1 = add_reference_frame(
935 &mut cst,
936 Some(root),
937 LayoutTransform::translation(100.0, 0.0, 0.0),
938 LayoutVector2D::zero(),
939 );
940
941 let child2 = add_reference_frame(
942 &mut cst,
943 Some(child1),
944 LayoutTransform::translation(0.0, 50.0, 0.0),
945 LayoutVector2D::zero(),
946 );
947
948 let child3 = add_reference_frame(
949 &mut cst,
950 Some(child2),
951 LayoutTransform::translation(200.0, 200.0, 0.0),
952 LayoutVector2D::zero(),
953 );
954
955 cst.update_tree(&SceneProperties::new());
956
957 test_pt(100.0, 100.0, &cst, child1, root, 200.0, 100.0);
958 test_pt(100.0, 100.0, &cst, child2, root, 200.0, 150.0);
959 test_pt(100.0, 100.0, &cst, child2, child1, 100.0, 150.0);
960 test_pt(100.0, 100.0, &cst, child3, root, 400.0, 350.0);
961}
962
963#[test]
964fn test_cst_simple_scale() {
965 let mut cst = SpatialTree::new();
968
969 let root = add_reference_frame(
970 &mut cst,
971 None,
972 LayoutTransform::identity(),
973 LayoutVector2D::zero(),
974 );
975
976 let child1 = add_reference_frame(
977 &mut cst,
978 Some(root),
979 LayoutTransform::scale(4.0, 1.0, 1.0),
980 LayoutVector2D::zero(),
981 );
982
983 let child2 = add_reference_frame(
984 &mut cst,
985 Some(child1),
986 LayoutTransform::scale(1.0, 2.0, 1.0),
987 LayoutVector2D::zero(),
988 );
989
990 let child3 = add_reference_frame(
991 &mut cst,
992 Some(child2),
993 LayoutTransform::scale(2.0, 2.0, 1.0),
994 LayoutVector2D::zero(),
995 );
996
997 cst.update_tree(&SceneProperties::new());
998
999 test_pt(100.0, 100.0, &cst, child1, root, 400.0, 100.0);
1000 test_pt(100.0, 100.0, &cst, child2, root, 400.0, 200.0);
1001 test_pt(100.0, 100.0, &cst, child3, root, 800.0, 400.0);
1002 test_pt(100.0, 100.0, &cst, child2, child1, 100.0, 200.0);
1003 test_pt(100.0, 100.0, &cst, child3, child1, 200.0, 400.0);
1004}
1005
1006#[test]
1007fn test_cst_scale_translation() {
1008 let mut cst = SpatialTree::new();
1011
1012 let root = add_reference_frame(
1013 &mut cst,
1014 None,
1015 LayoutTransform::identity(),
1016 LayoutVector2D::zero(),
1017 );
1018
1019 let child1 = add_reference_frame(
1020 &mut cst,
1021 Some(root),
1022 LayoutTransform::translation(100.0, 50.0, 0.0),
1023 LayoutVector2D::zero(),
1024 );
1025
1026 let child2 = add_reference_frame(
1027 &mut cst,
1028 Some(child1),
1029 LayoutTransform::scale(2.0, 4.0, 1.0),
1030 LayoutVector2D::zero(),
1031 );
1032
1033 let child3 = add_reference_frame(
1034 &mut cst,
1035 Some(child2),
1036 LayoutTransform::translation(200.0, -100.0, 0.0),
1037 LayoutVector2D::zero(),
1038 );
1039
1040 let child4 = add_reference_frame(
1041 &mut cst,
1042 Some(child3),
1043 LayoutTransform::scale(3.0, 2.0, 1.0),
1044 LayoutVector2D::zero(),
1045 );
1046
1047 cst.update_tree(&SceneProperties::new());
1048
1049 test_pt(100.0, 100.0, &cst, child1, root, 200.0, 150.0);
1050 test_pt(100.0, 100.0, &cst, child2, root, 300.0, 450.0);
1051 test_pt(100.0, 100.0, &cst, child4, root, 1100.0, 450.0);
1052
1053 test_pt(0.0, 0.0, &cst, child4, child1, 400.0, -400.0);
1054 test_pt(100.0, 100.0, &cst, child4, child1, 1000.0, 400.0);
1055 test_pt(100.0, 100.0, &cst, child2, child1, 200.0, 400.0);
1056
1057 test_pt(100.0, 100.0, &cst, child3, child1, 600.0, 0.0);
1058}
1059
1060#[test]
1061fn test_cst_translation_rotate() {
1062 use euclid::Angle;
1064
1065 let mut cst = SpatialTree::new();
1066
1067 let root = add_reference_frame(
1068 &mut cst,
1069 None,
1070 LayoutTransform::identity(),
1071 LayoutVector2D::zero(),
1072 );
1073
1074 let child1 = add_reference_frame(
1075 &mut cst,
1076 Some(root),
1077 LayoutTransform::rotation(0.0, 0.0, 1.0, Angle::degrees(-90.0)),
1078 LayoutVector2D::zero(),
1079 );
1080
1081 cst.update_tree(&SceneProperties::new());
1082
1083 test_pt(100.0, 0.0, &cst, child1, root, 0.0, -100.0);
1084}
1085
1086#[test]
1087fn test_is_ancestor1() {
1088 let mut st = SpatialTree::new();
1089
1090 let root = add_reference_frame(
1091 &mut st,
1092 None,
1093 LayoutTransform::identity(),
1094 LayoutVector2D::zero(),
1095 );
1096
1097 let child1_0 = add_reference_frame(
1098 &mut st,
1099 Some(root),
1100 LayoutTransform::identity(),
1101 LayoutVector2D::zero(),
1102 );
1103
1104 let child1_1 = add_reference_frame(
1105 &mut st,
1106 Some(child1_0),
1107 LayoutTransform::identity(),
1108 LayoutVector2D::zero(),
1109 );
1110
1111 let child2 = add_reference_frame(
1112 &mut st,
1113 Some(root),
1114 LayoutTransform::identity(),
1115 LayoutVector2D::zero(),
1116 );
1117
1118 st.update_tree(&SceneProperties::new());
1119
1120 assert!(!st.is_ancestor(root, root));
1121 assert!(!st.is_ancestor(child1_0, child1_0));
1122 assert!(!st.is_ancestor(child1_1, child1_1));
1123 assert!(!st.is_ancestor(child2, child2));
1124
1125 assert!(st.is_ancestor(root, child1_0));
1126 assert!(st.is_ancestor(root, child1_1));
1127 assert!(st.is_ancestor(child1_0, child1_1));
1128
1129 assert!(!st.is_ancestor(child1_0, root));
1130 assert!(!st.is_ancestor(child1_1, root));
1131 assert!(!st.is_ancestor(child1_1, child1_0));
1132
1133 assert!(st.is_ancestor(root, child2));
1134 assert!(!st.is_ancestor(child2, root));
1135
1136 assert!(!st.is_ancestor(child1_0, child2));
1137 assert!(!st.is_ancestor(child1_1, child2));
1138 assert!(!st.is_ancestor(child2, child1_0));
1139 assert!(!st.is_ancestor(child2, child1_1));
1140}
1141
1142#[test]
1144fn test_find_scroll_root_simple() {
1145 let mut st = SpatialTree::new();
1146
1147 let root = st.add_reference_frame(
1148 None,
1149 TransformStyle::Flat,
1150 PropertyBinding::Value(LayoutTransform::identity()),
1151 ReferenceFrameKind::Transform {
1152 is_2d_scale_translation: false,
1153 should_snap: false,
1154 },
1155 LayoutVector2D::new(0.0, 0.0),
1156 PipelineId::dummy(),
1157 );
1158
1159 let scroll = st.add_scroll_frame(
1160 root,
1161 ExternalScrollId(1, PipelineId::dummy()),
1162 PipelineId::dummy(),
1163 &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)),
1164 &LayoutSize::new(800.0, 400.0),
1165 ScrollSensitivity::ScriptAndInputEvents,
1166 ScrollFrameKind::Explicit,
1167 LayoutVector2D::new(0.0, 0.0),
1168 );
1169
1170 assert_eq!(st.find_scroll_root(scroll), scroll);
1171}
1172
1173#[test]
1175fn test_find_scroll_root_sub_scroll_frame() {
1176 let mut st = SpatialTree::new();
1177
1178 let root = st.add_reference_frame(
1179 None,
1180 TransformStyle::Flat,
1181 PropertyBinding::Value(LayoutTransform::identity()),
1182 ReferenceFrameKind::Transform {
1183 is_2d_scale_translation: false,
1184 should_snap: false,
1185 },
1186 LayoutVector2D::new(0.0, 0.0),
1187 PipelineId::dummy(),
1188 );
1189
1190 let root_scroll = st.add_scroll_frame(
1191 root,
1192 ExternalScrollId(1, PipelineId::dummy()),
1193 PipelineId::dummy(),
1194 &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)),
1195 &LayoutSize::new(800.0, 400.0),
1196 ScrollSensitivity::ScriptAndInputEvents,
1197 ScrollFrameKind::Explicit,
1198 LayoutVector2D::new(0.0, 0.0),
1199 );
1200
1201 let sub_scroll = st.add_scroll_frame(
1202 root_scroll,
1203 ExternalScrollId(1, PipelineId::dummy()),
1204 PipelineId::dummy(),
1205 &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)),
1206 &LayoutSize::new(800.0, 400.0),
1207 ScrollSensitivity::ScriptAndInputEvents,
1208 ScrollFrameKind::Explicit,
1209 LayoutVector2D::new(0.0, 0.0),
1210 );
1211
1212 assert_eq!(st.find_scroll_root(sub_scroll), root_scroll);
1213}
1214
1215#[test]
1217fn test_find_scroll_root_not_scrollable() {
1218 let mut st = SpatialTree::new();
1219
1220 let root = st.add_reference_frame(
1221 None,
1222 TransformStyle::Flat,
1223 PropertyBinding::Value(LayoutTransform::identity()),
1224 ReferenceFrameKind::Transform {
1225 is_2d_scale_translation: false,
1226 should_snap: false,
1227 },
1228 LayoutVector2D::new(0.0, 0.0),
1229 PipelineId::dummy(),
1230 );
1231
1232 let root_scroll = st.add_scroll_frame(
1233 root,
1234 ExternalScrollId(1, PipelineId::dummy()),
1235 PipelineId::dummy(),
1236 &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)),
1237 &LayoutSize::new(400.0, 400.0),
1238 ScrollSensitivity::ScriptAndInputEvents,
1239 ScrollFrameKind::Explicit,
1240 LayoutVector2D::new(0.0, 0.0),
1241 );
1242
1243 let sub_scroll = st.add_scroll_frame(
1244 root_scroll,
1245 ExternalScrollId(1, PipelineId::dummy()),
1246 PipelineId::dummy(),
1247 &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)),
1248 &LayoutSize::new(800.0, 400.0),
1249 ScrollSensitivity::ScriptAndInputEvents,
1250 ScrollFrameKind::Explicit,
1251 LayoutVector2D::new(0.0, 0.0),
1252 );
1253
1254 assert_eq!(st.find_scroll_root(sub_scroll), sub_scroll);
1255}
1256
1257#[test]
1259fn test_find_scroll_root_too_small() {
1260 let mut st = SpatialTree::new();
1261
1262 let root = st.add_reference_frame(
1263 None,
1264 TransformStyle::Flat,
1265 PropertyBinding::Value(LayoutTransform::identity()),
1266 ReferenceFrameKind::Transform {
1267 is_2d_scale_translation: false,
1268 should_snap: false,
1269 },
1270 LayoutVector2D::new(0.0, 0.0),
1271 PipelineId::dummy(),
1272 );
1273
1274 let root_scroll = st.add_scroll_frame(
1275 root,
1276 ExternalScrollId(1, PipelineId::dummy()),
1277 PipelineId::dummy(),
1278 &LayoutRect::from_size(LayoutSize::new(MIN_SCROLL_ROOT_SIZE, MIN_SCROLL_ROOT_SIZE)),
1279 &LayoutSize::new(1000.0, 1000.0),
1280 ScrollSensitivity::ScriptAndInputEvents,
1281 ScrollFrameKind::Explicit,
1282 LayoutVector2D::new(0.0, 0.0),
1283 );
1284
1285 let sub_scroll = st.add_scroll_frame(
1286 root_scroll,
1287 ExternalScrollId(1, PipelineId::dummy()),
1288 PipelineId::dummy(),
1289 &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)),
1290 &LayoutSize::new(800.0, 400.0),
1291 ScrollSensitivity::ScriptAndInputEvents,
1292 ScrollFrameKind::Explicit,
1293 LayoutVector2D::new(0.0, 0.0),
1294 );
1295
1296 assert_eq!(st.find_scroll_root(sub_scroll), sub_scroll);
1297}
1298
1299#[test]
1302fn test_find_scroll_root_perspective() {
1303 let mut st = SpatialTree::new();
1304
1305 let root = st.add_reference_frame(
1306 None,
1307 TransformStyle::Flat,
1308 PropertyBinding::Value(LayoutTransform::identity()),
1309 ReferenceFrameKind::Transform {
1310 is_2d_scale_translation: false,
1311 should_snap: false,
1312 },
1313 LayoutVector2D::new(0.0, 0.0),
1314 PipelineId::dummy(),
1315 );
1316
1317 let root_scroll = st.add_scroll_frame(
1318 root,
1319 ExternalScrollId(1, PipelineId::dummy()),
1320 PipelineId::dummy(),
1321 &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)),
1322 &LayoutSize::new(400.0, 400.0),
1323 ScrollSensitivity::ScriptAndInputEvents,
1324 ScrollFrameKind::Explicit,
1325 LayoutVector2D::new(0.0, 0.0),
1326 );
1327
1328 let perspective = st.add_reference_frame(
1329 Some(root_scroll),
1330 TransformStyle::Flat,
1331 PropertyBinding::Value(LayoutTransform::identity()),
1332 ReferenceFrameKind::Perspective {
1333 scrolling_relative_to: None,
1334 },
1335 LayoutVector2D::new(0.0, 0.0),
1336 PipelineId::dummy(),
1337 );
1338
1339 let sub_scroll = st.add_scroll_frame(
1340 perspective,
1341 ExternalScrollId(1, PipelineId::dummy()),
1342 PipelineId::dummy(),
1343 &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)),
1344 &LayoutSize::new(800.0, 400.0),
1345 ScrollSensitivity::ScriptAndInputEvents,
1346 ScrollFrameKind::Explicit,
1347 LayoutVector2D::new(0.0, 0.0),
1348 );
1349
1350 assert_eq!(st.find_scroll_root(sub_scroll), root_scroll);
1351}
1352
1353#[test]
1356fn test_find_scroll_root_2d_scale() {
1357 let mut st = SpatialTree::new();
1358
1359 let root = st.add_reference_frame(
1360 None,
1361 TransformStyle::Flat,
1362 PropertyBinding::Value(LayoutTransform::identity()),
1363 ReferenceFrameKind::Transform {
1364 is_2d_scale_translation: false,
1365 should_snap: false,
1366 },
1367 LayoutVector2D::new(0.0, 0.0),
1368 PipelineId::dummy(),
1369 );
1370
1371 let root_scroll = st.add_scroll_frame(
1372 root,
1373 ExternalScrollId(1, PipelineId::dummy()),
1374 PipelineId::dummy(),
1375 &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)),
1376 &LayoutSize::new(400.0, 400.0),
1377 ScrollSensitivity::ScriptAndInputEvents,
1378 ScrollFrameKind::Explicit,
1379 LayoutVector2D::new(0.0, 0.0),
1380 );
1381
1382 let scale = st.add_reference_frame(
1383 Some(root_scroll),
1384 TransformStyle::Flat,
1385 PropertyBinding::Value(LayoutTransform::identity()),
1386 ReferenceFrameKind::Transform {
1387 is_2d_scale_translation: true,
1388 should_snap: false,
1389 },
1390 LayoutVector2D::new(0.0, 0.0),
1391 PipelineId::dummy(),
1392 );
1393
1394 let sub_scroll = st.add_scroll_frame(
1395 scale,
1396 ExternalScrollId(1, PipelineId::dummy()),
1397 PipelineId::dummy(),
1398 &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)),
1399 &LayoutSize::new(800.0, 400.0),
1400 ScrollSensitivity::ScriptAndInputEvents,
1401 ScrollFrameKind::Explicit,
1402 LayoutVector2D::new(0.0, 0.0),
1403 );
1404
1405 assert_eq!(st.find_scroll_root(sub_scroll), sub_scroll);
1406}