1use alloc::collections::BTreeMap;
6use core::{
7 fmt,
8 sync::atomic::{AtomicU32, Ordering as AtomicOrdering},
9};
10
11use crate::{
12 dom::{DomId, DomNodeHash, DomNodeId, OptionDomNodeId, ScrollTagId, ScrollbarOrientation},
13 geom::{LogicalPosition, LogicalRect},
14 hit_test_tag::CursorType,
15 id::NodeId,
16 resources::IdNamespace,
17 window::MouseCursorType,
18 OrderedMap,
19};
20
21#[derive(Debug, Clone, PartialEq, PartialOrd)]
24pub struct HitTest {
25 pub regular_hit_test_nodes: BTreeMap<NodeId, HitTestItem>,
26 pub scroll_hit_test_nodes: BTreeMap<NodeId, ScrollHitTestItem>,
27 pub scrollbar_hit_test_nodes: BTreeMap<ScrollbarHitId, ScrollbarHitTestItem>,
29 pub cursor_hit_test_nodes: BTreeMap<NodeId, CursorHitTestItem>,
32}
33
34#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
36#[repr(C)]
37pub struct CursorHitTestItem {
38 pub cursor_type: CursorType,
39 pub hit_depth: u32,
40 pub point_in_viewport: LogicalPosition,
41}
42
43impl HitTest {
44 pub fn empty() -> Self {
45 Self {
46 regular_hit_test_nodes: BTreeMap::new(),
47 scroll_hit_test_nodes: BTreeMap::new(),
48 scrollbar_hit_test_nodes: BTreeMap::new(),
49 cursor_hit_test_nodes: BTreeMap::new(),
50 }
51 }
52 pub fn is_empty(&self) -> bool {
53 self.regular_hit_test_nodes.is_empty()
54 && self.scroll_hit_test_nodes.is_empty()
55 && self.scrollbar_hit_test_nodes.is_empty()
56 && self.cursor_hit_test_nodes.is_empty()
57 }
58}
59
60#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
62#[repr(C, u8)]
63pub enum ScrollbarHitId {
64 VerticalTrack(DomId, NodeId),
65 VerticalThumb(DomId, NodeId),
66 HorizontalTrack(DomId, NodeId),
67 HorizontalThumb(DomId, NodeId),
68}
69
70#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
72#[repr(C)]
73pub struct ScrollbarHitTestItem {
74 pub point_in_viewport: LogicalPosition,
75 pub point_relative_to_item: LogicalPosition,
76 pub orientation: ScrollbarOrientation,
77}
78
79#[derive(Copy, Clone, Eq, Hash, PartialEq, Ord, PartialOrd)]
81#[repr(C)]
82pub struct ExternalScrollId(pub u64, pub PipelineId);
83
84impl ::core::fmt::Display for ExternalScrollId {
85 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
86 write!(f, "ExternalScrollId({})", self.0)
87 }
88}
89
90impl ::core::fmt::Debug for ExternalScrollId {
91 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92 write!(f, "{}", self)
93 }
94}
95
96#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
98pub struct OverflowingScrollNode {
99 pub parent_rect: LogicalRect,
100 pub child_rect: LogicalRect,
101 pub virtual_child_rect: LogicalRect,
102 pub parent_external_scroll_id: ExternalScrollId,
103 pub parent_dom_hash: DomNodeHash,
104 pub scroll_tag_id: ScrollTagId,
105}
106
107impl Default for OverflowingScrollNode {
108 fn default() -> Self {
109 use crate::dom::TagId;
110 Self {
111 parent_rect: LogicalRect::zero(),
112 child_rect: LogicalRect::zero(),
113 virtual_child_rect: LogicalRect::zero(),
114 parent_external_scroll_id: ExternalScrollId(0, PipelineId::DUMMY),
115 parent_dom_hash: DomNodeHash { inner: 0 },
116 scroll_tag_id: ScrollTagId {
117 inner: TagId { inner: 0 },
118 },
119 }
120 }
121}
122
123pub type PipelineSourceId = u32;
127
128#[derive(Debug, Clone, PartialEq, PartialOrd)]
130pub struct ScrollPosition {
131 pub parent_rect: LogicalRect,
134 pub children_rect: LogicalRect,
136}
137
138#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
140pub struct DocumentId {
141 pub namespace_id: IdNamespace,
142 pub id: u32,
143}
144
145impl ::core::fmt::Display for DocumentId {
146 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
147 write!(
148 f,
149 "DocumentId {{ ns: {}, id: {} }}",
150 self.namespace_id, self.id
151 )
152 }
153}
154
155impl ::core::fmt::Debug for DocumentId {
156 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
157 write!(f, "{}", self)
158 }
159}
160
161#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
163pub struct PipelineId(pub PipelineSourceId, pub u32);
164
165impl ::core::fmt::Display for PipelineId {
166 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
167 write!(f, "PipelineId({}, {})", self.0, self.1)
168 }
169}
170
171impl ::core::fmt::Debug for PipelineId {
172 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
173 write!(f, "{}", self)
174 }
175}
176
177static LAST_PIPELINE_ID: AtomicU32 = AtomicU32::new(0);
178
179impl PipelineId {
180 pub const DUMMY: PipelineId = PipelineId(0, 0);
181
182 pub fn new() -> Self {
183 PipelineId(
184 LAST_PIPELINE_ID.fetch_add(1, AtomicOrdering::SeqCst),
185 0,
186 )
187 }
188}
189
190#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
192pub struct HitTestItem {
193 pub point_in_viewport: LogicalPosition,
197 pub point_relative_to_item: LogicalPosition,
200 pub is_focusable: bool,
202 pub is_virtual_view_hit: Option<(DomId, LogicalPosition)>,
204 pub hit_depth: u32,
208}
209
210#[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)]
226pub struct ScrollStates(pub OrderedMap<ExternalScrollId, ScrollState>);
227
228impl ScrollStates {
229 pub fn new() -> ScrollStates {
230 ScrollStates::default()
231 }
232
233 pub fn get_scroll_position(&self, scroll_id: &ExternalScrollId) -> Option<LogicalPosition> {
234 self.0.get(&scroll_id).map(|entry| entry.get())
235 }
236
237 pub fn set_scroll_position(
240 &mut self,
241 node: &OverflowingScrollNode,
242 scroll_position: LogicalPosition,
243 ) {
244 self.0
245 .entry(node.parent_external_scroll_id)
246 .or_default()
247 .set(scroll_position.x, scroll_position.y, &node.child_rect);
248 }
249
250 pub fn scroll_node(
254 &mut self,
255 node: &OverflowingScrollNode,
256 scroll_by_x: f32,
257 scroll_by_y: f32,
258 ) {
259 self.0
260 .entry(node.parent_external_scroll_id)
261 .or_default()
262 .add(scroll_by_x, scroll_by_y, &node.child_rect);
263 }
264}
265
266#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
268#[repr(C)]
269pub struct ScrollState {
270 pub scroll_position: LogicalPosition,
272}
273
274impl_option!(
275 ScrollState,
276 OptionScrollState,
277 [Debug, Copy, Clone, PartialEq, PartialOrd]
278);
279
280impl ScrollState {
281 pub fn get(&self) -> LogicalPosition {
283 self.scroll_position
284 }
285
286 pub fn add(&mut self, x: f32, y: f32, child_rect: &LogicalRect) {
288 self.scroll_position.x = (self.scroll_position.x + x)
289 .max(0.0)
290 .min(child_rect.size.width);
291 self.scroll_position.y = (self.scroll_position.y + y)
292 .max(0.0)
293 .min(child_rect.size.height);
294 }
295
296 pub fn set(&mut self, x: f32, y: f32, child_rect: &LogicalRect) {
298 self.scroll_position.x = x.max(0.0).min(child_rect.size.width);
299 self.scroll_position.y = y.max(0.0).min(child_rect.size.height);
300 }
301}
302
303impl Default for ScrollState {
304 fn default() -> Self {
305 ScrollState {
306 scroll_position: LogicalPosition::zero(),
307 }
308 }
309}
310
311#[derive(Debug, Clone, PartialEq)]
313pub struct FullHitTest {
314 pub hovered_nodes: BTreeMap<DomId, HitTest>,
315 pub focused_node: OptionDomNodeId,
316}
317
318impl FullHitTest {
319 pub fn empty(focused_node: Option<DomNodeId>) -> Self {
321 Self {
322 hovered_nodes: BTreeMap::new(),
323 focused_node: focused_node.into(),
324 }
325 }
326
327 pub fn is_empty(&self) -> bool {
329 self.hovered_nodes.is_empty()
330 }
331}
332
333#[derive(Debug, Clone, Default, PartialEq)]
335pub struct CursorTypeHitTest {
336 pub cursor_node: Option<(DomId, NodeId)>,
340 pub cursor_icon: MouseCursorType,
343}