1use std::{
2 fmt,
3 collections::BTreeMap,
4};
5use azul_css::CssProperty;
6use crate::{
7 FastHashMap,
8 id_tree::NodeId,
9 dom::{
10 Dom, CompactDom, DomId, TagId, TabIndex, DomString,
11 HoverEventFilter, FocusEventFilter, NotEventFilter,
12 WindowEventFilter,
13 },
14 callbacks::{
15 LayoutInfo, Callback, LayoutCallback,
16 IFrameCallback, RefAny,
17 },
18};
19#[cfg(feature = "opengl")]
20use crate::callbacks::GlCallback;
21
22pub struct UiState {
23 pub dom_id: DomId,
25 pub(crate) dom: CompactDom,
27 pub dynamic_css_overrides: BTreeMap<NodeId, FastHashMap<DomString, CssProperty>>,
29 pub tag_ids_to_hover_active_states: BTreeMap<TagId, (NodeId, HoverGroup)>,
31
32 pub tab_index_tags: BTreeMap<TagId, (NodeId, TabIndex)>,
34 pub draggable_tags: BTreeMap<TagId, NodeId>,
36 pub tag_ids_to_node_ids: BTreeMap<TagId, NodeId>,
38 pub node_ids_to_tag_ids: BTreeMap<NodeId, TagId>,
40
41 pub hover_callbacks: BTreeMap<NodeId, BTreeMap<HoverEventFilter, (Callback, RefAny)>>,
46 pub focus_callbacks: BTreeMap<NodeId, BTreeMap<FocusEventFilter, (Callback, RefAny)>>,
47 pub not_callbacks: BTreeMap<NodeId, BTreeMap<NotEventFilter, (Callback, RefAny)>>,
48 pub window_callbacks: BTreeMap<NodeId, BTreeMap<WindowEventFilter, (Callback, RefAny)>>,
49}
50
51impl UiState {
52 #[inline(always)]
53 pub const fn get_dom(&self) -> &CompactDom {
54 &self.dom
55 }
56}
57
58impl fmt::Debug for UiState {
59 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
60 write!(f,
61 "UiState {{ \
62
63 dom: {:?}, \
64 dynamic_css_overrides: {:?}, \
65 tag_ids_to_hover_active_states: {:?}, \
66 tab_index_tags: {:?}, \
67 draggable_tags: {:?}, \
68 tag_ids_to_node_ids: {:?}, \
69 node_ids_to_tag_ids: {:?}, \
70 hover_callbacks: {:?}, \
71 focus_callbacks: {:?}, \
72 not_callbacks: {:?}, \
73 window_callbacks: {:?}, \
74 }}",
75
76 self.dom,
77 self.dynamic_css_overrides,
78 self.tag_ids_to_hover_active_states,
79 self.tab_index_tags,
80 self.draggable_tags,
81 self.tag_ids_to_node_ids,
82 self.node_ids_to_tag_ids,
83 self.hover_callbacks,
84 self.focus_callbacks,
85 self.not_callbacks,
86 self.window_callbacks,
87 )
88 }
89}
90
91#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
94pub struct HoverGroup {
95 pub affects_layout: bool,
98 pub active_or_hover: ActiveHover,
100}
101
102#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
104pub enum ActiveHover {
105 Active,
106 Hover,
107}
108
109impl UiState {
110
111 pub fn new(dom: Dom, parent_dom: Option<(DomId, NodeId)>) -> UiState {
115
116 let dom: CompactDom = dom.into();
117
118 let mut tab_index_tags = BTreeMap::new();
133 let mut draggable_tags = BTreeMap::new();
135
136 let mut tag_ids_to_node_ids = BTreeMap::new();
138 let mut node_ids_to_tag_ids = BTreeMap::new();
140 let mut dynamic_css_overrides = BTreeMap::new();
142
143 let mut hover_callbacks = BTreeMap::new();
144 let mut focus_callbacks = BTreeMap::new();
145 let mut not_callbacks = BTreeMap::new();
146 let mut window_callbacks = BTreeMap::new();
147
148 macro_rules! filter_step_0 {
149 ($event_filter:ident, $callback_type:ty, $data_source:expr, $filter_func:ident) => {{
150 let node_hover_callbacks: BTreeMap<$event_filter, $callback_type> = $data_source.iter()
151 .filter_map(|(event_filter, cb)| event_filter.$filter_func().map(|not_evt| (not_evt, cb.clone())))
152 .collect();
153 node_hover_callbacks
154 }};
155 };
156
157 macro_rules! filter_and_insert_callbacks {(
158 $node_id:ident,
159 $data_source:expr,
160 $event_filter:ident,
161 $callback_type:ty,
162 $filter_func:ident,
163 $final_callback_list:ident,
164 ) => {
165 let node_hover_callbacks = filter_step_0!($event_filter, $callback_type, $data_source, $filter_func);
166 if !node_hover_callbacks.is_empty() {
167 $final_callback_list.insert($node_id, node_hover_callbacks);
168 }
169 };(
170 $node_id:ident,
171 $data_source:expr,
172 $event_filter:ident,
173 $callback_type:ty,
174 $filter_func:ident,
175 $final_callback_list:ident,
176 $node_tag_id:ident,
177 ) => {
178 let node_hover_callbacks = filter_step_0!($event_filter, $callback_type, $data_source, $filter_func);
179 if !node_hover_callbacks.is_empty() {
180 $final_callback_list.insert($node_id, node_hover_callbacks);
181 let tag_id = $node_tag_id.unwrap_or_else(|| TagId::new());
182 $node_tag_id = Some(tag_id);
183 }
184 };}
185
186 TagId::reset();
187
188 {
189 let arena = &dom.arena;
190
191 debug_assert!(arena.node_hierarchy[NodeId::new(0)].next_sibling.is_none());
192
193 for node_id in arena.linear_iter() {
194
195 let node = &arena.node_data[node_id];
196
197 let mut node_tag_id = None;
198
199 if !node.get_callbacks().is_empty() {
201
202 filter_and_insert_callbacks!(
204 node_id,
205 node.get_callbacks(),
206 HoverEventFilter,
207 (Callback, RefAny),
208 as_hover_event_filter,
209 hover_callbacks,
210 node_tag_id,
211 );
212
213 filter_and_insert_callbacks!(
215 node_id,
216 node.get_callbacks(),
217 FocusEventFilter,
218 (Callback, RefAny),
219 as_focus_event_filter,
220 focus_callbacks,
221 node_tag_id,
222 );
223
224 filter_and_insert_callbacks!(
225 node_id,
226 node.get_callbacks(),
227 NotEventFilter,
228 (Callback, RefAny),
229 as_not_event_filter,
230 not_callbacks,
231 node_tag_id,
232 );
233
234 filter_and_insert_callbacks!(
235 node_id,
236 node.get_callbacks(),
237 WindowEventFilter,
238 (Callback, RefAny),
239 as_window_event_filter,
240 window_callbacks,
241 );
242 }
243
244 if node.get_is_draggable() {
245 let tag_id = node_tag_id.unwrap_or_else(|| TagId::new());
246 draggable_tags.insert(tag_id, node_id);
247 node_tag_id = Some(tag_id);
248 }
249
250 let should_insert_tabindex_auto = !focus_callbacks.is_empty();
254 let node_tab_index = node.get_tab_index().or(if should_insert_tabindex_auto { Some(TabIndex::Auto) } else { None });
255
256 if let Some(tab_index) = node_tab_index {
257 let tag_id = node_tag_id.unwrap_or_else(|| TagId::new());
258 tab_index_tags.insert(tag_id, (node_id, tab_index));
259 node_tag_id = Some(tag_id);
260 }
261
262 if let Some(tag_id) = node_tag_id {
263 tag_ids_to_node_ids.insert(tag_id, node_id);
264 node_ids_to_tag_ids.insert(node_id, tag_id);
265 }
266
267 if !node.get_dynamic_css_overrides().is_empty() {
269 dynamic_css_overrides.insert(node_id, node.get_dynamic_css_overrides().iter().cloned().collect());
270 }
271 }
272 }
273
274 UiState {
275 dom_id: DomId::new(parent_dom),
276 dom,
277 dynamic_css_overrides,
278 tag_ids_to_hover_active_states: BTreeMap::new(),
279
280 tab_index_tags,
281 draggable_tags,
282 node_ids_to_tag_ids,
283 tag_ids_to_node_ids,
284
285 hover_callbacks,
286 focus_callbacks,
287 not_callbacks,
288 window_callbacks,
289 }
290 }
291
292 pub fn new_from_app_state<'a>(
293 data: &RefAny,
294 layout_info: LayoutInfo<'a>,
295 parent_dom: Option<(DomId, NodeId)>,
296 layout_callback: LayoutCallback,
297 ) -> UiState {
298
299 use std::ffi::c_void;
300 use crate::callbacks::{LayoutInfoPtr, RefAnyPtr};
301
302 let data_box = Box::new(data.clone());
303 let layout_info_box = Box::new(layout_info);
304 let data_box_ptr = Box::into_raw(data_box) as *mut c_void;
305 let layout_info_box_ptr = Box::into_raw(layout_info_box) as *mut c_void;
306
307 let dom_ptr = (layout_callback)(
308 RefAnyPtr { ptr: data_box_ptr },
309 LayoutInfoPtr { ptr: layout_info_box_ptr }
310 );
311
312 let dom = unsafe { Box::<Dom>::from_raw(dom_ptr.ptr as *mut Dom) };
313 let _ = unsafe { Box::<RefAny>::from_raw(data_box_ptr as *mut RefAny) };
314 let _ = unsafe { Box::<LayoutInfo<'a>>::from_raw(layout_info_box_ptr as *mut LayoutInfo<'a>) };
315
316 Self::new(*dom, parent_dom)
317 }
318
319 pub fn create_tags_for_hover_nodes(&mut self, hover_nodes: &BTreeMap<NodeId, HoverGroup>) {
320
321 for (hover_node_id, hover_group) in hover_nodes {
322 let hover_tag = match self.node_ids_to_tag_ids.get(hover_node_id) {
323 Some(tag_id) => *tag_id,
324 None => TagId::new(),
325 };
326
327 self.node_ids_to_tag_ids.insert(*hover_node_id, hover_tag);
328 self.tag_ids_to_node_ids.insert(hover_tag, *hover_node_id);
329 self.tag_ids_to_hover_active_states.insert(hover_tag, (*hover_node_id, *hover_group));
330 }
331 }
332
333 pub fn scan_for_iframe_callbacks(&self) -> Vec<(NodeId, &(IFrameCallback, RefAny))> {
334 use crate::dom::NodeType::IFrame;
335 self.dom.arena.node_hierarchy.linear_iter().filter_map(|node_id| {
336 let node_data = &self.dom.arena.node_data[node_id];
337 match node_data.get_node_type() {
338 IFrame(cb) => Some((node_id, cb)),
339 _ => None,
340 }
341 }).collect()
342 }
343
344 #[cfg(feature = "opengl")]
345 pub fn scan_for_gltexture_callbacks(&self) -> Vec<(NodeId, &(GlCallback, RefAny))> {
346 use crate::dom::NodeType::GlTexture;
347 self.dom.arena.node_hierarchy.linear_iter().filter_map(|node_id| {
348 let node_data = &self.dom.arena.node_data[node_id];
349 match node_data.get_node_type() {
350 GlTexture(cb) => Some((node_id, cb)),
351 _ => None,
352 }
353 }).collect()
354 }
355}