use alloc::collections::BTreeMap;
use core::{
fmt,
sync::atomic::{AtomicUsize, Ordering as AtomicOrdering},
};
use crate::{
dom::{DomId, DomNodeHash, DomNodeId, OptionDomNodeId, ScrollTagId, ScrollbarOrientation},
geom::{LogicalPosition, LogicalRect, LogicalSize},
hit_test_tag::CursorType,
id::NodeId,
resources::IdNamespace,
styled_dom::NodeHierarchyItemId,
window::MouseCursorType,
FastHashMap,
};
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct HitTest {
pub regular_hit_test_nodes: BTreeMap<NodeId, HitTestItem>,
pub scroll_hit_test_nodes: BTreeMap<NodeId, ScrollHitTestItem>,
pub scrollbar_hit_test_nodes: BTreeMap<ScrollbarHitId, ScrollbarHitTestItem>,
pub cursor_hit_test_nodes: BTreeMap<NodeId, CursorHitTestItem>,
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
#[repr(C)]
pub struct CursorHitTestItem {
pub cursor_type: CursorType,
pub hit_depth: u32,
pub point_in_viewport: LogicalPosition,
}
impl HitTest {
pub fn empty() -> Self {
Self {
regular_hit_test_nodes: BTreeMap::new(),
scroll_hit_test_nodes: BTreeMap::new(),
scrollbar_hit_test_nodes: BTreeMap::new(),
cursor_hit_test_nodes: BTreeMap::new(),
}
}
pub fn is_empty(&self) -> bool {
self.regular_hit_test_nodes.is_empty()
&& self.scroll_hit_test_nodes.is_empty()
&& self.scrollbar_hit_test_nodes.is_empty()
&& self.cursor_hit_test_nodes.is_empty()
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C, u8)]
pub enum ScrollbarHitId {
VerticalTrack(DomId, NodeId),
VerticalThumb(DomId, NodeId),
HorizontalTrack(DomId, NodeId),
HorizontalThumb(DomId, NodeId),
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
#[repr(C)]
pub struct ScrollbarHitTestItem {
pub point_in_viewport: LogicalPosition,
pub point_relative_to_item: LogicalPosition,
pub orientation: ScrollbarOrientation,
}
#[derive(Copy, Clone, Eq, Hash, PartialEq, Ord, PartialOrd)]
#[repr(C)]
pub struct ExternalScrollId(pub u64, pub PipelineId);
impl ::core::fmt::Display for ExternalScrollId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ExternalScrollId({})", self.0)
}
}
impl ::core::fmt::Debug for ExternalScrollId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self)
}
}
#[derive(Debug, Default, Clone, PartialEq, PartialOrd)]
pub struct ScrolledNodes {
pub overflowing_nodes: BTreeMap<NodeHierarchyItemId, OverflowingScrollNode>,
pub clip_nodes: BTreeMap<NodeId, LogicalSize>,
pub tags_to_node_ids: BTreeMap<ScrollTagId, NodeHierarchyItemId>,
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub struct OverflowingScrollNode {
pub parent_rect: LogicalRect,
pub child_rect: LogicalRect,
pub virtual_child_rect: LogicalRect,
pub parent_external_scroll_id: ExternalScrollId,
pub parent_dom_hash: DomNodeHash,
pub scroll_tag_id: ScrollTagId,
}
impl Default for OverflowingScrollNode {
fn default() -> Self {
use crate::dom::TagId;
Self {
parent_rect: LogicalRect::zero(),
child_rect: LogicalRect::zero(),
virtual_child_rect: LogicalRect::zero(),
parent_external_scroll_id: ExternalScrollId(0, PipelineId::DUMMY),
parent_dom_hash: DomNodeHash { inner: 0 },
scroll_tag_id: ScrollTagId {
inner: TagId { inner: 0 },
},
}
}
}
pub type PipelineSourceId = u32;
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct ScrollPosition {
pub parent_rect: LogicalRect,
pub children_rect: LogicalRect,
}
#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct DocumentId {
pub namespace_id: IdNamespace,
pub id: u32,
}
impl ::core::fmt::Display for DocumentId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"DocumentId {{ ns: {}, id: {} }}",
self.namespace_id, self.id
)
}
}
impl ::core::fmt::Debug for DocumentId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self)
}
}
#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct PipelineId(pub PipelineSourceId, pub u32);
impl ::core::fmt::Display for PipelineId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "PipelineId({}, {})", self.0, self.1)
}
}
impl ::core::fmt::Debug for PipelineId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self)
}
}
static LAST_PIPELINE_ID: AtomicUsize = AtomicUsize::new(0);
impl PipelineId {
pub const DUMMY: PipelineId = PipelineId(0, 0);
pub fn new() -> Self {
PipelineId(
LAST_PIPELINE_ID.fetch_add(1, AtomicOrdering::SeqCst) as u32,
0,
)
}
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub struct HitTestItem {
pub point_in_viewport: LogicalPosition,
pub point_relative_to_item: LogicalPosition,
pub is_focusable: bool,
pub is_iframe_hit: Option<(DomId, LogicalPosition)>,
pub hit_depth: u32,
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub struct ScrollHitTestItem {
pub point_in_viewport: LogicalPosition,
pub point_relative_to_item: LogicalPosition,
pub scroll_node: OverflowingScrollNode,
}
#[derive(Debug, Default)]
pub struct ScrollStates(pub FastHashMap<ExternalScrollId, ScrollState>);
impl ScrollStates {
#[must_use]
pub fn should_scroll_render(
&mut self,
(scroll_x, scroll_y): &(f32, f32),
hit_test: &FullHitTest,
) -> bool {
let mut should_scroll_render = false;
for hit_test in hit_test.hovered_nodes.values() {
for scroll_hit_test_item in hit_test.scroll_hit_test_nodes.values() {
self.scroll_node(&scroll_hit_test_item.scroll_node, *scroll_x, *scroll_y);
should_scroll_render = true;
break; }
}
should_scroll_render
}
pub fn new() -> ScrollStates {
ScrollStates::default()
}
pub fn get_scroll_position(&self, scroll_id: &ExternalScrollId) -> Option<LogicalPosition> {
self.0.get(&scroll_id).map(|entry| entry.get())
}
pub fn set_scroll_position(
&mut self,
node: &OverflowingScrollNode,
scroll_position: LogicalPosition,
) {
self.0
.entry(node.parent_external_scroll_id)
.or_insert_with(|| ScrollState::default())
.set(scroll_position.x, scroll_position.y, &node.child_rect);
}
pub fn scroll_node(
&mut self,
node: &OverflowingScrollNode,
scroll_by_x: f32,
scroll_by_y: f32,
) {
self.0
.entry(node.parent_external_scroll_id)
.or_insert_with(|| ScrollState::default())
.add(scroll_by_x, scroll_by_y, &node.child_rect);
}
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
#[repr(C)]
pub struct ScrollState {
pub scroll_position: LogicalPosition,
}
impl_option!(
ScrollState,
OptionScrollState,
[Debug, Copy, Clone, PartialEq, PartialOrd]
);
impl ScrollState {
pub fn get(&self) -> LogicalPosition {
self.scroll_position
}
pub fn add(&mut self, x: f32, y: f32, child_rect: &LogicalRect) {
self.scroll_position.x = (self.scroll_position.x + x)
.max(0.0)
.min(child_rect.size.width);
self.scroll_position.y = (self.scroll_position.y + y)
.max(0.0)
.min(child_rect.size.height);
}
pub fn set(&mut self, x: f32, y: f32, child_rect: &LogicalRect) {
self.scroll_position.x = x.max(0.0).min(child_rect.size.width);
self.scroll_position.y = y.max(0.0).min(child_rect.size.height);
}
}
impl Default for ScrollState {
fn default() -> Self {
ScrollState {
scroll_position: LogicalPosition::zero(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FullHitTest {
pub hovered_nodes: BTreeMap<DomId, HitTest>,
pub focused_node: OptionDomNodeId,
}
pub struct FullHitTestHoveredNode {
pub dom_id: DomId,
pub hit_test: HitTest,
}
impl_option!(
FullHitTest,
OptionFullHitTest,
copy = false,
[Debug, Clone, PartialEq]
);
impl FullHitTest {
pub fn empty(focused_node: Option<DomNodeId>) -> Self {
Self {
hovered_nodes: BTreeMap::new(),
focused_node: focused_node.into(),
}
}
pub fn is_empty(&self) -> bool {
self.hovered_nodes.is_empty()
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct CursorTypeHitTest {
pub cursor_node: Option<(DomId, NodeId)>,
pub cursor_icon: MouseCursorType,
}