use std::collections::{BTreeMap, VecDeque};
use crate::hit_test::FullHitTest;
const MAX_HOVER_HISTORY: usize = 5;
fn deepest_node_across_doms(ht: &FullHitTest) -> Option<azul_core::dom::DomNodeId> {
for (dom_id, hit) in ht.hovered_nodes.iter().rev() {
if let Some(node_id) = hit.regular_hit_test_nodes.keys().last().copied() {
return Some(azul_core::dom::DomNodeId {
dom: *dom_id,
node: azul_core::styled_dom::NodeHierarchyItemId::from_crate_internal(Some(
node_id,
)),
});
}
}
None
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum InputPointId {
Mouse,
Touch(u64),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HoverManager {
hover_histories: BTreeMap<InputPointId, VecDeque<FullHitTest>>,
}
impl HoverManager {
#[must_use] pub const fn new() -> Self {
Self {
hover_histories: BTreeMap::new(),
}
}
#[must_use] pub fn debug_counts(&self) -> (usize, usize) {
let points = self.hover_histories.len();
let total: usize = self.hover_histories.values().map(VecDeque::len).sum();
(points, total)
}
pub fn push_hit_test(&mut self, input_id: InputPointId, hit_test: FullHitTest) {
let history = self
.hover_histories
.entry(input_id)
.or_insert_with(|| VecDeque::with_capacity(MAX_HOVER_HISTORY));
history.push_front(hit_test);
if history.len() > MAX_HOVER_HISTORY {
history.pop_back();
}
}
pub fn remove_input_point(&mut self, input_id: &InputPointId) {
self.hover_histories.remove(input_id);
}
#[must_use] pub fn get_current(&self, input_id: &InputPointId) -> Option<&FullHitTest> {
self.hover_histories
.get(input_id)
.and_then(|history| history.front())
}
#[must_use] pub fn get_current_mouse(&self) -> Option<&FullHitTest> {
self.get_current(&InputPointId::Mouse)
}
#[must_use] pub fn get_frame(&self, input_id: &InputPointId, frames_ago: usize) -> Option<&FullHitTest> {
self.hover_histories
.get(input_id)
.and_then(|history| history.get(frames_ago))
}
#[must_use] pub fn get_history(&self, input_id: &InputPointId) -> Option<&VecDeque<FullHitTest>> {
self.hover_histories.get(input_id)
}
#[must_use] pub fn get_active_input_points(&self) -> Vec<InputPointId> {
self.hover_histories.keys().copied().collect()
}
#[must_use] pub fn frame_count(&self, input_id: &InputPointId) -> usize {
self.hover_histories
.get(input_id)
.map_or(0, VecDeque::len)
}
pub fn purge_dom(&mut self, dom_id: &azul_core::dom::DomId) {
for history in self.hover_histories.values_mut() {
for frame in history.iter_mut() {
frame.hovered_nodes.remove(dom_id);
}
}
}
pub fn clear(&mut self) {
self.hover_histories.clear();
}
pub(crate) fn clear_input_point(&mut self, input_id: &InputPointId) {
if let Some(history) = self.hover_histories.get_mut(input_id) {
history.clear();
}
}
#[must_use] pub fn has_sufficient_history_for_gestures(&self, input_id: &InputPointId) -> bool {
self.frame_count(input_id) >= 2
}
#[must_use] pub fn any_has_sufficient_history_for_gestures(&self) -> bool {
self.hover_histories
.iter()
.any(|(_, history)| history.len() >= 2)
}
#[must_use] pub fn current_hover_node(&self) -> Option<azul_core::id::NodeId> {
let current = self.get_current_mouse()?;
let dom_id = azul_core::dom::DomId { inner: 0 };
let ht = current.hovered_nodes.get(&dom_id)?;
ht.regular_hit_test_nodes.keys().last().copied()
}
#[must_use] pub fn previous_hover_node(&self) -> Option<azul_core::id::NodeId> {
let history = self.hover_histories.get(&InputPointId::Mouse)?;
let previous = history.get(1)?; let dom_id = azul_core::dom::DomId { inner: 0 };
let ht = previous.hovered_nodes.get(&dom_id)?;
ht.regular_hit_test_nodes.keys().last().copied()
}
#[must_use] pub fn current_hover_node_full(&self) -> Option<azul_core::dom::DomNodeId> {
deepest_node_across_doms(self.get_current_mouse()?)
}
#[must_use] pub fn previous_hover_node_full(&self) -> Option<azul_core::dom::DomNodeId> {
let history = self.hover_histories.get(&InputPointId::Mouse)?;
deepest_node_across_doms(history.get(1)?)
}
}
impl crate::managers::NodeIdRemap for HoverManager {
fn remap_node_ids(&mut self, dom_id: azul_core::dom::DomId, map: &crate::managers::NodeIdMap) {
let node_id_map = map.as_btree_map();
for history in self.hover_histories.values_mut() {
for hit_test in history.iter_mut() {
if let Some(ht) = hit_test.hovered_nodes.get_mut(&dom_id) {
crate::managers::remap_keys(&mut ht.regular_hit_test_nodes, map);
crate::managers::remap_keys(&mut ht.scroll_hit_test_nodes, map);
crate::managers::remap_keys(&mut ht.cursor_hit_test_nodes, map);
let old_sb: Vec<_> = ht.scrollbar_hit_test_nodes.keys().copied().collect();
let mut new_sb = BTreeMap::new();
for old_key in old_sb {
let Some(new_key) = remap_scrollbar_hit_id(&old_key, dom_id, node_id_map)
else {
ht.scrollbar_hit_test_nodes.remove(&old_key);
continue;
};
if let Some(item) = ht.scrollbar_hit_test_nodes.remove(&old_key) {
new_sb.insert(new_key, item);
}
}
ht.scrollbar_hit_test_nodes = new_sb;
}
}
}
}
}
impl Default for HoverManager {
fn default() -> Self {
Self::new()
}
}
fn remap_scrollbar_hit_id(
id: &azul_core::hit_test::ScrollbarHitId,
dom_id: azul_core::dom::DomId,
node_id_map: &BTreeMap<azul_core::id::NodeId, azul_core::id::NodeId>,
) -> Option<azul_core::hit_test::ScrollbarHitId> {
use azul_core::hit_test::ScrollbarHitId;
Some(match id {
ScrollbarHitId::VerticalTrack(d, n) if *d == dom_id => {
ScrollbarHitId::VerticalTrack(*d, *node_id_map.get(n)?)
}
ScrollbarHitId::VerticalThumb(d, n) if *d == dom_id => {
ScrollbarHitId::VerticalThumb(*d, *node_id_map.get(n)?)
}
ScrollbarHitId::HorizontalTrack(d, n) if *d == dom_id => {
ScrollbarHitId::HorizontalTrack(*d, *node_id_map.get(n)?)
}
ScrollbarHitId::HorizontalThumb(d, n) if *d == dom_id => {
ScrollbarHitId::HorizontalThumb(*d, *node_id_map.get(n)?)
}
other => *other,
})
}