Source code

Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
use api::{ExternalScrollId, PropertyBinding, ReferenceFrameKind, TransformStyle};
6
use api::{PipelineId, ScrollClamping, ScrollNodeState, ScrollLocation, ScrollSensitivity};
7
use api::units::*;
8
use euclid::Transform3D;
9
use crate::gpu_types::TransformPalette;
10
use crate::internal_types::{FastHashMap, FastHashSet};
11
use crate::print_tree::{PrintableTree, PrintTree, PrintTreePrinter};
12
use crate::scene::SceneProperties;
13
use crate::spatial_node::{ScrollFrameInfo, SpatialNode, SpatialNodeType, StickyFrameInfo, ScrollFrameKind};
14
use std::{ops, u32};
15
use crate::util::{FastTransform, LayoutToWorldFastTransform, MatrixHelpers, ScaleOffset, scale_factors};
16
17
pub type ScrollStates = FastHashMap<ExternalScrollId, ScrollFrameInfo>;
18
19
/// An id that identifies coordinate systems in the SpatialTree. Each
20
/// coordinate system has an id and those ids will be shared when the coordinates
21
/// system are the same or are in the same axis-aligned space. This allows
22
/// for optimizing mask generation.
23
#[derive(Debug, Copy, Clone, PartialEq)]
24
#[cfg_attr(feature = "capture", derive(Serialize))]
25
#[cfg_attr(feature = "replay", derive(Deserialize))]
26
pub struct CoordinateSystemId(pub u32);
27
28
/// A node in the hierarchy of coordinate system
29
/// transforms.
30
#[derive(Debug)]
31
pub struct CoordinateSystem {
32
pub transform: LayoutTransform,
33
pub world_transform: LayoutToWorldTransform,
34
pub should_flatten: bool,
35
pub parent: Option<CoordinateSystemId>,
36
}
37
38
impl CoordinateSystem {
39
fn root() -> Self {
40
CoordinateSystem {
41
transform: LayoutTransform::identity(),
42
world_transform: LayoutToWorldTransform::identity(),
43
should_flatten: false,
44
parent: None,
45
}
46
}
47
}
48
49
#[derive(Debug, Copy, Clone, Eq, Hash, MallocSizeOf, PartialEq, PartialOrd, Ord)]
50
#[cfg_attr(feature = "capture", derive(Serialize))]
51
#[cfg_attr(feature = "replay", derive(Deserialize))]
52
pub struct SpatialNodeIndex(pub u32);
53
54
impl SpatialNodeIndex {
55
pub const INVALID: SpatialNodeIndex = SpatialNodeIndex(u32::MAX);
56
}
57
58
//Note: these have to match ROOT_REFERENCE_FRAME_SPATIAL_ID and ROOT_SCROLL_NODE_SPATIAL_ID
59
pub const ROOT_SPATIAL_NODE_INDEX: SpatialNodeIndex = SpatialNodeIndex(0);
60
const TOPMOST_SCROLL_NODE_INDEX: SpatialNodeIndex = SpatialNodeIndex(1);
61
62
impl SpatialNodeIndex {
63
pub fn new(index: usize) -> Self {
64
debug_assert!(index < ::std::u32::MAX as usize);
65
SpatialNodeIndex(index as u32)
66
}
67
}
68
69
impl CoordinateSystemId {
70
pub fn root() -> Self {
71
CoordinateSystemId(0)
72
}
73
}
74
75
#[derive(Debug, Copy, Clone, PartialEq)]
76
pub enum VisibleFace {
77
Front,
78
Back,
79
}
80
81
impl Default for VisibleFace {
82
fn default() -> Self {
83
VisibleFace::Front
84
}
85
}
86
87
impl ops::Not for VisibleFace {
88
type Output = Self;
89
fn not(self) -> Self {
90
match self {
91
VisibleFace::Front => VisibleFace::Back,
92
VisibleFace::Back => VisibleFace::Front,
93
}
94
}
95
}
96
97
pub struct SpatialTree {
98
/// Nodes which determine the positions (offsets and transforms) for primitives
99
/// and clips.
100
pub spatial_nodes: Vec<SpatialNode>,
101
102
/// A list of transforms that establish new coordinate systems.
103
/// Spatial nodes only establish a new coordinate system when
104
/// they have a transform that is not a simple 2d translation.
105
coord_systems: Vec<CoordinateSystem>,
106
107
pub pending_scroll_offsets: FastHashMap<ExternalScrollId, (LayoutPoint, ScrollClamping)>,
108
109
/// A set of pipelines which should be discarded the next time this
110
/// tree is drained.
111
pub pipelines_to_discard: FastHashSet<PipelineId>,
112
113
/// Temporary stack of nodes to update when traversing the tree.
114
nodes_to_update: Vec<(SpatialNodeIndex, TransformUpdateState)>,
115
}
116
117
#[derive(Clone)]
118
pub struct TransformUpdateState {
119
pub parent_reference_frame_transform: LayoutToWorldFastTransform,
120
pub parent_accumulated_scroll_offset: LayoutVector2D,
121
pub nearest_scrolling_ancestor_offset: LayoutVector2D,
122
pub nearest_scrolling_ancestor_viewport: LayoutRect,
123
124
/// An id for keeping track of the axis-aligned space of this node. This is used in
125
/// order to to track what kinds of clip optimizations can be done for a particular
126
/// display list item, since optimizations can usually only be done among
127
/// coordinate systems which are relatively axis aligned.
128
pub current_coordinate_system_id: CoordinateSystemId,
129
130
/// Scale and offset from the coordinate system that started this compatible coordinate system.
131
pub coordinate_system_relative_scale_offset: ScaleOffset,
132
133
/// True if this node is transformed by an invertible transform. If not, display items
134
/// transformed by this node will not be displayed and display items not transformed by this
135
/// node will not be clipped by clips that are transformed by this node.
136
pub invertible: bool,
137
138
/// True if this node is a part of Preserve3D hierarchy.
139
pub preserves_3d: bool,
140
}
141
142
143
/// Transformation between two nodes in the spatial tree that can sometimes be
144
/// encoded more efficiently than with a full matrix.
145
#[derive(Debug, Clone)]
146
pub enum CoordinateSpaceMapping<Src, Dst> {
147
Local,
148
ScaleOffset(ScaleOffset),
149
Transform(Transform3D<f32, Src, Dst>),
150
}
151
152
impl<Src, Dst> CoordinateSpaceMapping<Src, Dst> {
153
pub fn into_transform(self) -> Transform3D<f32, Src, Dst> {
154
match self {
155
CoordinateSpaceMapping::Local => Transform3D::identity(),
156
CoordinateSpaceMapping::ScaleOffset(scale_offset) => scale_offset.to_transform(),
157
CoordinateSpaceMapping::Transform(transform) => transform,
158
}
159
}
160
161
pub fn into_fast_transform(self) -> FastTransform<Src, Dst> {
162
match self {
163
CoordinateSpaceMapping::Local => FastTransform::identity(),
164
CoordinateSpaceMapping::ScaleOffset(scale_offset) => FastTransform::with_scale_offset(scale_offset),
165
CoordinateSpaceMapping::Transform(transform) => FastTransform::with_transform(transform),
166
}
167
}
168
169
pub fn visible_face(&self) -> VisibleFace {
170
match *self {
171
CoordinateSpaceMapping::Transform(ref transform) if transform.is_backface_visible() => VisibleFace::Back,
172
CoordinateSpaceMapping::Local |
173
CoordinateSpaceMapping::Transform(_) |
174
CoordinateSpaceMapping::ScaleOffset(_) => VisibleFace::Front,
175
176
}
177
}
178
179
pub fn is_perspective(&self) -> bool {
180
match *self {
181
CoordinateSpaceMapping::Local |
182
CoordinateSpaceMapping::ScaleOffset(_) => false,
183
CoordinateSpaceMapping::Transform(ref transform) => transform.has_perspective_component(),
184
}
185
}
186
187
pub fn is_2d_axis_aligned(&self) -> bool {
188
match *self {
189
CoordinateSpaceMapping::Local |
190
CoordinateSpaceMapping::ScaleOffset(_) => true,
191
CoordinateSpaceMapping::Transform(ref transform) => transform.preserves_2d_axis_alignment(),
192
}
193
}
194
195
pub fn scale_factors(&self) -> (f32, f32) {
196
match *self {
197
CoordinateSpaceMapping::Local => (1.0, 1.0),
198
CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => (scale_offset.scale.x, scale_offset.scale.y),
199
CoordinateSpaceMapping::Transform(ref transform) => scale_factors(transform),
200
}
201
}
202
203
pub fn inverse(&self) -> Option<CoordinateSpaceMapping<Dst, Src>> {
204
match *self {
205
CoordinateSpaceMapping::Local => Some(CoordinateSpaceMapping::Local),
206
CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => {
207
Some(CoordinateSpaceMapping::ScaleOffset(scale_offset.inverse()))
208
}
209
CoordinateSpaceMapping::Transform(ref transform) => {
210
transform.inverse().map(CoordinateSpaceMapping::Transform)
211
}
212
}
213
}
214
}
215
216
enum TransformScroll {
217
Scrolled,
218
Unscrolled,
219
}
220
221
impl SpatialTree {
222
pub fn new() -> Self {
223
SpatialTree {
224
spatial_nodes: Vec::new(),
225
coord_systems: Vec::new(),
226
pending_scroll_offsets: FastHashMap::default(),
227
pipelines_to_discard: FastHashSet::default(),
228
nodes_to_update: Vec::new(),
229
}
230
}
231
232
/// Calculate the accumulated external scroll offset for
233
/// a given spatial node.
234
pub fn external_scroll_offset(&self, node_index: SpatialNodeIndex) -> LayoutVector2D {
235
let mut offset = LayoutVector2D::zero();
236
let mut current_node = Some(node_index);
237
238
while let Some(node_index) = current_node {
239
let node = &self.spatial_nodes[node_index.0 as usize];
240
241
match node.node_type {
242
SpatialNodeType::ScrollFrame(ref scrolling) => {
243
offset += scrolling.external_scroll_offset;
244
}
245
SpatialNodeType::StickyFrame(..) => {
246
// Doesn't provide any external scroll offset
247
}
248
SpatialNodeType::ReferenceFrame(..) => {
249
// External scroll offsets are not propagated across
250
// reference frames.
251
break;
252
}
253
}
254
255
current_node = node.parent;
256
}
257
258
offset
259
}
260
261
/// Calculate the relative transform from `child_index` to `parent_index`.
262
/// This method will panic if the nodes are not connected!
263
pub fn get_relative_transform(
264
&self,
265
child_index: SpatialNodeIndex,
266
parent_index: SpatialNodeIndex,
267
) -> CoordinateSpaceMapping<LayoutPixel, LayoutPixel> {
268
if child_index == parent_index {
269
return CoordinateSpaceMapping::Local;
270
}
271
272
let child = &self.spatial_nodes[child_index.0 as usize];
273
let parent = &self.spatial_nodes[parent_index.0 as usize];
274
275
if child.coordinate_system_id == parent.coordinate_system_id {
276
let scale_offset = parent.content_transform
277
.inverse()
278
.accumulate(&child.content_transform);
279
return CoordinateSpaceMapping::ScaleOffset(scale_offset);
280
}
281
282
if child_index.0 < parent_index.0 {
283
warn!("Unexpected transform queried from {:?} to {:?}, please call the graphics team!", child_index, parent_index);
284
let child_cs = &self.coord_systems[child.coordinate_system_id.0 as usize];
285
let child_transform = child.content_transform
286
.to_transform::<LayoutPixel, LayoutPixel>()
287
.post_transform(&child_cs.world_transform);
288
let parent_cs = &self.coord_systems[parent.coordinate_system_id.0 as usize];
289
let parent_transform = parent.content_transform
290
.to_transform()
291
.post_transform(&parent_cs.world_transform);
292
293
let result = parent_transform
294
.inverse()
295
.unwrap_or_default()
296
.post_transform(&child_transform)
297
.with_source::<LayoutPixel>()
298
.with_destination::<LayoutPixel>();
299
return CoordinateSpaceMapping::Transform(result);
300
}
301
302
let mut coordinate_system_id = child.coordinate_system_id;
303
let mut transform = child.content_transform.to_transform();
304
305
// we need to update the associated parameters of a transform in two cases:
306
// 1) when the flattening happens, so that we don't lose that original 3D aspects
307
// 2) when we reach the end of iteration, so that our result is up to date
308
309
while coordinate_system_id != parent.coordinate_system_id {
310
let coord_system = &self.coord_systems[coordinate_system_id.0 as usize];
311
312
if coord_system.should_flatten {
313
transform.flatten_z_output();
314
}
315
316
coordinate_system_id = coord_system.parent.expect("invalid parent!");
317
transform = transform.post_transform(&coord_system.transform);
318
}
319
320
transform = transform.post_transform(
321
&parent.content_transform
322
.inverse()
323
.to_transform(),
324
);
325
326
CoordinateSpaceMapping::Transform(transform)
327
}
328
329
fn get_world_transform_impl(
330
&self,
331
index: SpatialNodeIndex,
332
scroll: TransformScroll,
333
) -> CoordinateSpaceMapping<LayoutPixel, WorldPixel> {
334
let child = &self.spatial_nodes[index.0 as usize];
335
336
if child.coordinate_system_id.0 == 0 {
337
if index == ROOT_SPATIAL_NODE_INDEX {
338
CoordinateSpaceMapping::Local
339
} else {
340
CoordinateSpaceMapping::ScaleOffset(child.content_transform)
341
}
342
} else {
343
let system = &self.coord_systems[child.coordinate_system_id.0 as usize];
344
let scale_offset = match scroll {
345
TransformScroll::Scrolled => &child.content_transform,
346
TransformScroll::Unscrolled => &child.viewport_transform,
347
};
348
let transform = scale_offset
349
.to_transform()
350
.post_transform(&system.world_transform);
351
352
CoordinateSpaceMapping::Transform(transform)
353
}
354
}
355
356
/// Calculate the relative transform from `index` to the root.
357
pub fn get_world_transform(
358
&self,
359
index: SpatialNodeIndex,
360
) -> CoordinateSpaceMapping<LayoutPixel, WorldPixel> {
361
self.get_world_transform_impl(index, TransformScroll::Scrolled)
362
}
363
364
/// Calculate the relative transform from `index` to the root.
365
/// Unlike `get_world_transform`, this variant doesn't account for the local scroll offset.
366
pub fn get_world_viewport_transform(
367
&self,
368
index: SpatialNodeIndex,
369
) -> CoordinateSpaceMapping<LayoutPixel, WorldPixel> {
370
self.get_world_transform_impl(index, TransformScroll::Unscrolled)
371
}
372
373
/// The root reference frame, which is the true root of the SpatialTree. Initially
374
/// this ID is not valid, which is indicated by ```spatial_nodes``` being empty.
375
pub fn root_reference_frame_index(&self) -> SpatialNodeIndex {
376
// TODO(mrobinson): We should eventually make this impossible to misuse.
377
debug_assert!(!self.spatial_nodes.is_empty());
378
ROOT_SPATIAL_NODE_INDEX
379
}
380
381
/// The root scroll node which is the first child of the root reference frame.
382
/// Initially this ID is not valid, which is indicated by ```spatial_nodes``` being empty.
383
pub fn topmost_scroll_node_index(&self) -> SpatialNodeIndex {
384
// TODO(mrobinson): We should eventually make this impossible to misuse.
385
debug_assert!(self.spatial_nodes.len() >= 1);
386
TOPMOST_SCROLL_NODE_INDEX
387
}
388
389
pub fn get_scroll_node_state(&self) -> Vec<ScrollNodeState> {
390
let mut result = vec![];
391
for node in &self.spatial_nodes {
392
if let SpatialNodeType::ScrollFrame(info) = node.node_type {
393
if let Some(id) = info.external_id {
394
result.push(ScrollNodeState {
395
id,
396
scroll_offset: info.offset - info.external_scroll_offset,
397
})
398
}
399
}
400
}
401
result
402
}
403
404
pub fn drain(&mut self) -> ScrollStates {
405
let mut scroll_states = FastHashMap::default();
406
for old_node in &mut self.spatial_nodes.drain(..) {
407
if self.pipelines_to_discard.contains(&old_node.pipeline_id) {
408
continue;
409
}
410
411
match old_node.node_type {
412
SpatialNodeType::ScrollFrame(info) if info.external_id.is_some() => {
413
scroll_states.insert(info.external_id.unwrap(), info);
414
}
415
_ => {}
416
}
417
}
418
419
self.coord_systems.clear();
420
self.pipelines_to_discard.clear();
421
scroll_states
422
}
423
424
pub fn scroll_node(
425
&mut self,
426
origin: LayoutPoint,
427
id: ExternalScrollId,
428
clamp: ScrollClamping
429
) -> bool {
430
for node in &mut self.spatial_nodes {
431
if node.matches_external_id(id) {
432
return node.set_scroll_origin(&origin, clamp);
433
}
434
}
435
436
self.pending_scroll_offsets.insert(id, (origin, clamp));
437
false
438
}
439
440
fn find_nearest_scrolling_ancestor(
441
&self,
442
index: Option<SpatialNodeIndex>
443
) -> SpatialNodeIndex {
444
let index = match index {
445
Some(index) => index,
446
None => return self.topmost_scroll_node_index(),
447
};
448
449
let node = &self.spatial_nodes[index.0 as usize];
450
match node.node_type {
451
SpatialNodeType::ScrollFrame(state) if state.sensitive_to_input_events() => index,
452
_ => self.find_nearest_scrolling_ancestor(node.parent)
453
}
454
}
455
456
pub fn scroll_nearest_scrolling_ancestor(
457
&mut self,
458
scroll_location: ScrollLocation,
459
node_index: Option<SpatialNodeIndex>,
460
) -> bool {
461
if self.spatial_nodes.is_empty() {
462
return false;
463
}
464
let node_index = self.find_nearest_scrolling_ancestor(node_index);
465
self.spatial_nodes[node_index.0 as usize].scroll(scroll_location)
466
}
467
468
pub fn update_tree(
469
&mut self,
470
pan: WorldPoint,
471
global_device_pixel_scale: DevicePixelScale,
472
scene_properties: &SceneProperties,
473
) {
474
if self.spatial_nodes.is_empty() {
475
return;
476
}
477
478
self.coord_systems.clear();
479
self.coord_systems.push(CoordinateSystem::root());
480
481
let root_node_index = self.root_reference_frame_index();
482
let state = TransformUpdateState {
483
parent_reference_frame_transform: LayoutVector2D::new(pan.x, pan.y).into(),
484
parent_accumulated_scroll_offset: LayoutVector2D::zero(),
485
nearest_scrolling_ancestor_offset: LayoutVector2D::zero(),
486
nearest_scrolling_ancestor_viewport: LayoutRect::zero(),
487
current_coordinate_system_id: CoordinateSystemId::root(),
488
coordinate_system_relative_scale_offset: ScaleOffset::identity(),
489
invertible: true,
490
preserves_3d: false,
491
};
492
debug_assert!(self.nodes_to_update.is_empty());
493
self.nodes_to_update.push((root_node_index, state));
494
495
while let Some((node_index, mut state)) = self.nodes_to_update.pop() {
496
let (previous, following) = self.spatial_nodes.split_at_mut(node_index.0 as usize);
497
let node = match following.get_mut(0) {
498
Some(node) => node,
499
None => continue,
500
};
501
502
node.update(&mut state, &mut self.coord_systems, global_device_pixel_scale, scene_properties, &*previous);
503
504
if !node.children.is_empty() {
505
node.prepare_state_for_children(&mut state);
506
self.nodes_to_update.extend(node.children
507
.iter()
508
.rev()
509
.map(|child_index| (*child_index, state.clone()))
510
);
511
}
512
}
513
}
514
515
pub fn build_transform_palette(&self) -> TransformPalette {
516
let mut palette = TransformPalette::new(self.spatial_nodes.len());
517
//Note: getting the world transform of a node is O(1) operation
518
for i in 0 .. self.spatial_nodes.len() {
519
let index = SpatialNodeIndex(i as u32);
520
let world_transform = self.get_world_transform(index).into_transform();
521
palette.set_world_transform(index, world_transform);
522
}
523
palette
524
}
525
526
pub fn finalize_and_apply_pending_scroll_offsets(&mut self, old_states: ScrollStates) {
527
for node in &mut self.spatial_nodes {
528
let external_id = match node.node_type {
529
SpatialNodeType::ScrollFrame(ScrollFrameInfo { external_id: Some(id), ..} ) => id,
530
_ => continue,
531
};
532
533
if let Some(scrolling_state) = old_states.get(&external_id) {
534
node.apply_old_scrolling_state(scrolling_state);
535
}
536
537
if let Some((offset, clamping)) = self.pending_scroll_offsets.remove(&external_id) {
538
node.set_scroll_origin(&offset, clamping);
539
}
540
}
541
}
542
543
pub fn add_scroll_frame(
544
&mut self,
545
parent_index: SpatialNodeIndex,
546
external_id: Option<ExternalScrollId>,
547
pipeline_id: PipelineId,
548
frame_rect: &LayoutRect,
549
content_size: &LayoutSize,
550
scroll_sensitivity: ScrollSensitivity,
551
frame_kind: ScrollFrameKind,
552
external_scroll_offset: LayoutVector2D,
553
) -> SpatialNodeIndex {
554
let node = SpatialNode::new_scroll_frame(
555
pipeline_id,
556
parent_index,
557
external_id,
558
frame_rect,
559
content_size,
560
scroll_sensitivity,
561
frame_kind,
562
external_scroll_offset,
563
);
564
self.add_spatial_node(node)
565
}
566
567
pub fn add_reference_frame(
568
&mut self,
569
parent_index: Option<SpatialNodeIndex>,
570
transform_style: TransformStyle,
571
source_transform: PropertyBinding<LayoutTransform>,
572
kind: ReferenceFrameKind,
573
origin_in_parent_reference_frame: LayoutVector2D,
574
pipeline_id: PipelineId,
575
) -> SpatialNodeIndex {
576
let node = SpatialNode::new_reference_frame(
577
parent_index,
578
transform_style,
579
source_transform,
580
kind,
581
origin_in_parent_reference_frame,
582
pipeline_id,
583
);
584
self.add_spatial_node(node)
585
}
586
587
pub fn add_sticky_frame(
588
&mut self,
589
parent_index: SpatialNodeIndex,
590
sticky_frame_info: StickyFrameInfo,
591
pipeline_id: PipelineId,
592
) -> SpatialNodeIndex {
593
let node = SpatialNode::new_sticky_frame(
594
parent_index,
595
sticky_frame_info,
596
pipeline_id,
597
);
598
self.add_spatial_node(node)
599
}
600
601
pub fn add_spatial_node(&mut self, mut node: SpatialNode) -> SpatialNodeIndex {
602
let index = SpatialNodeIndex::new(self.spatial_nodes.len());
603
604
// When the parent node is None this means we are adding the root.
605
if let Some(parent_index) = node.parent {
606
let parent_node = &mut self.spatial_nodes[parent_index.0 as usize];
607
parent_node.add_child(index);
608
node.update_snapping(Some(parent_node));
609
} else {
610
node.update_snapping(None);
611
}
612
613
self.spatial_nodes.push(node);
614
index
615
}
616
617
pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
618
self.pipelines_to_discard.insert(pipeline_id);
619
}
620
621
/// Find the spatial node that is the scroll root for a given spatial node.
622
/// A scroll root is the first spatial node when found travelling up the
623
/// spatial node tree that is an explicit scroll frame.
624
pub fn find_scroll_root(
625
&self,
626
spatial_node_index: SpatialNodeIndex,
627
) -> SpatialNodeIndex {
628
let mut scroll_root = ROOT_SPATIAL_NODE_INDEX;
629
let mut node_index = spatial_node_index;
630
631
while node_index != ROOT_SPATIAL_NODE_INDEX {
632
let node = &self.spatial_nodes[node_index.0 as usize];
633
match node.node_type {
634
SpatialNodeType::ReferenceFrame(..) |
635
SpatialNodeType::StickyFrame(..) => {
636
// TODO(gw): In future, we may need to consider sticky frames.
637
}
638
SpatialNodeType::ScrollFrame(ref info) => {
639
// If we found an explicit scroll root, store that
640
// and keep looking up the tree.
641
if let ScrollFrameKind::Explicit = info.frame_kind {
642
scroll_root = node_index;
643
}
644
}
645
}
646
node_index = node.parent.expect("unable to find parent node");
647
}
648
649
scroll_root
650
}
651
652
fn print_node<T: PrintTreePrinter>(
653
&self,
654
index: SpatialNodeIndex,
655
pt: &mut T,
656
) {
657
let node = &self.spatial_nodes[index.0 as usize];
658
match node.node_type {
659
SpatialNodeType::StickyFrame(ref sticky_frame_info) => {
660
pt.new_level(format!("StickyFrame"));
661
pt.add_item(format!("sticky info: {:?}", sticky_frame_info));
662
}
663
SpatialNodeType::ScrollFrame(scrolling_info) => {
664
pt.new_level(format!("ScrollFrame"));
665
pt.add_item(format!("viewport: {:?}", scrolling_info.viewport_rect));
666
pt.add_item(format!("scrollable_size: {:?}", scrolling_info.scrollable_size));
667
pt.add_item(format!("scroll offset: {:?}", scrolling_info.offset));
668
pt.add_item(format!("external_scroll_offset: {:?}", scrolling_info.external_scroll_offset));
669
pt.add_item(format!("kind: {:?}", scrolling_info.frame_kind));
670
}
671
SpatialNodeType::ReferenceFrame(ref info) => {
672
pt.new_level(format!("ReferenceFrame"));
673
pt.add_item(format!("kind: {:?}", info.kind));
674
pt.add_item(format!("transform_style: {:?}", info.transform_style));
675
pt.add_item(format!("source_transform: {:?}", info.source_transform));
676
pt.add_item(format!("origin_in_parent_reference_frame: {:?}", info.origin_in_parent_reference_frame));
677
}
678
}
679
680
pt.add_item(format!("index: {:?}", index));
681
pt.add_item(format!("content_transform: {:?}", node.content_transform));
682
pt.add_item(format!("viewport_transform: {:?}", node.viewport_transform));
683
pt.add_item(format!("snapping_transform: {:?}", node.snapping_transform));
684
pt.add_item(format!("coordinate_system_id: {:?}", node.coordinate_system_id));
685
686
for child_index in &node.children {
687
self.print_node(*child_index, pt);
688
}
689
690
pt.end_level();
691
}
692
693
/// Get the visible face of the transfrom from the specified node to its parent.
694
pub fn get_local_visible_face(&self, node_index: SpatialNodeIndex) -> VisibleFace {
695
let node = &self.spatial_nodes[node_index.0 as usize];
696
let parent_index = match node.parent {
697
Some(index) => index,
698
None => return VisibleFace::Front
699
};
700
self.get_relative_transform(node_index, parent_index)
701
.visible_face()
702
}
703
704
#[allow(dead_code)]
705
pub fn print(&self) {
706
if !self.spatial_nodes.is_empty() {
707
let mut pt = PrintTree::new("spatial tree");
708
self.print_with(&mut pt);
709
}
710
}
711
}
712
713
impl PrintableTree for SpatialTree {
714
fn print_with<T: PrintTreePrinter>(&self, pt: &mut T) {
715
if !self.spatial_nodes.is_empty() {
716
self.print_node(self.root_reference_frame_index(), pt);
717
}
718
}
719
}
720
721
#[cfg(test)]
722
fn add_reference_frame(
723
cst: &mut SpatialTree,
724
parent: Option<SpatialNodeIndex>,
725
transform: LayoutTransform,
726
origin_in_parent_reference_frame: LayoutVector2D,
727
) -> SpatialNodeIndex {
728
cst.add_reference_frame(
729
parent,
730
TransformStyle::Preserve3D,
731
PropertyBinding::Value(transform),
732
ReferenceFrameKind::Transform,
733
origin_in_parent_reference_frame,
734
PipelineId::dummy(),
735
)
736
}
737
738
#[cfg(test)]
739
fn test_pt(
740
px: f32,
741
py: f32,
742
cst: &SpatialTree,
743
child: SpatialNodeIndex,
744
parent: SpatialNodeIndex,
745
expected_x: f32,
746
expected_y: f32,
747
) {
748
use euclid::approxeq::ApproxEq;
749
const EPSILON: f32 = 0.0001;
750
751
let p = LayoutPoint::new(px, py);
752
let m = cst.get_relative_transform(child, parent).into_transform();
753
let pt = m.transform_point2d(p).unwrap();
754
assert!(pt.x.approx_eq_eps(&expected_x, &EPSILON) &&
755
pt.y.approx_eq_eps(&expected_y, &EPSILON),
756
"p: {:?} -> {:?}\nm={:?}",
757
p, pt, m,
758
);
759
}
760
761
#[test]
762
fn test_cst_simple_translation() {
763
// Basic translations only
764
765
let mut cst = SpatialTree::new();
766
767
let root = add_reference_frame(
768
&mut cst,
769
None,
770
LayoutTransform::identity(),
771
LayoutVector2D::zero(),
772
);
773
774
let child1 = add_reference_frame(
775
&mut cst,
776
Some(root),
777
LayoutTransform::create_translation(100.0, 0.0, 0.0),
778
LayoutVector2D::zero(),
779
);
780
781
let child2 = add_reference_frame(
782
&mut cst,
783
Some(child1),
784
LayoutTransform::create_translation(0.0, 50.0, 0.0),
785
LayoutVector2D::zero(),
786
);
787
788
let child3 = add_reference_frame(
789
&mut cst,
790
Some(child2),
791
LayoutTransform::create_translation(200.0, 200.0, 0.0),
792
LayoutVector2D::zero(),
793
);
794
795
cst.update_tree(WorldPoint::zero(), DevicePixelScale::new(1.0), &SceneProperties::new());
796
797
test_pt(100.0, 100.0, &cst, child1, root, 200.0, 100.0);
798
test_pt(100.0, 100.0, &cst, child2, root, 200.0, 150.0);
799
test_pt(100.0, 100.0, &cst, child2, child1, 100.0, 150.0);
800
test_pt(100.0, 100.0, &cst, child3, root, 400.0, 350.0);
801
}
802
803
#[test]
804
fn test_cst_simple_scale() {
805
// Basic scale only
806
807
let mut cst = SpatialTree::new();
808
809
let root = add_reference_frame(
810
&mut cst,
811
None,
812
LayoutTransform::identity(),
813
LayoutVector2D::zero(),
814
);
815
816
let child1 = add_reference_frame(
817
&mut cst,
818
Some(root),
819
LayoutTransform::create_scale(4.0, 1.0, 1.0),
820
LayoutVector2D::zero(),
821
);
822
823
let child2 = add_reference_frame(
824
&mut cst,
825
Some(child1),
826
LayoutTransform::create_scale(1.0, 2.0, 1.0),
827
LayoutVector2D::zero(),
828
);
829
830
let child3 = add_reference_frame(
831
&mut cst,
832
Some(child2),
833
LayoutTransform::create_scale(2.0, 2.0, 1.0),
834
LayoutVector2D::zero(),
835
);
836
837
cst.update_tree(WorldPoint::zero(), DevicePixelScale::new(1.0), &SceneProperties::new());
838
839
test_pt(100.0, 100.0, &cst, child1, root, 400.0, 100.0);
840
test_pt(100.0, 100.0, &cst, child2, root, 400.0, 200.0);
841
test_pt(100.0, 100.0, &cst, child3, root, 800.0, 400.0);
842
test_pt(100.0, 100.0, &cst, child2, child1, 100.0, 200.0);
843
test_pt(100.0, 100.0, &cst, child3, child1, 200.0, 400.0);
844
}
845
846
#[test]
847
fn test_cst_scale_translation() {
848
// Scale + translation
849
850
let mut cst = SpatialTree::new();
851
852
let root = add_reference_frame(
853
&mut cst,
854
None,
855
LayoutTransform::identity(),
856
LayoutVector2D::zero(),
857
);
858
859
let child1 = add_reference_frame(
860
&mut cst,
861
Some(root),
862
LayoutTransform::create_translation(100.0, 50.0, 0.0),
863
LayoutVector2D::zero(),
864
);
865
866
let child2 = add_reference_frame(
867
&mut cst,
868
Some(child1),
869
LayoutTransform::create_scale(2.0, 4.0, 1.0),
870
LayoutVector2D::zero(),
871
);
872
873
let child3 = add_reference_frame(
874
&mut cst,
875
Some(child2),
876
LayoutTransform::create_translation(200.0, -100.0, 0.0),
877
LayoutVector2D::zero(),
878
);
879
880
let child4 = add_reference_frame(
881
&mut cst,
882
Some(child3),
883
LayoutTransform::create_scale(3.0, 2.0, 1.0),
884
LayoutVector2D::zero(),
885
);
886
887
cst.update_tree(WorldPoint::zero(), DevicePixelScale::new(1.0), &SceneProperties::new());
888
889
test_pt(100.0, 100.0, &cst, child1, root, 200.0, 150.0);
890
test_pt(100.0, 100.0, &cst, child2, root, 300.0, 450.0);
891
test_pt(100.0, 100.0, &cst, child4, root, 1100.0, 450.0);
892
893
test_pt(0.0, 0.0, &cst, child4, child1, 400.0, -400.0);
894
test_pt(100.0, 100.0, &cst, child4, child1, 1000.0, 400.0);
895
test_pt(100.0, 100.0, &cst, child2, child1, 200.0, 400.0);
896
897
test_pt(100.0, 100.0, &cst, child3, child1, 600.0, 0.0);
898
}
899
900
#[test]
901
fn test_cst_translation_rotate() {
902
// Rotation + translation
903
use euclid::Angle;
904
905
let mut cst = SpatialTree::new();
906
907
let root = add_reference_frame(
908
&mut cst,
909
None,
910
LayoutTransform::identity(),
911
LayoutVector2D::zero(),
912
);
913
914
let child1 = add_reference_frame(
915
&mut cst,
916
Some(root),
917
LayoutTransform::create_rotation(0.0, 0.0, 1.0, Angle::degrees(90.0)),
918
LayoutVector2D::zero(),
919
);
920
921
cst.update_tree(WorldPoint::zero(), DevicePixelScale::new(1.0), &SceneProperties::new());
922
923
test_pt(100.0, 0.0, &cst, child1, root, 0.0, -100.0);
924
}