use std::{
fmt,
sync::atomic::{AtomicUsize, Ordering},
collections::BTreeMap,
rc::Rc,
any::Any,
hash::Hash,
cell::{Ref as StdRef, RefMut as StdRefMut, RefCell},
ffi::c_void,
};
use azul_css::{LayoutPoint, LayoutRect, LayoutSize, CssPath};
#[cfg(feature = "css_parser")]
use azul_css_parser::CssPathParseError;
use crate::{
FastHashMap,
app_resources::{AppResources, IdNamespace, Words, WordPositions, ScaledWords, LayoutedGlyphs},
dom::{Dom, DomPtr, DomId, TagId, NodeType, NodeData},
display_list::CachedDisplayList,
ui_state::UiState,
ui_description::UiDescription,
ui_solver::{PositionedRectangle, LayoutedRectangle, ScrolledNodes, LayoutResult},
id_tree::{NodeId, Node, NodeHierarchy},
window::{
WindowSize, WindowState, FullWindowState, CallbacksOfHitTest,
KeyboardState, MouseState, LogicalSize, PhysicalSize,
UpdateFocusWarning, CallCallbacksResult, ScrollStates,
},
task::{Timer, TerminateTimer, Task, TimerId},
gl::Texture,
};
use gleam::gl::Gl;
pub type UpdateScreen = Option<()>;
#[allow(non_upper_case_globals)]
pub const Redraw: Option<()> = Some(());
#[allow(non_upper_case_globals)]
pub const DontRedraw: Option<()> = None;
#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Ref<T: 'static>(Rc<RefCell<T>>);
impl<T: 'static> Clone for Ref<T> {
fn clone(&self) -> Self {
Ref(self.0.clone())
}
}
impl<T: 'static + Hash> Hash for Ref<T> {
fn hash<H>(&self, state: &mut H) where H: ::std::hash::Hasher {
let self_ptr = Rc::into_raw(self.0.clone()) as *const c_void as usize;
state.write_usize(self_ptr);
self.0.borrow().hash(state)
}
}
impl<T: 'static> Ref<T> {
pub fn new(data: T) -> Self {
Ref(Rc::new(RefCell::new(data)))
}
pub fn borrow(&self) -> StdRef<T> {
self.0.borrow()
}
pub fn borrow_mut(&mut self) -> StdRefMut<T> {
self.0.borrow_mut()
}
pub fn get_type_name(&self) -> &'static str {
use std::any;
any::type_name::<T>()
}
pub fn upcast(self) -> RefAny {
use std::any;
RefAny {
ptr: self.0 as Rc<dyn Any>,
type_name: any::type_name::<T>(),
}
}
}
impl<T: 'static> From<Ref<T>> for RefAny {
fn from(r: Ref<T>) -> Self {
r.upcast()
}
}
#[derive(Debug)]
pub struct RefAny {
ptr: Rc<dyn Any>,
type_name: &'static str,
}
impl Clone for RefAny {
fn clone(&self) -> Self {
RefAny {
ptr: self.ptr.clone(),
type_name: self.type_name,
}
}
}
#[no_mangle] #[repr(C)] pub struct RefAnyPtr { pub ptr: *mut c_void }
impl ::std::hash::Hash for RefAny {
fn hash<H>(&self, state: &mut H) where H: ::std::hash::Hasher {
let self_ptr = Rc::into_raw(self.ptr.clone()) as *const c_void as usize;
state.write_usize(self_ptr);
}
}
impl PartialEq for RefAny {
fn eq(&self, rhs: &Self) -> bool {
Rc::ptr_eq(&self.ptr, &rhs.ptr)
}
}
impl PartialOrd for RefAny {
fn partial_cmp(&self, rhs: &Self) -> Option<::std::cmp::Ordering> {
Some(self.cmp(rhs))
}
}
impl Ord for RefAny {
fn cmp(&self, rhs: &Self) -> ::std::cmp::Ordering {
let self_ptr = Rc::into_raw(self.ptr.clone()) as *const c_void as usize;
let rhs_ptr = Rc::into_raw(rhs.ptr.clone()) as *const c_void as usize;
self_ptr.cmp(&rhs_ptr)
}
}
impl Eq for RefAny { }
impl RefAny {
pub fn downcast<T: 'static>(&self) -> Option<&RefCell<T>> {
self.ptr.downcast_ref::<RefCell<T>>()
}
pub fn get_type_name(&self) -> &'static str {
self.type_name
}
}
pub type PipelineSourceId = u32;
pub type LayoutCallback = fn(RefAnyPtr, LayoutInfoPtr) -> DomPtr;
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub struct ScrollPosition {
pub scroll_frame_rect: LayoutRect,
pub parent_rect: LayoutedRectangle,
pub scroll_location: LayoutPoint,
}
#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct DocumentId {
pub namespace_id: IdNamespace,
pub id: u32
}
impl ::std::fmt::Display for DocumentId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "DocumentId {{ ns: {}, id: {} }}", self.namespace_id, self.id)
}
}
impl ::std::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 ::std::fmt::Display for PipelineId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "PipelineId({}, {})", self.0, self.1)
}
}
impl ::std::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, Ordering::SeqCst) as u32, 0)
}
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub struct HitTestItem {
pub pipeline: PipelineId,
pub tag: TagId,
pub point_in_viewport: LayoutPoint,
pub point_relative_to_item: LayoutPoint,
}
#[macro_export]
macro_rules! impl_callback {($callback_value:ident) => (
impl ::std::fmt::Display for $callback_value {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl ::std::fmt::Debug for $callback_value {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let callback = stringify!($callback_value);
write!(f, "{} @ 0x{:x}", callback, self.0 as usize)
}
}
impl Clone for $callback_value {
fn clone(&self) -> Self {
$callback_value(self.0.clone())
}
}
impl ::std::hash::Hash for $callback_value {
fn hash<H>(&self, state: &mut H) where H: ::std::hash::Hasher {
state.write_usize(self.0 as usize);
}
}
impl PartialEq for $callback_value {
fn eq(&self, rhs: &Self) -> bool {
self.0 as usize == rhs.0 as usize
}
}
impl PartialOrd for $callback_value {
fn partial_cmp(&self, other: &Self) -> Option<::std::cmp::Ordering> {
Some((self.0 as usize).cmp(&(other.0 as usize)))
}
}
impl Ord for $callback_value {
fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
(self.0 as usize).cmp(&(other.0 as usize))
}
}
impl Eq for $callback_value { }
impl Copy for $callback_value { }
)}
macro_rules! impl_get_gl_context {() => {
pub fn get_gl_context(&self) -> Rc<dyn Gl> {
self.gl_context.clone()
}
};}
pub struct Callback(pub CallbackType);
impl_callback!(Callback);
pub struct CallbackInfo<'a> {
pub state: &'a RefAny,
pub current_window_state: &'a FullWindowState,
pub modifiable_window_state: &'a mut WindowState,
pub layout_result: &'a BTreeMap<DomId, LayoutResult>,
pub scrolled_nodes: &'a BTreeMap<DomId, ScrolledNodes>,
pub cached_display_list: &'a CachedDisplayList,
pub gl_context: Rc<dyn Gl>,
pub resources : &'a mut AppResources,
pub timers: &'a mut FastHashMap<TimerId, Timer>,
pub tasks: &'a mut Vec<Task>,
pub ui_state: &'a BTreeMap<DomId, UiState>,
pub stop_propagation: &'a mut bool,
pub focus_target: &'a mut Option<FocusTarget>,
pub current_scroll_states: &'a BTreeMap<DomId, BTreeMap<NodeId, ScrollPosition>>,
pub nodes_scrolled_in_callback: &'a mut BTreeMap<DomId, BTreeMap<NodeId, LayoutPoint>>,
pub hit_dom_node: (DomId, NodeId),
pub cursor_relative_to_item: Option<(f32, f32)>,
pub cursor_in_viewport: Option<(f32, f32)>,
}
pub type CallbackReturn = UpdateScreen;
pub type CallbackType = fn(CallbackInfo) -> CallbackReturn;
impl<'a> fmt::Debug for CallbackInfo<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "CallbackInfo {{
data: {{ .. }}, \
current_window_state: {:?}, \
modifiable_window_state: {:?}, \
layout_result: {:?}, \
scrolled_nodes: {:?}, \
cached_display_list: {:?}, \
gl_context: {{ .. }}, \
resources: {{ .. }}, \
timers: {{ .. }}, \
tasks: {{ .. }}, \
ui_state: {:?}, \
focus_target: {:?}, \
current_scroll_states: {:?}, \
nodes_scrolled_in_callback: {:?}, \
hit_dom_node: {:?}, \
cursor_relative_to_item: {:?}, \
cursor_in_viewport: {:?}, \
}}",
self.current_window_state,
self.modifiable_window_state,
self.layout_result,
self.scrolled_nodes,
self.cached_display_list,
self.ui_state,
self.focus_target,
self.current_scroll_states,
self.nodes_scrolled_in_callback,
self.hit_dom_node,
self.cursor_relative_to_item,
self.cursor_in_viewport,
)
}
}
impl<'a> CallbackInfo<'a> {
pub fn stop_propagation(&mut self) {
*self.stop_propagation = true;
}
}
pub struct GlCallback(pub GlCallbackType);
impl_callback!(GlCallback);
pub struct GlCallbackInfo<'a> {
pub state: &'a RefAny,
pub layout_info: LayoutInfo<'a>,
pub bounds: HidpiAdjustedBounds,
}
pub type GlCallbackReturn = Option<Texture>;
pub type GlCallbackType = fn(GlCallbackInfo) -> GlCallbackReturn;
pub struct IFrameCallback(pub IFrameCallbackType);
impl_callback!(IFrameCallback);
pub struct IFrameCallbackInfo<'a> {
pub state: &'a RefAny,
pub layout_info: LayoutInfo<'a>,
pub bounds: HidpiAdjustedBounds,
}
pub type IFrameCallbackReturn = Option<Dom>;
pub type IFrameCallbackType = fn(IFrameCallbackInfo) -> IFrameCallbackReturn;
pub struct TimerCallback(pub TimerCallbackType);
impl_callback!(TimerCallback);
pub struct TimerCallbackInfo<'a> {
pub state: &'a mut RefAny,
pub app_resources: &'a mut AppResources,
}
pub type TimerCallbackReturn = (UpdateScreen, TerminateTimer);
pub type TimerCallbackType = fn(TimerCallbackInfo) -> TimerCallbackReturn;
#[no_mangle] #[repr(C)] pub struct LayoutInfoPtr { pub ptr: *mut c_void }
pub struct LayoutInfo<'a> {
pub window_size: &'a WindowSize,
pub window_size_width_stops: &'a mut Vec<f32>,
pub window_size_height_stops: &'a mut Vec<f32>,
pub gl_context: Rc<dyn Gl>,
pub resources: &'a AppResources,
}
impl<'a> LayoutInfo<'a> {
impl_get_gl_context!();
}
impl<'a> LayoutInfo<'a> {
pub fn window_width_larger_than(&mut self, width: f32) -> bool {
self.window_size_width_stops.push(width);
self.window_size.get_logical_size().width > width
}
pub fn window_width_smaller_than(&mut self, width: f32) -> bool {
self.window_size_width_stops.push(width);
self.window_size.get_logical_size().width < width
}
pub fn window_height_larger_than(&mut self, height: f32) -> bool {
self.window_size_height_stops.push(height);
self.window_size.get_logical_size().height > height
}
pub fn window_height_smaller_than(&mut self, height: f32) -> bool {
self.window_size_height_stops.push(height);
self.window_size.get_logical_size().height < height
}
}
#[derive(Debug, Copy, Clone)]
pub struct HidpiAdjustedBounds {
pub logical_size: LogicalSize,
pub hidpi_factor: f32,
}
impl HidpiAdjustedBounds {
#[inline(always)]
pub fn from_bounds(bounds: LayoutSize, hidpi_factor: f32) -> Self {
let logical_size = LogicalSize::new(bounds.width, bounds.height);
Self {
logical_size,
hidpi_factor,
}
}
pub fn get_physical_size(&self) -> PhysicalSize<u32> {
self.get_logical_size().to_physical(self.hidpi_factor)
}
pub fn get_logical_size(&self) -> LogicalSize {
LogicalSize::new(
self.logical_size.width * self.hidpi_factor,
self.logical_size.height * self.hidpi_factor
)
}
pub fn get_hidpi_factor(&self) -> f32 {
self.hidpi_factor
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FocusTarget {
Id((DomId, NodeId)),
Path((DomId, CssPath)),
NoFocus,
}
impl FocusTarget {
pub fn resolve(
&self,
ui_descriptions: &BTreeMap<DomId, UiDescription>,
ui_states: &BTreeMap<DomId, UiState>,
) -> Result<Option<(DomId, NodeId)>, UpdateFocusWarning> {
use crate::callbacks::FocusTarget::*;
use crate::style::matches_html_element;
match self {
Id((dom_id, node_id)) => {
let ui_state = ui_states.get(&dom_id).ok_or(UpdateFocusWarning::FocusInvalidDomId(dom_id.clone()))?;
let _ = ui_state.dom.arena.node_data.get(*node_id).ok_or(UpdateFocusWarning::FocusInvalidNodeId(*node_id))?;
Ok(Some((dom_id.clone(), *node_id)))
},
NoFocus => Ok(None),
Path((dom_id, css_path)) => {
let ui_state = ui_states.get(&dom_id).ok_or(UpdateFocusWarning::FocusInvalidDomId(dom_id.clone()))?;
let ui_description = ui_descriptions.get(&dom_id).ok_or(UpdateFocusWarning::FocusInvalidDomId(dom_id.clone()))?;
let html_node_tree = &ui_description.html_tree;
let node_hierarchy = &ui_state.dom.arena.node_hierarchy;
let node_data = &ui_state.dom.arena.node_data;
let resolved_node_id = html_node_tree
.linear_iter()
.find(|node_id| matches_html_element(css_path, *node_id, &node_hierarchy, &node_data, &html_node_tree))
.ok_or(UpdateFocusWarning::CouldNotFindFocusNode(css_path.clone()))?;
Ok(Some((dom_id.clone(), resolved_node_id)))
},
}
}
}
impl<'a> CallbackInfo<'a> {
impl_callback_info_api!();
impl_task_api!();
impl_get_gl_context!();
}
pub struct ParentNodesIterator<'a> {
ui_state: &'a BTreeMap<DomId, UiState>,
current_item: (DomId, NodeId),
}
impl<'a> ParentNodesIterator<'a> {
pub fn current_node(&self) -> (DomId, NodeId) {
self.current_item.clone()
}
pub fn current_index_in_parent(&self) -> Option<usize> {
let node_layout = &self.ui_state[&self.current_item.0].dom.arena.node_hierarchy;
if node_layout[self.current_item.1].parent.is_some() {
Some(node_layout.get_index_in_parent(self.current_item.1))
} else {
None
}
}
}
impl<'a> Iterator for ParentNodesIterator<'a> {
type Item = (DomId, NodeId);
fn next(&mut self) -> Option<(DomId, NodeId)> {
let parent_node_id = self.ui_state[&self.current_item.0].dom.arena.node_hierarchy[self.current_item.1].parent?;
self.current_item.1 = parent_node_id;
Some((self.current_item.0.clone(), parent_node_id))
}
}
pub fn call_callbacks(
callbacks_filter_list: &BTreeMap<DomId, CallbacksOfHitTest>,
ui_state_map: &BTreeMap<DomId, UiState>,
ui_description_map: &BTreeMap<DomId, UiDescription>,
timers: &mut FastHashMap<TimerId, Timer>,
tasks: &mut Vec<Task>,
scroll_states: &BTreeMap<DomId, BTreeMap<NodeId, ScrollPosition>>,
modifiable_scroll_states: &mut ScrollStates,
full_window_state: &mut FullWindowState,
layout_result: &BTreeMap<DomId, LayoutResult>,
scrolled_nodes: &BTreeMap<DomId, ScrolledNodes>,
cached_display_list: &CachedDisplayList,
gl_context: Rc<dyn Gl>,
resources: &mut AppResources,
) -> CallCallbacksResult {
let mut ret = CallCallbacksResult {
needs_restyle_hover_active: callbacks_filter_list.values().any(|v| v.needs_redraw_anyways),
needs_relayout_hover_active: callbacks_filter_list.values().any(|v| v.needs_relayout_anyways),
needs_restyle_focus_changed: false,
should_scroll_render: false,
callbacks_update_screen: DontRedraw,
modified_window_state: full_window_state.clone().into(),
};
let mut new_focus_target = None;
let mut nodes_scrolled_in_callbacks = BTreeMap::new();
for (dom_id, callbacks_of_hit_test) in callbacks_filter_list.iter() {
let ui_state = match ui_state_map.get(dom_id) {
Some(s) => s,
None => continue,
};
let mut callbacks_grouped_by_event_type = BTreeMap::new();
for (node_id, determine_callback_result) in callbacks_of_hit_test.nodes_with_callbacks.iter() {
for (event_filter, callback) in determine_callback_result.normal_callbacks.iter() {
callbacks_grouped_by_event_type
.entry(event_filter)
.or_insert_with(|| Vec::new())
.push((node_id, callback));
}
}
'outer: for (event_filter, callback_nodes) in callbacks_grouped_by_event_type {
for (node_id, _) in callback_nodes {
let mut new_focus = None;
let mut stop_propagation = false;
let hit_item = &callbacks_of_hit_test.nodes_with_callbacks[&node_id].hit_test_item;
let callback = ui_state.get_dom().arena.node_data
.get(*node_id)
.map(|nd| nd.get_callbacks())
.and_then(|dc| dc.iter().find_map(|(evt, cb)| if evt == event_filter { Some(cb) } else { None }));
let (callback, callback_ptr) = match callback {
Some(s) => s,
None => continue,
};
let callback_return = (callback.0)(CallbackInfo {
state: callback_ptr,
current_window_state: &full_window_state,
modifiable_window_state: &mut ret.modified_window_state,
layout_result,
scrolled_nodes,
cached_display_list,
gl_context: gl_context.clone(),
resources,
timers,
tasks,
ui_state: ui_state_map,
stop_propagation: &mut stop_propagation,
focus_target: &mut new_focus,
current_scroll_states: scroll_states,
nodes_scrolled_in_callback: &mut nodes_scrolled_in_callbacks,
hit_dom_node: (dom_id.clone(), *node_id),
cursor_relative_to_item: hit_item.as_ref().map(|hi| (hi.point_relative_to_item.x, hi.point_relative_to_item.y)),
cursor_in_viewport: hit_item.as_ref().map(|hi| (hi.point_in_viewport.x, hi.point_in_viewport.y)),
});
if callback_return == Redraw {
ret.callbacks_update_screen = Redraw;
}
if let Some(new_focus) = new_focus.clone() {
new_focus_target = Some(new_focus);
}
if stop_propagation {
continue 'outer;
}
}
}
}
for (dom_id, callback_scrolled_nodes) in nodes_scrolled_in_callbacks {
let scrolled_nodes = match scrolled_nodes.get(&dom_id) {
Some(s) => s,
None => continue,
};
for (scroll_node_id, scroll_position) in &callback_scrolled_nodes {
let overflowing_node = match scrolled_nodes.overflowing_nodes.get(&scroll_node_id) {
Some(s) => s,
None => continue,
};
modifiable_scroll_states.set_scroll_position(&overflowing_node, *scroll_position);
ret.should_scroll_render = true;
}
}
let new_focus_node = new_focus_target.and_then(|ft| ft.resolve(&ui_description_map, &ui_state_map).ok()?);
let focus_has_not_changed = full_window_state.focused_node == new_focus_node;
if !focus_has_not_changed {
}
full_window_state.focused_node = new_focus_node;
ret
}