use std::cell::{Cell, RefCell};
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
use super::handle::Element;
use crate::element::ElementTag;
use crate::value::WhiskerValue;
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BindType {
#[default]
Bind = 0,
Catch = 1,
CaptureBind = 2,
CaptureCatch = 3,
}
pub type EventFiring = (Rc<dyn Fn(WhiskerValue) + 'static>, WhiskerValue);
#[derive(Default)]
pub struct EventDispatchPlan {
pub consumed: bool,
pub firings: Vec<EventFiring>,
}
pub trait DynRenderer {
fn create_element(&mut self, tag: ElementTag) -> Element;
fn create_element_by_name(&mut self, tag_name: &str) -> Element;
fn release_element(&mut self, handle: Element);
fn set_attribute(&mut self, handle: Element, key: &str, value: &str);
fn set_attribute_int(&mut self, handle: Element, key: &str, value: i64) {
self.set_attribute(handle, key, &value.to_string());
}
fn set_attribute_bool(&mut self, handle: Element, key: &str, value: bool) {
self.set_attribute(handle, key, if value { "true" } else { "false" });
}
fn set_attribute_double(&mut self, handle: Element, key: &str, value: f64) {
self.set_attribute(handle, key, &value.to_string());
}
fn set_inline_styles(&mut self, handle: Element, css: &str);
fn element_sign(&self, _handle: Element) -> i32 {
0
}
fn set_update_list_info(&mut self, _handle: Element, _count: i32) {}
fn install_list_native_item_provider(
&mut self,
_handle: Element,
provider: super::list_provider::NativeItemProvider,
) -> bool {
drop(provider);
false
}
fn append_child(&mut self, parent: Element, child: Element);
fn remove_child(&mut self, parent: Element, child: Element);
fn set_event_listener(
&mut self,
handle: Element,
event_name: &str,
bind_type: BindType,
callback: Box<dyn Fn(WhiskerValue) + 'static>,
);
fn plan_event_dispatch(
&self,
_target_sign: i32,
_event_name: &str,
_body: &WhiskerValue,
) -> EventDispatchPlan {
EventDispatchPlan::default()
}
fn set_root(&mut self, page: Element);
fn flush(&mut self);
fn module_component_ptr(&self, _handle: Element) -> usize {
0
}
}
thread_local! {
static CURRENT_RENDERER: RefCell<Option<Box<dyn DynRenderer>>> = const { RefCell::new(None) };
static CHILDREN_OF: RefCell<HashMap<Element, Vec<Element>>> =
RefCell::new(HashMap::new());
static PARENT_OF: RefCell<HashMap<Element, Element>> =
RefCell::new(HashMap::new());
static PHANTOM_ELEMENTS: RefCell<HashSet<Element>> =
RefCell::new(HashSet::new());
static NEXT_PHANTOM_ID: Cell<u32> = const { Cell::new(PHANTOM_BASE) };
}
pub const PHANTOM_BASE: u32 = 1 << 31;
pub fn install_renderer(r: Box<dyn DynRenderer>) -> Option<Box<dyn DynRenderer>> {
CURRENT_RENDERER.with_borrow_mut(|slot| slot.replace(r))
}
pub fn uninstall_renderer(prev: Option<Box<dyn DynRenderer>>) {
CURRENT_RENDERER.with_borrow_mut(|slot| *slot = prev);
}
pub fn with_installed_renderer<R>(r: Box<dyn DynRenderer>, f: impl FnOnce() -> R) -> R {
let prev = install_renderer(r);
let result = f();
let _new = CURRENT_RENDERER.with_borrow_mut(|slot| slot.take());
if let Some(p) = prev {
let _ = install_renderer(p);
}
result
}
pub fn current_renderer_id() -> Option<&'static str> {
CURRENT_RENDERER.with_borrow(|slot| slot.as_ref().map(|_| "installed"))
}
fn with_renderer<R>(f: impl FnOnce(&mut dyn DynRenderer) -> R, default: R) -> R {
CURRENT_RENDERER.with_borrow_mut(|slot| match slot.as_mut() {
Some(r) => f(r.as_mut()),
None => {
#[cfg(debug_assertions)]
eprintln!("whisker-view: renderer call outside any installed renderer; ignored");
default
}
})
}
pub fn create_element_by_name(tag_name: &str) -> Element {
let handle = with_renderer(|r| r.create_element_by_name(tag_name), Element(u32::MAX));
if handle.id() != u32::MAX {
crate::reactive::with_runtime(|rt| {
if let Some(owner_id) = rt.current_owner() {
if let Some(owner) = rt.owners.get_mut(owner_id) {
owner.elements.push(handle);
}
}
});
}
handle
}
pub fn create_element(tag: ElementTag) -> Element {
let handle = with_renderer(|r| r.create_element(tag), Element(u32::MAX));
if handle.id() != u32::MAX {
crate::reactive::with_runtime(|rt| {
if let Some(owner_id) = rt.current_owner() {
if let Some(owner) = rt.owners.get_mut(owner_id) {
owner.elements.push(handle);
}
}
});
}
handle
}
pub fn release_element(handle: Element) {
if is_phantom(handle) {
PHANTOM_ELEMENTS.with_borrow_mut(|s| {
s.remove(&handle);
});
CHILDREN_OF.with_borrow_mut(|m| {
m.remove(&handle);
});
PARENT_OF.with_borrow_mut(|m| {
m.remove(&handle);
});
return;
}
with_renderer(|r| r.release_element(handle), ())
}
pub fn create_phantom_element() -> Element {
let id = NEXT_PHANTOM_ID.with(|c| {
let id = c.get();
c.set(id.wrapping_add(1));
id
});
let handle = Element::from_raw(id);
PHANTOM_ELEMENTS.with_borrow_mut(|s| {
s.insert(handle);
});
crate::reactive::with_runtime(|rt| {
if let Some(owner_id) = rt.current_owner() {
if let Some(owner) = rt.owners.get_mut(owner_id) {
owner.elements.push(handle);
}
}
});
handle
}
pub fn is_phantom(handle: Element) -> bool {
if handle.id() < PHANTOM_BASE {
return false;
}
PHANTOM_ELEMENTS.with_borrow(|s| s.contains(&handle))
}
fn nearest_real_ancestor(start: Element) -> Option<Element> {
let mut current = start;
loop {
let parent = PARENT_OF.with_borrow(|m| m.get(¤t).copied())?;
if !is_phantom(parent) {
return Some(parent);
}
current = parent;
}
}
fn count_real_descendants_before(root: Element, target: Element) -> usize {
fn walk(node: Element, target: Element, count: &mut usize, found: &mut bool) {
if *found {
return;
}
let children = CHILDREN_OF.with_borrow(|m| m.get(&node).cloned().unwrap_or_default());
for child in children {
if *found {
return;
}
if child == target {
*found = true;
return;
}
if is_phantom(child) {
walk(child, target, count, found);
} else {
*count += 1;
}
}
}
let mut count = 0usize;
let mut found = false;
walk(root, target, &mut count, &mut found);
count
}
fn collect_transparent_real_descendants(root: Element) -> Vec<Element> {
let mut out = Vec::new();
fn walk(node: Element, out: &mut Vec<Element>) {
let children = CHILDREN_OF.with_borrow(|m| m.get(&node).cloned().unwrap_or_default());
for child in children {
if is_phantom(child) {
walk(child, out);
} else {
out.push(child);
}
}
}
walk(root, &mut out);
out
}
pub fn set_attribute(handle: Element, key: &str, value: &str) {
if is_phantom(handle) {
return; }
with_renderer(|r| r.set_attribute(handle, key, value), ())
}
pub fn set_attribute_int(handle: Element, key: &str, value: i64) {
if is_phantom(handle) {
return;
}
with_renderer(|r| r.set_attribute_int(handle, key, value), ())
}
pub fn set_attribute_bool(handle: Element, key: &str, value: bool) {
if is_phantom(handle) {
return;
}
with_renderer(|r| r.set_attribute_bool(handle, key, value), ())
}
pub fn set_attribute_double(handle: Element, key: &str, value: f64) {
if is_phantom(handle) {
return;
}
with_renderer(|r| r.set_attribute_double(handle, key, value), ())
}
pub fn set_inline_styles(handle: Element, css: &str) {
if is_phantom(handle) {
return;
}
with_renderer(|r| r.set_inline_styles(handle, css), ())
}
pub fn element_sign(handle: Element) -> i32 {
if is_phantom(handle) {
return 0;
}
with_renderer(|r| r.element_sign(handle), 0)
}
pub fn set_update_list_info(handle: Element, count: i32) {
if is_phantom(handle) {
return;
}
with_renderer(|r| r.set_update_list_info(handle, count), ())
}
pub fn install_list_native_item_provider(
handle: Element,
provider: super::list_provider::NativeItemProvider,
) -> bool {
if is_phantom(handle) {
drop(provider);
return false;
}
with_renderer(
|r| r.install_list_native_item_provider(handle, provider),
false,
)
}
pub fn append_child(parent: Element, child: Element) {
CHILDREN_OF.with_borrow_mut(|map| {
map.entry(parent).or_default().push(child);
});
PARENT_OF.with_borrow_mut(|map| {
map.insert(child, parent);
});
let parent_is_phantom = is_phantom(parent);
let child_is_phantom = is_phantom(child);
if parent_is_phantom {
if let Some(real_anc) = nearest_real_ancestor(parent) {
let to_attach: Vec<Element> = if child_is_phantom {
collect_transparent_real_descendants(child)
} else {
vec![child]
};
for real in to_attach {
let pos = count_real_descendants_before(real_anc, real);
bridge_insert_or_append(real_anc, real, pos);
}
}
} else if child_is_phantom {
for real in collect_transparent_real_descendants(child) {
let pos = count_real_descendants_before(parent, real);
bridge_insert_or_append(parent, real, pos);
}
} else {
with_renderer(|r| r.append_child(parent, child), ());
}
crate::reactive::on_component_root_attached(parent, child);
}
pub fn remove_child(parent: Element, child: Element) {
let parent_is_phantom = is_phantom(parent);
let child_is_phantom = is_phantom(child);
if parent_is_phantom {
if let Some(real_anc) = nearest_real_ancestor(parent) {
let to_detach: Vec<Element> = if child_is_phantom {
collect_transparent_real_descendants(child)
} else {
vec![child]
};
for real in to_detach {
with_renderer(|r| r.remove_child(real_anc, real), ());
}
}
} else if child_is_phantom {
for real in collect_transparent_real_descendants(child) {
with_renderer(|r| r.remove_child(parent, real), ());
}
} else {
with_renderer(|r| r.remove_child(parent, child), ());
}
CHILDREN_OF.with_borrow_mut(|map| {
if let Some(children) = map.get_mut(&parent) {
children.retain(|c| *c != child);
}
});
PARENT_OF.with_borrow_mut(|map| {
map.remove(&child);
});
}
fn bridge_insert_or_append(real_parent: Element, real_child: Element, position: usize) {
with_renderer(|r| r.append_child(real_parent, real_child), ());
let real_descendants = collect_transparent_real_descendants(real_parent);
if position + 1 < real_descendants.len() {
let to_move: Vec<Element> = real_descendants[position + 1..].to_vec();
for sib in &to_move {
with_renderer(|r| r.remove_child(real_parent, *sib), ());
}
for sib in &to_move {
with_renderer(|r| r.append_child(real_parent, *sib), ());
}
}
}
pub fn insert_child_at(parent: Element, child: Element, index: usize) {
let to_re_append: Vec<Element> = CHILDREN_OF.with_borrow(|map| {
map.get(&parent)
.map(|children| {
if index >= children.len() {
Vec::new()
} else {
children[index..].to_vec()
}
})
.unwrap_or_default()
});
for c in &to_re_append {
remove_child(parent, *c);
}
append_child(parent, child);
for c in to_re_append {
append_child(parent, c);
}
}
pub fn previous_sibling(parent: Element, child: Element) -> Option<Element> {
CHILDREN_OF.with_borrow(|map| {
let children = map.get(&parent)?;
let idx = children.iter().position(|c| *c == child)?;
if idx == 0 {
None
} else {
Some(children[idx - 1])
}
})
}
pub fn child_index(parent: Element, child: Element) -> Option<usize> {
CHILDREN_OF.with_borrow(|map| {
let children = map.get(&parent)?;
children.iter().position(|c| *c == child)
})
}
pub fn children_of(parent: Element) -> Vec<Element> {
CHILDREN_OF.with_borrow(|map| map.get(&parent).cloned().unwrap_or_default())
}
#[doc(hidden)]
pub fn __reset_children_mirror_for_tests() {
CHILDREN_OF.with_borrow_mut(|map| map.clear());
}
pub fn set_event_listener(
handle: Element,
event_name: &str,
bind_type: BindType,
callback: Box<dyn Fn(WhiskerValue) + 'static>,
) {
if is_phantom(handle) {
drop(callback);
return;
}
with_renderer(
|r| r.set_event_listener(handle, event_name, bind_type, callback),
(),
)
}
pub fn dispatch_event(target_sign: i32, event_name: &str, body: WhiskerValue) -> bool {
let plan = with_renderer(
|r| r.plan_event_dispatch(target_sign, event_name, &body),
EventDispatchPlan::default(),
);
for (listener, event) in plan.firings {
listener(event);
}
plan.consumed
}
pub fn set_root(page: Element) {
with_renderer(|r| r.set_root(page), ())
}
pub fn flush() {
with_renderer(|r| r.flush(), ())
}
pub fn module_component_ptr(handle: Element) -> usize {
if is_phantom(handle) {
return 0;
}
CURRENT_RENDERER.with_borrow(|slot| match slot.as_ref() {
Some(r) => r.module_component_ptr(handle),
None => 0,
})
}