1
//! Hit-test result types for determining which DOM nodes are under the cursor,
2
//! scroll state tracking, and pipeline/document identification. These types
3
//! feed into the event dispatch system.
4

            
5
use alloc::collections::BTreeMap;
6
use core::{
7
    fmt,
8
    sync::atomic::{AtomicU32, Ordering as AtomicOrdering},
9
};
10

            
11
use 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
/// Result of a hit test against a single DOM, containing all nodes hit
22
/// by the cursor along with scroll, scrollbar, and cursor-type information.
23
#[derive(Debug, Clone, PartialEq, PartialOrd)]
24
pub struct HitTest {
25
    pub regular_hit_test_nodes: BTreeMap<NodeId, HitTestItem>,
26
    pub scroll_hit_test_nodes: BTreeMap<NodeId, ScrollHitTestItem>,
27
    /// Hit test results for scrollbar components.
28
    pub scrollbar_hit_test_nodes: BTreeMap<ScrollbarHitId, ScrollbarHitTestItem>,
29
    /// Hit test results for cursor areas (text runs with cursor property).
30
    /// Maps NodeId to (CursorType, hit_depth) - the cursor type and z-depth of the hit.
31
    pub cursor_hit_test_nodes: BTreeMap<NodeId, CursorHitTestItem>,
32
}
33

            
34
/// Hit test item for cursor areas (determines which cursor icon to show).
35
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
36
#[repr(C)]
37
pub struct CursorHitTestItem {
38
    pub cursor_type: CursorType,
39
    pub hit_depth: u32,
40
    pub point_in_viewport: LogicalPosition,
41
}
42

            
43
impl 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
/// Unique identifier for a specific component of a scrollbar.
61
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
62
#[repr(C, u8)]
63
pub enum ScrollbarHitId {
64
    VerticalTrack(DomId, NodeId),
65
    VerticalThumb(DomId, NodeId),
66
    HorizontalTrack(DomId, NodeId),
67
    HorizontalThumb(DomId, NodeId),
68
}
69

            
70
/// Hit test item specifically for scrollbar components.
71
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
72
#[repr(C)]
73
pub struct ScrollbarHitTestItem {
74
    pub point_in_viewport: LogicalPosition,
75
    pub point_relative_to_item: LogicalPosition,
76
    pub orientation: ScrollbarOrientation,
77
}
78

            
79
/// Scroll frame identifier combining a unique `u64` tag with its owning `PipelineId`.
80
#[derive(Copy, Clone, Eq, Hash, PartialEq, Ord, PartialOrd)]
81
#[repr(C)]
82
pub struct ExternalScrollId(pub u64, pub PipelineId);
83

            
84
impl ::core::fmt::Display for ExternalScrollId {
85
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
86
        write!(f, "ExternalScrollId({})", self.0)
87
    }
88
}
89

            
90
impl ::core::fmt::Debug for ExternalScrollId {
91
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92
        write!(f, "{}", self)
93
    }
94
}
95

            
96
/// A node whose content overflows its parent, requiring scroll handling.
97
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
98
pub 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

            
107
impl 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

            
123
/// Extra source identifier within a pipeline, allowing multiple independent
124
/// subsystems to generate `PipelineId` values without collision.
125
/// All pipelines still share the same `IdNamespace` and `DocumentId`.
126
pub type PipelineSourceId = u32;
127

            
128
/// Information about a scroll frame, given to the user by the framework
129
#[derive(Debug, Clone, PartialEq, PartialOrd)]
130
pub struct ScrollPosition {
131
    /// How big is the parent container
132
    /// (so that things like "scroll to left edge" can be implemented)?
133
    pub parent_rect: LogicalRect,
134
    /// How big is the scroll rect (i.e. the union of all children)?
135
    pub children_rect: LogicalRect,
136
}
137

            
138
/// Identifies a document within a namespace, used for multi-document rendering.
139
#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
140
pub struct DocumentId {
141
    pub namespace_id: IdNamespace,
142
    pub id: u32,
143
}
144

            
145
impl ::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

            
155
impl ::core::fmt::Debug for DocumentId {
156
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
157
        write!(f, "{}", self)
158
    }
159
}
160

            
161
/// Identifies a rendering pipeline by source and sequence number.
162
#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
163
pub struct PipelineId(pub PipelineSourceId, pub u32);
164

            
165
impl ::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

            
171
impl ::core::fmt::Debug for PipelineId {
172
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
173
        write!(f, "{}", self)
174
    }
