use std::{collections::HashMap, mem};
pub use euclid::Rect;
use rustc_hash::{FxHashMap, FxHashSet};
use tracing::info;
use crate::{
custom_measurer::LayoutMeasurer,
dom_adapter::{DOMAdapter, NodeAreas, NodeKey},
geometry::{Area, Size2D},
measure::measure_node,
prelude::Gaps,
};
pub struct LayoutMetadata {
pub root_area: Area,
}
#[derive(PartialEq, Debug, Clone)]
pub enum RootNodeCandidate<Key: NodeKey> {
Valid(Key),
None,
}
impl<Key: NodeKey> RootNodeCandidate<Key> {
pub fn take(&mut self) -> Self {
mem::replace(self, Self::None)
}
pub fn propose_new_candidate(
&mut self,
proposed_candidate: &Key,
dom_adapter: &mut impl DOMAdapter<Key>,
) {
if let RootNodeCandidate::Valid(current_candidate) = self {
if current_candidate != proposed_candidate {
let closest_parent =
dom_adapter.closest_common_parent(proposed_candidate, current_candidate);
if let Some(closest_parent) = closest_parent {
*self = RootNodeCandidate::Valid(closest_parent);
}
}
} else {
*self = RootNodeCandidate::Valid(*proposed_candidate)
}
}
}
pub struct Torin<Key: NodeKey> {
pub results: FxHashMap<Key, NodeAreas>,
pub dirty: FxHashSet<Key>,
pub root_node_candidate: RootNodeCandidate<Key>,
}
impl<Key: NodeKey> Default for Torin<Key> {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "dioxus")]
use dioxus_core::Mutations;
#[cfg(feature = "dioxus")]
use dioxus_native_core::prelude::*;
#[cfg(feature = "dioxus")]
impl Torin<NodeId> {
pub fn apply_mutations(
&mut self,
mutations: &Mutations,
dioxus_integration_state: &DioxusState,
dom_adapter: &mut impl DOMAdapter<NodeId>,
) {
use dioxus_core::Mutation;
for mutation in &mutations.edits {
match mutation {
Mutation::SetText { id, .. } => {
self.invalidate(dioxus_integration_state.element_to_node_id(*id));
}
Mutation::InsertAfter { id, m } => {
if *m > 0 {
self.invalidate(dioxus_integration_state.element_to_node_id(*id));
}
}
Mutation::InsertBefore { id, m } => {
if *m > 0 {
self.invalidate(dioxus_integration_state.element_to_node_id(*id));
}
}
Mutation::Remove { id } => {
self.remove(
dioxus_integration_state.element_to_node_id(*id),
dom_adapter,
true,
);
}
Mutation::ReplaceWith { id, m } => {
if *m > 0 {
self.remove(
dioxus_integration_state.element_to_node_id(*id),
dom_adapter,
true,
);
}
}
_ => {}
}
}
}
}
impl<Key: NodeKey> Torin<Key> {
pub fn new() -> Self {
Self {
results: HashMap::default(),
dirty: FxHashSet::default(),
root_node_candidate: RootNodeCandidate::None,
}
}
pub fn size(&self) -> usize {
self.results.len()
}
pub fn reset(&mut self) {
self.root_node_candidate = RootNodeCandidate::None;
self.results.clear();
self.dirty.clear();
}
pub fn get_dirty_nodes(&self) -> &FxHashSet<Key> {
&self.dirty
}
pub fn raw_remove(&mut self, node_id: Key) {
self.results.remove(&node_id);
self.dirty.remove(&node_id);
if let RootNodeCandidate::Valid(id) = self.root_node_candidate {
if id == node_id {
self.root_node_candidate = RootNodeCandidate::None
}
}
}
pub fn remove(
&mut self,
node_id: Key,
dom_adapter: &mut impl DOMAdapter<Key>,
invalidate_parent: bool,
) {
self.raw_remove(node_id);
if invalidate_parent {
self.invalidate(dom_adapter.parent_of(&node_id).unwrap());
}
for child_id in dom_adapter.children_of(&node_id) {
self.remove(child_id, dom_adapter, false);
}
}
pub fn invalidate(&mut self, node_id: Key) {
self.dirty.insert(node_id);
}
pub fn check_dirty_dependants(
&mut self,
node_id: Key,
dom_adapter: &mut impl DOMAdapter<Key>,
ignore: bool,
) {
if (self.dirty.contains(&node_id) && ignore) || !dom_adapter.is_node_valid(&node_id) {
return;
}
self.invalidate(node_id);
self.root_node_candidate
.propose_new_candidate(&node_id, dom_adapter);
let parent_id = dom_adapter.parent_of(&node_id);
if let Some(parent_id) = parent_id {
let parent = dom_adapter.get_node(&parent_id);
if let Some(parent) = parent {
if parent.does_depend_on_inner() {
self.check_dirty_dependants(parent_id, dom_adapter, true);
} else {
let parent_children = dom_adapter.children_of(&parent_id);
let multiple_children = parent_children.len() > 1;
if multiple_children {
self.root_node_candidate
.propose_new_candidate(&parent_id, dom_adapter);
}
}
}
}
}
pub fn get_root_candidate(&self) -> RootNodeCandidate<Key> {
self.root_node_candidate.clone()
}
pub fn find_best_root(&mut self, dom_adapter: &mut impl DOMAdapter<Key>) {
if self.results.is_empty() {
return;
}
for dirty in self.dirty.clone() {
self.check_dirty_dependants(dirty, dom_adapter, false);
}
}
pub fn measure(
&mut self,
suggested_root_id: Key,
root_area: Area,
measurer: &mut Option<impl LayoutMeasurer<Key>>,
dom_adapter: &mut impl DOMAdapter<Key>,
) {
if self.dirty.is_empty() && !self.results.is_empty() {
return;
}
let root_id = if let RootNodeCandidate::Valid(id) = self.root_node_candidate.take() {
id
} else {
suggested_root_id
};
let root_parent = dom_adapter.parent_of(&root_id);
let areas = root_parent
.and_then(|root_parent| self.get(root_parent).cloned())
.unwrap_or(NodeAreas {
area: root_area,
inner_area: root_area,
inner_sizes: Size2D::default(),
margin: Gaps::default(),
});
let root = dom_adapter.get_node(&root_id).unwrap();
let root_height = dom_adapter.height(&root_id).unwrap();
info!(
"Processing {} dirty nodes and {} cached nodes from a height of {}",
self.dirty.len(),
self.results.len(),
root_height
);
let metadata = LayoutMetadata { root_area };
let (root_revalidated, root_areas) = measure_node(
root_id,
&root,
self,
&areas.inner_area,
&areas.inner_area,
measurer,
true,
dom_adapter,
&metadata,
false,
);
if root_revalidated {
self.cache_node(root_id, root_areas);
}
self.dirty.clear();
self.root_node_candidate = RootNodeCandidate::None;
}
pub fn get(&self, node_id: Key) -> Option<&NodeAreas> {
self.results.get(&node_id)
}
pub fn cache_node(&mut self, node_id: Key, areas: NodeAreas) {
self.results.insert(node_id, areas);
}
}