1use std::collections::VecDeque;
4
5use glam::Vec2;
6use thunderdome::Arena;
7
8use crate::dom::Dom;
9use crate::event::EventInterest;
10use crate::geometry::{Constraints, Rect};
11use crate::id::WidgetId;
12use crate::input::{InputState, MouseInterest};
13use crate::widget::LayoutContext;
14
15#[derive(Debug)]
18pub struct LayoutDom {
19 nodes: Arena<LayoutDomNode>,
20 clip_stack: Vec<WidgetId>,
21
22 unscaled_viewport: Rect,
23 scale_factor: f32,
24
25 pub(crate) interest_mouse: MouseInterest,
26}
27
28#[derive(Debug)]
30#[non_exhaustive]
31pub struct LayoutDomNode {
32 pub rect: Rect,
34
35 pub clipping_enabled: bool,
37
38 pub new_layer: bool,
41
42 pub clipped_by: Option<WidgetId>,
44
45 pub event_interest: EventInterest,
47}
48
49impl LayoutDom {
50 pub fn new() -> Self {
52 Self {
53 nodes: Arena::new(),
54 clip_stack: Vec::new(),
55
56 unscaled_viewport: Rect::ONE,
57 scale_factor: 1.0,
58
59 interest_mouse: MouseInterest::new(),
60 }
61 }
62
63 pub(crate) fn sync_removals(&mut self, removals: &[WidgetId]) {
64 for id in removals {
65 self.nodes.remove(id.index());
66 }
67 }
68
69 pub fn get(&self, id: WidgetId) -> Option<&LayoutDomNode> {
71 self.nodes.get(id.index())
72 }
73
74 pub fn get_mut(&mut self, id: WidgetId) -> Option<&mut LayoutDomNode> {
76 self.nodes.get_mut(id.index())
77 }
78
79 pub fn set_unscaled_viewport(&mut self, view: Rect) {
81 self.unscaled_viewport = view;
82 }
83
84 pub fn set_scale_factor(&mut self, scale: f32) {
86 self.scale_factor = scale;
87 }
88
89 pub fn scale_factor(&self) -> f32 {
91 self.scale_factor
92 }
93
94 pub fn viewport(&self) -> Rect {
96 Rect::from_pos_size(
97 self.unscaled_viewport.pos() / self.scale_factor,
98 self.unscaled_viewport.size() / self.scale_factor,
99 )
100 }
101
102 pub fn unscaled_viewport(&self) -> Rect {
104 self.unscaled_viewport
105 }
106
107 pub fn len(&self) -> usize {
109 self.nodes.len()
110 }
111
112 pub fn is_empty(&self) -> bool {
114 self.nodes.is_empty()
115 }
116
117 pub fn calculate_all(&mut self, dom: &Dom, input: &InputState) {
119 profiling::scope!("LayoutDom::calculate_all");
120 log::debug!("LayoutDom::calculate_all()");
121
122 self.clip_stack.clear();
123 self.interest_mouse.clear();
124
125 let constraints = Constraints::tight(self.viewport().size());
126
127 self.calculate(dom, input, dom.root(), constraints);
128 self.resolve_positions(dom);
129 }
130
131 pub fn calculate(
137 &mut self,
138 dom: &Dom,
139 input: &InputState,
140 id: WidgetId,
141 constraints: Constraints,
142 ) -> Vec2 {
143 dom.enter(id);
144 let dom_node = dom.get(id).unwrap();
145
146 let context = LayoutContext {
147 dom,
148 input,
149 layout: self,
150 };
151
152 let size = dom_node.widget.layout(context, constraints);
153
154 let new_layer = self.interest_mouse.current_layer_root() == Some(id);
157
158 let event_interest = dom_node.widget.event_interest();
161 if event_interest.intersects(EventInterest::MOUSE_ALL) {
162 self.interest_mouse.insert(id, event_interest);
163 }
164
165 if new_layer {
168 self.interest_mouse.pop_layer();
169 }
170
171 let clipping_enabled = self.clip_stack.last() == Some(&id);
174
175 let clipped_by = if clipping_enabled {
178 self.clip_stack.iter().nth_back(2).copied()
179 } else {
180 self.clip_stack.last().copied()
181 };
182
183 self.nodes.insert_at(
184 id.index(),
185 LayoutDomNode {
186 rect: Rect::from_pos_size(Vec2::ZERO, size),
187 clipping_enabled,
188 new_layer,
189 clipped_by,
190 event_interest,
191 },
192 );
193
194 if clipping_enabled {
195 self.clip_stack.pop();
196 }
197
198 dom.exit(id);
199 size
200 }
201
202 pub fn enable_clipping(&mut self, dom: &Dom) {
204 self.clip_stack.push(dom.current());
205 }
206
207 pub fn new_layer(&mut self, dom: &Dom) {
209 self.interest_mouse.push_layer(dom.current());
210 }
211
212 pub fn set_pos(&mut self, id: WidgetId, pos: Vec2) {
214 if let Some(node) = self.nodes.get_mut(id.index()) {
215 node.rect.set_pos(pos);
216 }
217 }
218
219 fn resolve_positions(&mut self, dom: &Dom) {
220 let mut queue = VecDeque::new();
221
222 queue.push_back((dom.root(), Vec2::ZERO));
223
224 while let Some((id, parent_pos)) = queue.pop_front() {
225 if let Some(layout_node) = self.nodes.get_mut(id.index()) {
226 let node = dom.get(id).unwrap();
227 layout_node
228 .rect
229 .set_pos(layout_node.rect.pos() + parent_pos);
230
231 queue.extend(node.children.iter().map(|&id| (id, layout_node.rect.pos())));
232 }
233 }
234 }
235}