1use alloc::collections::BTreeMap;
2use core::{
3 fmt,
4 sync::atomic::{AtomicUsize, Ordering as AtomicOrdering},
5};
6
7use crate::{
8 dom::{DomId, DomNodeHash, DomNodeId, OptionDomNodeId, ScrollTagId, ScrollbarOrientation},
9 geom::{LogicalPosition, LogicalRect, LogicalSize},
10 hit_test_tag::CursorType,
11 id::NodeId,
12 resources::IdNamespace,
13 styled_dom::NodeHierarchyItemId,
14 window::MouseCursorType,
15 FastHashMap,
16};
17
18#[derive(Debug, Clone, PartialEq, PartialOrd)]
19pub struct HitTest {
20 pub regular_hit_test_nodes: BTreeMap<NodeId, HitTestItem>,
21 pub scroll_hit_test_nodes: BTreeMap<NodeId, ScrollHitTestItem>,
22 pub scrollbar_hit_test_nodes: BTreeMap<ScrollbarHitId, ScrollbarHitTestItem>,
24 pub cursor_hit_test_nodes: BTreeMap<NodeId, CursorHitTestItem>,
27}
28
29#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
31#[repr(C)]
32pub struct CursorHitTestItem {
33 pub cursor_type: CursorType,
34 pub hit_depth: u32,
35 pub point_in_viewport: LogicalPosition,
36}
37
38impl HitTest {
39 pub fn empty() -> Self {
40 Self {
41 regular_hit_test_nodes: BTreeMap::new(),
42 scroll_hit_test_nodes: BTreeMap::new(),
43 scrollbar_hit_test_nodes: BTreeMap::new(),
44 cursor_hit_test_nodes: BTreeMap::new(),
45 }
46 }
47 pub fn is_empty(&self) -> bool {
48 self.regular_hit_test_nodes.is_empty()
49 && self.scroll_hit_test_nodes.is_empty()
50 && self.scrollbar_hit_test_nodes.is_empty()
51 && self.cursor_hit_test_nodes.is_empty()
52 }
53}
54
55#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
57#[repr(C, u8)]
58pub enum ScrollbarHitId {
59 VerticalTrack(DomId, NodeId),
60 VerticalThumb(DomId, NodeId),
61 HorizontalTrack(DomId, NodeId),
62 HorizontalThumb(DomId, NodeId),
63}
64
65#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
67#[repr(C)]
68pub struct ScrollbarHitTestItem {
69 pub point_in_viewport: LogicalPosition,
70 pub point_relative_to_item: LogicalPosition,
71 pub orientation: ScrollbarOrientation,
72}
73
74#[derive(Copy, Clone, Eq, Hash, PartialEq, Ord, PartialOrd)]
75#[repr(C)]
76pub struct ExternalScrollId(pub u64, pub PipelineId);
77
78impl ::core::fmt::Display for ExternalScrollId {
79 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80 write!(f, "ExternalScrollId({})", self.0)
81 }
82}
83
84impl ::core::fmt::Debug for ExternalScrollId {
85 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
86 write!(f, "{}", self)
87 }
88}
89
90#[derive(Debug, Default, Clone, PartialEq, PartialOrd)]
91pub struct ScrolledNodes {
92 pub overflowing_nodes: BTreeMap<NodeHierarchyItemId, OverflowingScrollNode>,
93 pub clip_nodes: BTreeMap<NodeId, LogicalSize>,
96 pub tags_to_node_ids: BTreeMap<ScrollTagId, NodeHierarchyItemId>,
97}
98
99#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
100pub struct OverflowingScrollNode {
101 pub parent_rect: LogicalRect,
102 pub child_rect: LogicalRect,
103 pub virtual_child_rect: LogicalRect,
104 pub parent_external_scroll_id: ExternalScrollId,
105 pub parent_dom_hash: DomNodeHash,
106 pub scroll_tag_id: ScrollTagId,
107}
108
109impl Default for OverflowingScrollNode {
110 fn default() -> Self {
111 use crate::dom::TagId;
112 Self {
113 parent_rect: LogicalRect::zero(),
114 child_rect: LogicalRect::zero(),
115 virtual_child_rect: LogicalRect::zero(),
116 parent_external_scroll_id: ExternalScrollId(0, PipelineId::DUMMY),
117 parent_dom_hash: DomNodeHash { inner: 0 },
118 scroll_tag_id: ScrollTagId {
119 inner: TagId { inner: 0 },
120 },
121 }
122 }
123}
124
125pub type PipelineSourceId = u32;
131
132#[derive(Debug, Clone, PartialEq, PartialOrd)]
134pub struct ScrollPosition {
135 pub parent_rect: LogicalRect,
138 pub children_rect: LogicalRect,
140}
141
142#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
143pub struct DocumentId {
144 pub namespace_id: IdNamespace,
145 pub id: u32,
146}
147
148impl ::core::fmt::Display for DocumentId {
149 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
150 write!(
151 f,
152 "DocumentId {{ ns: {}, id: {} }}",
153 self.namespace_id, self.id
154 )
155 }
156}
157
158impl ::core::fmt::Debug for DocumentId {
159 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
160 write!(f, "{}", self)
161 }
162}
163
164#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
165pub struct PipelineId(pub PipelineSourceId, pub u32);
166
167impl ::core::fmt::Display for PipelineId {
168 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
169 write!(f, "PipelineId({}, {})", self.0, self.1)
170 }
171}
172
173impl ::core::fmt::Debug for PipelineId {
174 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
175 write!(f, "{}", self)
176 }
177}
178
179static LAST_PIPELINE_ID: AtomicUsize = AtomicUsize::new(0);
180
181impl PipelineId {
182 pub const DUMMY: PipelineId = PipelineId(0, 0);
183
184 pub fn new() -> Self {
185 PipelineId(
186 LAST_PIPELINE_ID.fetch_add(1, AtomicOrdering::SeqCst) as u32,
187 0,
188 )
189 }
190}
191
192#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
193pub struct HitTestItem {
194 pub point_in_viewport: LogicalPosition,
198 pub point_relative_to_item: LogicalPosition,
201 pub is_focusable: bool,
203 pub is_iframe_hit: Option<(DomId, LogicalPosition)>,
205 pub hit_depth: u32,
209}
210
211#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
212pub struct ScrollHitTestItem {
213 pub point_in_viewport: LogicalPosition,
217 pub point_relative_to_item: LogicalPosition,
220 pub scroll_node: OverflowingScrollNode,
222}
223
224#[derive(Debug, Default)]
225pub struct ScrollStates(pub FastHashMap<ExternalScrollId, ScrollState>);
226
227impl ScrollStates {
228 #[must_use]
233 pub fn should_scroll_render(
234 &mut self,
235 (scroll_x, scroll_y): &(f32, f32),
236 hit_test: &FullHitTest,
237 ) -> bool {
238 let mut should_scroll_render = false;
239
240 for hit_test in hit_test.hovered_nodes.values() {
241 for scroll_hit_test_item in hit_test.scroll_hit_test_nodes.values() {
242 self.scroll_node(&scroll_hit_test_item.scroll_node, *scroll_x, *scroll_y);
243 should_scroll_render = true;
244 break; }
246 }
247
248 should_scroll_render
249 }
250
251 pub fn new() -> ScrollStates {
252 ScrollStates::default()
253 }
254
255 pub fn get_scroll_position(&self, scroll_id: &ExternalScrollId) -> Option<LogicalPosition> {
256 self.0.get(&scroll_id).map(|entry| entry.get())
257 }
258
259 pub fn set_scroll_position(
262 &mut self,
263 node: &OverflowingScrollNode,
264 scroll_position: LogicalPosition,
265 ) {
266 self.0
267 .entry(node.parent_external_scroll_id)
268 .or_insert_with(|| ScrollState::default())
269 .set(scroll_position.x, scroll_position.y, &node.child_rect);
270 }
271
272 pub fn scroll_node(
276 &mut self,
277 node: &OverflowingScrollNode,
278 scroll_by_x: f32,
279 scroll_by_y: f32,
280 ) {
281 self.0
282 .entry(node.parent_external_scroll_id)
283 .or_insert_with(|| ScrollState::default())
284 .add(scroll_by_x, scroll_by_y, &node.child_rect);
285 }
286}
287
288#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
289#[repr(C)]
290pub struct ScrollState {
291 pub scroll_position: LogicalPosition,
293}
294
295impl_option!(
296 ScrollState,
297 OptionScrollState,
298 [Debug, Copy, Clone, PartialEq, PartialOrd]
299);
300
301impl ScrollState {
302 pub fn get(&self) -> LogicalPosition {
304 self.scroll_position
305 }
306
307 pub fn add(&mut self, x: f32, y: f32, child_rect: &LogicalRect) {
309 self.scroll_position.x = (self.scroll_position.x + x)
310 .max(0.0)
311 .min(child_rect.size.width);
312 self.scroll_position.y = (self.scroll_position.y + y)
313 .max(0.0)
314 .min(child_rect.size.height);
315 }
316
317 pub fn set(&mut self, x: f32, y: f32, child_rect: &LogicalRect) {
319 self.scroll_position.x = x.max(0.0).min(child_rect.size.width);
320 self.scroll_position.y = y.max(0.0).min(child_rect.size.height);
321 }
322}
323
324impl Default for ScrollState {
325 fn default() -> Self {
326 ScrollState {
327 scroll_position: LogicalPosition::zero(),
328 }
329 }
330}
331
332#[derive(Debug, Clone, PartialEq)]
333pub struct FullHitTest {
334 pub hovered_nodes: BTreeMap<DomId, HitTest>,
335 pub focused_node: OptionDomNodeId,
336}
337
338pub struct FullHitTestHoveredNode {
339 pub dom_id: DomId,
340 pub hit_test: HitTest,
341}
342
343impl_option!(
344 FullHitTest,
345 OptionFullHitTest,
346 copy = false,
347 [Debug, Clone, PartialEq]
348);
349
350impl FullHitTest {
351 pub fn empty(focused_node: Option<DomNodeId>) -> Self {
353 Self {
354 hovered_nodes: BTreeMap::new(),
355 focused_node: focused_node.into(),
356 }
357 }
358
359 pub fn is_empty(&self) -> bool {
361 self.hovered_nodes.is_empty()
362 }
363}
364
365#[derive(Debug, Clone, Default, PartialEq)]
366pub struct CursorTypeHitTest {
367 pub cursor_node: Option<(DomId, NodeId)>,
371 pub cursor_icon: MouseCursorType,
374}