kozan_core/compositor/
mod.rs1pub mod frame;
22pub(crate) mod layer;
23pub(crate) mod layer_builder;
24pub mod layer_tree;
25
26use std::sync::Arc;
27
28use kozan_primitives::geometry::{Offset, Point};
29
30use crate::paint::DisplayList;
31use crate::scroll::{ScrollController, ScrollOffsets, ScrollTree};
32
33use self::frame::CompositorFrame;
34use self::layer_tree::LayerTree;
35
36pub struct Compositor {
42 display_list: Option<Arc<DisplayList>>,
43 layer_tree: Option<LayerTree>,
44 scroll_tree: ScrollTree,
45 scroll_offsets: ScrollOffsets,
46}
47
48impl Compositor {
49 pub fn new() -> Self {
50 Self {
51 display_list: None,
52 layer_tree: None,
53 scroll_tree: ScrollTree::new(),
54 scroll_offsets: ScrollOffsets::new(),
55 }
56 }
57
58 pub fn commit(
66 &mut self,
67 display_list: Arc<DisplayList>,
68 layer_tree: LayerTree,
69 scroll_tree: ScrollTree,
70 ) {
71 self.display_list = Some(display_list);
72 self.layer_tree = Some(layer_tree);
73 self.scroll_tree = scroll_tree;
74 }
75
76 pub fn try_scroll(&mut self, target: u32, delta: Offset) -> bool {
80 !ScrollController::new(&self.scroll_tree, &mut self.scroll_offsets)
81 .scroll(target, delta)
82 .is_empty()
83 }
84
85 pub fn produce_frame(&self) -> Option<CompositorFrame> {
90 let display_list = self.display_list.as_ref()?;
91 Some(CompositorFrame {
92 display_list: Arc::clone(display_list),
93 scroll_offsets: self.scroll_offsets.clone(),
94 })
95 }
96
97 pub fn scroll_offsets(&self) -> &ScrollOffsets {
99 &self.scroll_offsets
100 }
101
102 pub fn scroll_tree(&self) -> &ScrollTree {
103 &self.scroll_tree
104 }
105
106 pub fn has_content(&self) -> bool {
107 self.display_list.is_some()
108 }
109
110 pub fn hit_test_scroll_target(&self, point: Point) -> Option<u32> {
120 let tree = self.layer_tree.as_ref()?;
121 let root = tree.root()?;
122
123 let mut best: Option<u32> = None;
124 self.hit_test_layer(tree, root, point, &mut best);
125
126 best.or_else(|| self.scroll_tree.root_scroller())
127 }
128
129 fn hit_test_layer(
130 &self,
131 tree: &LayerTree,
132 layer_id: layer::LayerId,
133 point: Point,
134 best: &mut Option<u32>,
135 ) {
136 let layer = tree.layer(layer_id);
137
138 if !layer.bounds.contains_point(point) {
139 return;
140 }
141
142 if layer.is_scrollable {
144 if let Some(dom_id) = layer.dom_node {
145 if self.scroll_tree.contains(dom_id) {
146 *best = Some(dom_id);
147 }
148 }
149 }
150
151 for &child_id in &layer.children {
153 self.hit_test_layer(tree, child_id, point, best);
154 }
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use crate::paint::display_list::DisplayListBuilder;
162 use crate::scroll::node::ScrollNode;
163 use kozan_primitives::geometry::Size;
164
165 fn _assert_send<T: Send>() {}
166 fn _assert_sync<T: Sync>() {}
167 #[test]
168 fn send_sync_bounds() {
169 _assert_send::<Compositor>();
170 _assert_send::<CompositorFrame>();
171 _assert_send::<LayerTree>();
172 _assert_sync::<CompositorFrame>();
173 }
174
175 fn empty_display_list() -> Arc<DisplayList> {
176 Arc::new(DisplayListBuilder::new().finish())
177 }
178
179 fn test_scroll_state() -> (ScrollTree, ScrollOffsets) {
180 let mut tree = ScrollTree::new();
181 tree.set(
182 1,
183 ScrollNode {
184 dom_id: 1,
185 parent: None,
186 container: Size::new(800.0, 600.0),
187 content: Size::new(800.0, 2000.0),
188 scrollable_x: false,
189 scrollable_y: true,
190 },
191 );
192 let mut offsets = ScrollOffsets::new();
193 offsets.set_offset(1, Offset::ZERO);
194 (tree, offsets)
195 }
196
197 #[test]
198 fn no_content_before_commit() {
199 let c = Compositor::new();
200 assert!(!c.has_content());
201 assert!(c.produce_frame().is_none());
202 }
203
204 #[test]
205 fn commit_updates_display_list_and_tree() {
206 let mut c = Compositor::new();
207 let dl = empty_display_list();
208 let (tree, _offsets) = test_scroll_state();
209 c.commit(Arc::clone(&dl), LayerTree::new(), tree);
210 assert!(c.has_content());
211 assert!(Arc::ptr_eq(
212 &c.produce_frame().expect("frame").display_list,
213 &dl
214 ));
215 }
216
217 #[test]
218 fn commit_does_not_overwrite_compositor_scroll_offsets() {
219 let mut c = Compositor::new();
220 let (tree, _offsets) = test_scroll_state();
221 c.commit(empty_display_list(), LayerTree::new(), tree);
222
223 c.try_scroll(1, Offset::new(0.0, 120.0));
225 assert_eq!(c.scroll_offsets().offset(1).dy, 120.0);
226
227 let (tree2, _offsets2) = test_scroll_state();
229 c.commit(empty_display_list(), LayerTree::new(), tree2);
230 assert_eq!(c.scroll_offsets().offset(1).dy, 120.0);
231 }
232
233 #[test]
234 fn try_scroll_updates_offsets() {
235 let mut c = Compositor::new();
236 let (tree, _offsets) = test_scroll_state();
237 c.commit(empty_display_list(), LayerTree::new(), tree);
238
239 assert!(c.try_scroll(1, Offset::new(0.0, 100.0)));
240 assert_eq!(c.scroll_offsets().offset(1).dy, 100.0);
241 }
242
243 #[test]
244 fn try_scroll_clamps() {
245 let mut c = Compositor::new();
246 let (tree, _offsets) = test_scroll_state();
247 c.commit(empty_display_list(), LayerTree::new(), tree);
248 c.try_scroll(1, Offset::new(0.0, 99999.0));
249 assert_eq!(c.scroll_offsets().offset(1).dy, 1400.0);
250 }
251
252 #[test]
253 fn try_scroll_unknown_node() {
254 let mut c = Compositor::new();
255 let (tree, _offsets) = test_scroll_state();
256 c.commit(empty_display_list(), LayerTree::new(), tree);
257 assert!(!c.try_scroll(99, Offset::new(0.0, 100.0)));
258 }
259
260 #[test]
261 fn produce_frame_has_scroll_offsets() {
262 let mut c = Compositor::new();
263 let (tree, _offsets) = test_scroll_state();
264 c.commit(empty_display_list(), LayerTree::new(), tree);
265 c.try_scroll(1, Offset::new(0.0, 200.0));
266
267 let frame = c.produce_frame().expect("frame");
268 assert_eq!(frame.scroll_offsets.offset(1).dy, 200.0);
269 }
270}