use std::{
fmt,
collections::BTreeMap,
};
use azul_css::CssProperty;
use crate::{
FastHashMap,
id_tree::NodeId,
dom::{
Dom, CompactDom, DomId, TagId, TabIndex, DomString,
HoverEventFilter, FocusEventFilter, NotEventFilter,
WindowEventFilter,
},
callbacks::{
LayoutInfo, Callback, LayoutCallback,
IFrameCallback, RefAny,
},
};
#[cfg(feature = "opengl")]
use crate::callbacks::GlCallback;
pub struct UiState {
pub dom_id: DomId,
pub(crate) dom: CompactDom,
pub dynamic_css_overrides: BTreeMap<NodeId, FastHashMap<DomString, CssProperty>>,
pub tag_ids_to_hover_active_states: BTreeMap<TagId, (NodeId, HoverGroup)>,
pub tab_index_tags: BTreeMap<TagId, (NodeId, TabIndex)>,
pub draggable_tags: BTreeMap<TagId, NodeId>,
pub tag_ids_to_node_ids: BTreeMap<TagId, NodeId>,
pub node_ids_to_tag_ids: BTreeMap<NodeId, TagId>,
pub hover_callbacks: BTreeMap<NodeId, BTreeMap<HoverEventFilter, (Callback, RefAny)>>,
pub focus_callbacks: BTreeMap<NodeId, BTreeMap<FocusEventFilter, (Callback, RefAny)>>,
pub not_callbacks: BTreeMap<NodeId, BTreeMap<NotEventFilter, (Callback, RefAny)>>,
pub window_callbacks: BTreeMap<NodeId, BTreeMap<WindowEventFilter, (Callback, RefAny)>>,
}
impl UiState {
#[inline(always)]
pub const fn get_dom(&self) -> &CompactDom {
&self.dom
}
}
impl fmt::Debug for UiState {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f,
"UiState {{ \
dom: {:?}, \
dynamic_css_overrides: {:?}, \
tag_ids_to_hover_active_states: {:?}, \
tab_index_tags: {:?}, \
draggable_tags: {:?}, \
tag_ids_to_node_ids: {:?}, \
node_ids_to_tag_ids: {:?}, \
hover_callbacks: {:?}, \
focus_callbacks: {:?}, \
not_callbacks: {:?}, \
window_callbacks: {:?}, \
}}",
self.dom,
self.dynamic_css_overrides,
self.tag_ids_to_hover_active_states,
self.tab_index_tags,
self.draggable_tags,
self.tag_ids_to_node_ids,
self.node_ids_to_tag_ids,
self.hover_callbacks,
self.focus_callbacks,
self.not_callbacks,
self.window_callbacks,
)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
pub struct HoverGroup {
pub affects_layout: bool,
pub active_or_hover: ActiveHover,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum ActiveHover {
Active,
Hover,
}
impl UiState {
pub fn new(dom: Dom, parent_dom: Option<(DomId, NodeId)>) -> UiState {
let dom: CompactDom = dom.into();
let mut tab_index_tags = BTreeMap::new();
let mut draggable_tags = BTreeMap::new();
let mut tag_ids_to_node_ids = BTreeMap::new();
let mut node_ids_to_tag_ids = BTreeMap::new();
let mut dynamic_css_overrides = BTreeMap::new();
let mut hover_callbacks = BTreeMap::new();
let mut focus_callbacks = BTreeMap::new();
let mut not_callbacks = BTreeMap::new();
let mut window_callbacks = BTreeMap::new();
macro_rules! filter_step_0 {
($event_filter:ident, $callback_type:ty, $data_source:expr, $filter_func:ident) => {{
let node_hover_callbacks: BTreeMap<$event_filter, $callback_type> = $data_source.iter()
.filter_map(|(event_filter, cb)| event_filter.$filter_func().map(|not_evt| (not_evt, cb.clone())))
.collect();
node_hover_callbacks
}};
};
macro_rules! filter_and_insert_callbacks {(
$node_id:ident,
$data_source:expr,
$event_filter:ident,
$callback_type:ty,
$filter_func:ident,
$final_callback_list:ident,
) => {
let node_hover_callbacks = filter_step_0!($event_filter, $callback_type, $data_source, $filter_func);
if !node_hover_callbacks.is_empty() {
$final_callback_list.insert($node_id, node_hover_callbacks);
}
};(
$node_id:ident,
$data_source:expr,
$event_filter:ident,
$callback_type:ty,
$filter_func:ident,
$final_callback_list:ident,
$node_tag_id:ident,
) => {
let node_hover_callbacks = filter_step_0!($event_filter, $callback_type, $data_source, $filter_func);
if !node_hover_callbacks.is_empty() {
$final_callback_list.insert($node_id, node_hover_callbacks);
let tag_id = $node_tag_id.unwrap_or_else(|| TagId::new());
$node_tag_id = Some(tag_id);
}
};}
TagId::reset();
{
let arena = &dom.arena;
debug_assert!(arena.node_hierarchy[NodeId::new(0)].next_sibling.is_none());
for node_id in arena.linear_iter() {
let node = &arena.node_data[node_id];
let mut node_tag_id = None;
if !node.get_callbacks().is_empty() {
filter_and_insert_callbacks!(
node_id,
node.get_callbacks(),
HoverEventFilter,
(Callback, RefAny),
as_hover_event_filter,
hover_callbacks,
node_tag_id,
);
filter_and_insert_callbacks!(
node_id,
node.get_callbacks(),
FocusEventFilter,
(Callback, RefAny),
as_focus_event_filter,
focus_callbacks,
node_tag_id,
);
filter_and_insert_callbacks!(
node_id,
node.get_callbacks(),
NotEventFilter,
(Callback, RefAny),
as_not_event_filter,
not_callbacks,
node_tag_id,
);
filter_and_insert_callbacks!(
node_id,
node.get_callbacks(),
WindowEventFilter,
(Callback, RefAny),
as_window_event_filter,
window_callbacks,
);
}
if node.get_is_draggable() {
let tag_id = node_tag_id.unwrap_or_else(|| TagId::new());
draggable_tags.insert(tag_id, node_id);
node_tag_id = Some(tag_id);
}
let should_insert_tabindex_auto = !focus_callbacks.is_empty();
let node_tab_index = node.get_tab_index().or(if should_insert_tabindex_auto { Some(TabIndex::Auto) } else { None });
if let Some(tab_index) = node_tab_index {
let tag_id = node_tag_id.unwrap_or_else(|| TagId::new());
tab_index_tags.insert(tag_id, (node_id, tab_index));
node_tag_id = Some(tag_id);
}
if let Some(tag_id) = node_tag_id {
tag_ids_to_node_ids.insert(tag_id, node_id);
node_ids_to_tag_ids.insert(node_id, tag_id);
}
if !node.get_dynamic_css_overrides().is_empty() {
dynamic_css_overrides.insert(node_id, node.get_dynamic_css_overrides().iter().cloned().collect());
}
}
}
UiState {
dom_id: DomId::new(parent_dom),
dom,
dynamic_css_overrides,
tag_ids_to_hover_active_states: BTreeMap::new(),
tab_index_tags,
draggable_tags,
node_ids_to_tag_ids,
tag_ids_to_node_ids,
hover_callbacks,
focus_callbacks,
not_callbacks,
window_callbacks,
}
}
pub fn new_from_app_state<'a>(
data: &RefAny,
layout_info: LayoutInfo<'a>,
parent_dom: Option<(DomId, NodeId)>,
layout_callback: LayoutCallback,
) -> UiState {
use std::ffi::c_void;
use crate::callbacks::{LayoutInfoPtr, RefAnyPtr};
let data_box = Box::new(data.clone());
let layout_info_box = Box::new(layout_info);
let data_box_ptr = Box::into_raw(data_box) as *mut c_void;
let layout_info_box_ptr = Box::into_raw(layout_info_box) as *mut c_void;
let dom_ptr = (layout_callback)(
RefAnyPtr { ptr: data_box_ptr },
LayoutInfoPtr { ptr: layout_info_box_ptr }
);
let dom = unsafe { Box::<Dom>::from_raw(dom_ptr.ptr as *mut Dom) };
let _ = unsafe { Box::<RefAny>::from_raw(data_box_ptr as *mut RefAny) };
let _ = unsafe { Box::<LayoutInfo<'a>>::from_raw(layout_info_box_ptr as *mut LayoutInfo<'a>) };
Self::new(*dom, parent_dom)
}
pub fn create_tags_for_hover_nodes(&mut self, hover_nodes: &BTreeMap<NodeId, HoverGroup>) {
for (hover_node_id, hover_group) in hover_nodes {
let hover_tag = match self.node_ids_to_tag_ids.get(hover_node_id) {
Some(tag_id) => *tag_id,
None => TagId::new(),
};
self.node_ids_to_tag_ids.insert(*hover_node_id, hover_tag);
self.tag_ids_to_node_ids.insert(hover_tag, *hover_node_id);
self.tag_ids_to_hover_active_states.insert(hover_tag, (*hover_node_id, *hover_group));
}
}
pub fn scan_for_iframe_callbacks(&self) -> Vec<(NodeId, &(IFrameCallback, RefAny))> {
use crate::dom::NodeType::IFrame;
self.dom.arena.node_hierarchy.linear_iter().filter_map(|node_id| {
let node_data = &self.dom.arena.node_data[node_id];
match node_data.get_node_type() {
IFrame(cb) => Some((node_id, cb)),
_ => None,
}
}).collect()
}
#[cfg(feature = "opengl")]
pub fn scan_for_gltexture_callbacks(&self) -> Vec<(NodeId, &(GlCallback, RefAny))> {
use crate::dom::NodeType::GlTexture;
self.dom.arena.node_hierarchy.linear_iter().filter_map(|node_id| {
let node_data = &self.dom.arena.node_data[node_id];
match node_data.get_node_type() {
GlTexture(cb) => Some((node_id, cb)),
_ => None,
}
}).collect()
}
}