kozan_core/scroll/
tree.rs1use kozan_primitives::arena::Storage;
7use kozan_primitives::geometry::Size;
8
9use crate::layout::fragment::{Fragment, FragmentKind};
10
11use super::node::ScrollNode;
12
13#[derive(Clone)]
18pub struct ScrollTree {
19 nodes: Storage<ScrollNode>,
20 root: Option<u32>,
21}
22
23impl ScrollTree {
24 pub fn new() -> Self {
25 Self {
26 nodes: Storage::new(),
27 root: None,
28 }
29 }
30
31 pub fn set(&mut self, dom_id: u32, node: ScrollNode) {
32 if node.parent.is_none() {
33 self.root = Some(dom_id);
34 }
35 self.nodes.set(dom_id, node);
36 }
37
38 pub fn get(&self, dom_id: u32) -> Option<&ScrollNode> {
39 self.nodes.get(dom_id)
40 }
41
42 pub fn clear(&mut self) {
44 self.nodes.clear();
45 self.root = None;
46 }
47
48 pub fn contains(&self, dom_id: u32) -> bool {
49 self.nodes.get(dom_id).is_some()
50 }
51
52 pub fn root_scroller(&self) -> Option<u32> {
54 self.root
55 }
56
57 pub fn chain(&self, start: u32) -> ScrollChain<'_> {
59 ScrollChain {
60 nodes: &self.nodes,
61 current: Some(start),
62 }
63 }
64
65 pub fn sync(&mut self, root: &Fragment) {
70 self.clear();
71 self.sync_recursive(root, None);
72 }
73
74 fn sync_recursive(&mut self, fragment: &Fragment, parent_scroll: Option<u32>) {
75 let FragmentKind::Box(ref data) = fragment.kind else {
76 return;
77 };
78
79 let dom_id = fragment.dom_node;
80 let scrollable_x = data.overflow_x.is_user_scrollable();
81 let scrollable_y = data.overflow_y.is_user_scrollable();
82 let is_scrollable = scrollable_x || scrollable_y;
83
84 let container = Size::new(
86 (fragment.size.width - data.border.left - data.border.right).max(0.0),
87 (fragment.size.height - data.border.top - data.border.bottom).max(0.0),
88 );
89
90 let next_parent = if is_scrollable {
91 if let Some(id) = dom_id {
92 let content = Size::new(
96 (data.scrollable_overflow.width - data.border.left).max(0.0),
97 (data.scrollable_overflow.height - data.border.top).max(0.0),
98 );
99 self.set(
100 id,
101 ScrollNode {
102 dom_id: id,
103 parent: parent_scroll,
104 container,
105 content,
106 scrollable_x,
107 scrollable_y,
108 },
109 );
110 Some(id)
111 } else {
112 parent_scroll
113 }
114 } else {
115 parent_scroll
116 };
117
118 for child in &data.children {
119 self.sync_recursive(&child.fragment, next_parent);
120 }
121 }
122}
123
124pub struct ScrollChain<'a> {
126 nodes: &'a Storage<ScrollNode>,
127 current: Option<u32>,
128}
129
130impl Iterator for ScrollChain<'_> {
131 type Item = u32;
132
133 fn next(&mut self) -> Option<u32> {
134 let id = self.current?;
135 let node = self.nodes.get(id)?;
136 self.current = node.parent;
137 Some(id)
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use kozan_primitives::geometry::Size;
145
146 fn scrollable(id: u32, parent: Option<u32>) -> ScrollNode {
147 ScrollNode {
148 dom_id: id,
149 parent,
150 container: Size::new(200.0, 400.0),
151 content: Size::new(200.0, 1200.0),
152 scrollable_x: false,
153 scrollable_y: true,
154 }
155 }
156
157 #[test]
158 fn chain_walks_to_root() {
159 let mut tree = ScrollTree::new();
160 tree.set(1, scrollable(1, None));
161 tree.set(5, scrollable(5, Some(1)));
162 tree.set(9, scrollable(9, Some(5)));
163
164 let chain: Vec<u32> = tree.chain(9).collect();
165 assert_eq!(chain, vec![9, 5, 1]);
166 }
167
168 #[test]
169 fn chain_single_node() {
170 let mut tree = ScrollTree::new();
171 tree.set(1, scrollable(1, None));
172
173 let chain: Vec<u32> = tree.chain(1).collect();
174 assert_eq!(chain, vec![1]);
175 }
176
177 #[test]
178 fn root_scroller_returns_parentless_node() {
179 let mut tree = ScrollTree::new();
180 tree.set(1, scrollable(1, None));
181 tree.set(5, scrollable(5, Some(1)));
182 assert_eq!(tree.root_scroller(), Some(1));
183 }
184
185 #[test]
186 fn root_scroller_empty_tree() {
187 let tree = ScrollTree::new();
188 assert_eq!(tree.root_scroller(), None);
189 }
190
191 #[test]
192 fn chain_unknown_start_is_empty() {
193 let tree = ScrollTree::new();
194 let chain: Vec<u32> = tree.chain(99).collect();
195 assert!(chain.is_empty());
196 }
197}