use bevy::platform::collections::{HashMap, HashSet};
use bevy::prelude::*;
use bevy::text::{LetterSpacing, LineHeight};
use crossbeam_channel::Receiver;
use crate::protocol::{NodeId, op::Op, outbound::Outbound};
use crate::style::{Style, StyleDirty};
pub type ResolvedTextStyle = (TextColor, TextFont, LineHeight, LetterSpacing);
pub type OpReceiver = Receiver<Vec<Op>>;
#[cfg(not(target_arch = "wasm32"))]
pub type OutboundSender = tokio::sync::mpsc::UnboundedSender<Outbound>;
#[cfg(target_arch = "wasm32")]
pub type OutboundSender = crossbeam_channel::Sender<Outbound>;
#[derive(Component, Debug, Clone, Copy)]
pub struct ReactNode(pub NodeId);
#[derive(Component, Debug, Clone, Copy)]
pub struct RRoot;
#[derive(Component, Debug, Clone, Default)]
pub struct StyleVariants {
pub base: Option<Style>,
pub hover: Option<Style>,
pub press: Option<Style>,
pub focus: Option<Style>,
pub keys: StyleDirty,
pub restyle: Restyle,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum Restyle {
#[default]
Full,
Idle,
Base(StyleDirty),
}
impl Restyle {
pub fn note_base_delta(&mut self, mask: StyleDirty) {
*self = match *self {
Restyle::Idle => Restyle::Base(mask),
Restyle::Base(prev) => Restyle::Base(prev.union(mask)),
Restyle::Full => Restyle::Full,
};
}
}
#[derive(Component, Debug, Clone, Copy, Default)]
pub struct FocusState(pub bool);
#[derive(Component, Debug, Clone, Copy, Default)]
pub struct PointerHandlers {
pub down: bool,
pub moved: bool,
pub up: bool,
pub enter: bool,
pub leave: bool,
}
#[derive(Component, Debug, Clone, Copy, Default)]
pub struct ClickOwner;
#[derive(Component, Debug, Clone, Copy, Default)]
pub struct HoverState(pub bool);
#[derive(Component, Debug, Clone, Copy, Default)]
pub struct ScrollListener;
#[derive(Component, Debug, Clone, Copy, Default)]
pub struct WheelListener;
#[derive(Component, Debug, Clone, Copy)]
pub struct ScrollStep(pub f32);
#[derive(Resource, Clone)]
pub struct OutboundResource(pub OutboundSender);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpanKind {
RawInherited,
InlineStyled,
}
#[derive(Resource)]
pub struct JsBridge {
pub ext: std::sync::Arc<crate::ext::ExtRegistry>,
pub ops_rx: OpReceiver,
pub outbound_tx: OutboundSender,
pub nodes: HashMap<NodeId, Entity>,
pub names: crate::names::NameIndex,
pub shared_tags: crate::shared_tags::SharedTags,
pub props_cache: HashMap<NodeId, Box<crate::protocol::props::Props>>,
pub layer_dirty: HashSet<NodeId>,
pub promoted_layers: HashSet<NodeId>,
pub text_styles: HashMap<NodeId, ResolvedTextStyle>,
pub spans: HashMap<NodeId, SpanKind>,
pub detached: HashSet<NodeId>,
pub animated: HashSet<NodeId>,
pub scroll_positions: HashMap<NodeId, Vec2>,
pub siblings: HashMap<NodeId, SiblingLinks>,
pub child_list: HashMap<NodeId, ChildList>,
pub parent_of: HashMap<NodeId, NodeId>,
pub detached_parent: HashMap<NodeId, NodeId>,
pub child_detached: HashMap<NodeId, Vec<NodeId>>,
walk_stack: Vec<NodeId>,
}
#[derive(Clone, Copy, Default)]
pub struct SiblingLinks {
prev: Option<NodeId>,
next: Option<NodeId>,
}
#[derive(Clone, Copy)]
pub struct ChildList {
head: NodeId,
tail: NodeId,
}
impl JsBridge {
pub fn new(ops_rx: OpReceiver, outbound_tx: OutboundSender, root: Entity) -> Self {
let mut nodes = HashMap::new();
nodes.insert(crate::protocol::ROOT_ID, root);
Self {
ext: std::sync::Arc::new(crate::ext::builtin_registry()),
ops_rx,
outbound_tx,
nodes,
names: Default::default(),
shared_tags: crate::shared_tags::SharedTags::default(),
props_cache: HashMap::new(),
layer_dirty: HashSet::new(),
promoted_layers: HashSet::new(),
text_styles: HashMap::new(),
spans: HashMap::new(),
detached: HashSet::new(),
animated: HashSet::new(),
scroll_positions: HashMap::new(),
siblings: HashMap::new(),
child_list: HashMap::new(),
parent_of: HashMap::new(),
detached_parent: HashMap::new(),
child_detached: HashMap::new(),
walk_stack: Vec::new(),
}
}
pub fn is_detached(&self, id: NodeId) -> bool {
!self.detached.is_empty() && self.detached.contains(&id)
}
pub fn attach_detached(&mut self, node: NodeId, parent: NodeId) {
self.detach_detached(node);
self.detached_parent.insert(node, parent);
self.child_detached.entry(parent).or_default().push(node);
}
pub fn detach_detached(&mut self, node: NodeId) {
if let Some(parent) = self.detached_parent.remove(&node)
&& let Some(list) = self.child_detached.get_mut(&parent)
{
list.retain(|&id| id != node);
}
}
pub fn detached_under(&mut self, node: NodeId) -> Vec<NodeId> {
let mut out = Vec::new();
if self.child_detached.is_empty() {
return out;
}
let mut stack = std::mem::take(&mut self.walk_stack);
stack.push(node);
while let Some(n) = stack.pop() {
if let Some(detached) = self.child_detached.remove(&n) {
for d in detached {
self.detached_parent.remove(&d);
out.push(d);
stack.push(d);
}
if self.child_detached.is_empty() {
break;
}
}
stack.extend(self.children_of(n));
}
stack.clear();
self.walk_stack = stack;
out
}
pub fn detach(&mut self, child: NodeId) {
let Some(parent) = self.parent_of.remove(&child) else {
return;
};
let Some(links) = self.siblings.remove(&child) else {
return;
};
if let Some(prev) = links.prev
&& let Some(l) = self.siblings.get_mut(&prev)
{
l.next = links.next;
}
if let Some(next) = links.next
&& let Some(l) = self.siblings.get_mut(&next)
{
l.prev = links.prev;
}
if let Some(list) = self.child_list.get_mut(&parent) {
match (links.prev, links.next) {
(None, None) => {
self.child_list.remove(&parent);
}
(None, Some(next)) => list.head = next,
(Some(prev), None) => list.tail = prev,
(Some(_), Some(_)) => {}
}
}
}
pub fn append_child(&mut self, parent: NodeId, child: NodeId) {
self.detach(child);
self.parent_of.insert(child, parent);
match self.child_list.get_mut(&parent) {
Some(list) => {
let old_tail = list.tail;
if let Some(l) = self.siblings.get_mut(&old_tail) {
l.next = Some(child);
}
self.siblings.insert(
child,
SiblingLinks {
prev: Some(old_tail),
next: None,
},
);
list.tail = child;
}
None => {
self.siblings.insert(child, SiblingLinks::default());
self.child_list.insert(
parent,
ChildList {
head: child,
tail: child,
},
);
}
}
}
pub fn insert_before(&mut self, parent: NodeId, child: NodeId, before: NodeId) {
self.detach(child);
if self.parent_of.get(&before) != Some(&parent) {
self.append_child(parent, child);
return;
}
self.parent_of.insert(child, parent);
let before_links = self
.siblings
.get_mut(&before)
.expect("attached child has sibling links");
let prev = before_links.prev;
before_links.prev = Some(child);
self.siblings.insert(
child,
SiblingLinks {
prev,
next: Some(before),
},
);
match prev {
Some(p) => {
if let Some(l) = self.siblings.get_mut(&p) {
l.next = Some(child);
}
}
None => {
if let Some(list) = self.child_list.get_mut(&parent) {
list.head = child;
}
}
}
}
pub fn children_of(&self, parent: NodeId) -> impl Iterator<Item = NodeId> + '_ {
let mut cursor = self.child_list.get(&parent).map(|l| l.head);
std::iter::from_fn(move || {
let id = cursor?;
cursor = self.siblings.get(&id).and_then(|l| l.next);
Some(id)
})
}
fn forget_node_data(&mut self, id: NodeId) {
let entity = self.nodes.remove(&id);
let props = self.props_cache.remove(&id);
if let (Some(entity), Some(name)) = (entity, props.as_ref().and_then(|p| p.name.as_deref()))
{
self.names.remove(name, entity);
}
self.shared_tags
.forget(id, props.as_ref().and_then(|p| p.shared_tag.as_deref()));
take_if_any(&mut self.layer_dirty, id);
take_if_any(&mut self.promoted_layers, id);
remove_if_any(&mut self.text_styles, id);
remove_if_any(&mut self.spans, id);
take_if_any(&mut self.detached, id);
take_if_any(&mut self.animated, id);
remove_if_any(&mut self.scroll_positions, id);
}
pub fn forget_subtree(&mut self, child: NodeId) {
let mut stack = std::mem::take(&mut self.walk_stack);
stack.push(child);
while let Some(id) = stack.pop() {
self.forget_node_data(id);
if let Some(list) = self.child_list.remove(&id) {
let mut cursor = Some(list.head);
while let Some(kid) = cursor {
self.parent_of.remove(&kid);
cursor = self.siblings.remove(&kid).and_then(|l| l.next);
stack.push(kid);
}
}
}
self.walk_stack = stack;
}
}
fn remove_if_any<V>(map: &mut HashMap<NodeId, V>, id: NodeId) {
if !map.is_empty() {
map.remove(&id);
}
}
fn take_if_any(set: &mut HashSet<NodeId>, id: NodeId) {
if !set.is_empty() {
set.remove(&id);
}
}