175
}
176

            
177
static LAST_PIPELINE_ID: AtomicU32 = AtomicU32::new(0);
178

            
179
impl 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
/// A single hit-test result for a regular (non-scroll) DOM node.
191
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
192
pub struct HitTestItem {
193
    /// The hit point in the coordinate space of the "viewport" of the display item.
194
    /// The viewport is the scroll node formed by the root reference frame of the display item's
195
    /// pipeline.
196
    pub point_in_viewport: LogicalPosition,
197
    /// The coordinates of the original hit test point relative to the origin of this item.
198
    /// This is useful for calculating things like text offsets in the client.
199
    pub point_relative_to_item: LogicalPosition,
200
    /// Necessary to easily get the nearest VirtualView node
201
    pub is_focusable: bool,
202
    /// If this hit is a VirtualView node, stores the VirtualViews DomId + the origin of the VirtualView
203
    pub is_virtual_view_hit: Option<(DomId, LogicalPosition)>,
204
    /// Z-order depth from WebRender hit test (0 = frontmost/topmost in z-order).
205
    /// Lower values are closer to the user. This preserves the ordering from
206
    /// WebRender's hit test results which returns items front-to-back.
207
    pub hit_depth: u32,
208
}
209

            
210
/// A hit-test result for a scrollable DOM node.
211
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
212
pub struct ScrollHitTestItem {
213
    /// The hit point in the coordinate space of the "viewport" of the display item.
214
    /// The viewport is the scroll node formed by the root reference frame of the display item's
215
    /// pipeline.
216
    pub point_in_viewport: LogicalPosition,
217
    /// The coordinates of the original hit test point relative to the origin of this item.
218
    /// This is useful for calculating things like text offsets in the client.
219
    pub point_relative_to_item: LogicalPosition,
220
    /// If this hit is a VirtualView node, stores the VirtualViews DomId + the origin of the VirtualView
221
    pub scroll_node: OverflowingScrollNode,
222
}
223

            
224
/// Map of active scroll states, keyed by their external scroll ID.
225
#[derive(Debug, Default)]
226
pub struct ScrollStates(pub OrderedMap<ExternalScrollId, ScrollState>);
227

            
228
impl 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
    /// Set the scroll amount - does not update the `entry.used_this_frame`,
238
    /// since that is only relevant when we are actually querying the renderer.
239
    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
    /// Updating (add to) the existing scroll amount does not update the
251
    /// `entry.used_this_frame`, since that is only relevant when we are
252
    /// actually querying the renderer.
253
    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
/// Current scroll position for a single scroll frame.
267
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
268
#[repr(C)]
269
pub struct ScrollState {
270
    /// Amount in pixel that the current node is scrolled
271
    pub scroll_position: LogicalPosition,
272
}
273

            
274
impl_option!(
275
    ScrollState,
276
    OptionScrollState,
277
    [Debug, Copy, Clone, PartialEq, PartialOrd]
278
);
279

            
280
impl ScrollState {
281
    /// Return the current position of the scroll state
282
    pub fn get(&self) -> LogicalPosition {
283
        self.scroll_position
284
    }
285

            
286
    /// Add a scroll X / Y onto the existing scroll state
287
    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
    /// Set the scroll state to a new position
297
    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

            
303
impl Default for ScrollState {
304
    fn default() -> Self {
305
        ScrollState {
306
            scroll_position: LogicalPosition::zero(),
307
        }
308
    }
309
}
310

            
311
/// Complete hit-test result across all DOMs, including the currently focused node.
312
#[derive(Debug, Clone, PartialEq)]
313
pub struct FullHitTest {
314
    pub hovered_nodes: BTreeMap<DomId, HitTest>,
315
    pub focused_node: OptionDomNodeId,
316
}
317

            
318
impl FullHitTest {
319
    /// Create an empty hit-test result
320
34
    pub fn empty(focused_node: Option<DomNodeId>) -> Self {
321
34
        Self {
322
34
            hovered_nodes: BTreeMap::new(),
323
34
            focused_node: focused_node.into(),
324
34
        }
325
34
    }
326

            
327
    /// Returns `true` if no nodes were hovered (ignores `focused_node`).
328
2
    pub fn is_empty(&self) -> bool {
329
2
        self.hovered_nodes.is_empty()
330
2
    }
331
}
332

            
333
/// Result of determining which mouse cursor icon to display based on hit-test results.
334
#[derive(Debug, Clone, Default, PartialEq)]
335
pub struct CursorTypeHitTest {
336
    /// closest-node is used for determining the cursor: property
337
    /// The node is guaranteed to have a non-default cursor: property,
338
    /// so that the cursor icon can be set accordingly
339
    pub cursor_node: Option<(DomId, NodeId)>,
340
    /// Mouse cursor type to set (if cursor_node is None, this is set to
341
    /// `MouseCursorType::Default`)
342
    pub cursor_icon: MouseCursorType,
343
}