use std::{collections::HashMap, mem};
pub use euclid::Rect;
use fxhash::{FxHashMap, FxHashSet};
use tracing::info;
use crate::{
custom_measurer::LayoutMeasurer,
direction::DirectionMode,
display::DisplayMode,
dom_adapter::{DOMAdapter, NodeAreas, NodeKey},
geometry::{Area, Size2D},
node::Node,
prelude::{BoxModel, Gaps},
size::Size,
};
#[derive(PartialEq, Debug, Clone)]
pub enum RootNodeCandidate<Key: NodeKey> {
Valid(Key, FxHashSet<Key>),
None,
}
impl<Key: NodeKey> RootNodeCandidate<Key> {
pub fn take(&mut self) -> Self {
mem::replace(self, Self::None)
}
}
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 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 safe_invalidate(&mut self, node_id: Key, dom_adapter: &mut impl DOMAdapter<Key>) {
if dom_adapter.is_node_valid(&node_id) {
self.invalidate(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);
if RootNodeCandidate::None == self.root_node_candidate {
self.root_node_candidate =
RootNodeCandidate::Valid(node_id, FxHashSet::from_iter([node_id]));
} else if let RootNodeCandidate::Valid(root_candidate, ref mut prev_path) =
&mut self.root_node_candidate
{
if node_id != *root_candidate {
let closest_parent = dom_adapter.closest_common_parent(&node_id, root_candidate);
if let Some((closest_parent, path)) = closest_parent {
prev_path.extend(path);
*root_candidate = closest_parent;
}
}
}
for child in dom_adapter.children_of(&node_id) {
self.check_dirty_dependants(child, dom_adapter, true)
}
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 mut found_node = false;
let mut multiple_children = false;
for child_id in dom_adapter.children_of(&parent_id) {
if found_node {
self.check_dirty_dependants(child_id, dom_adapter, true);
}
if child_id == node_id {
found_node = true;
} else {
multiple_children = true;
}
}
if multiple_children {
if let RootNodeCandidate::Valid(root_candidate, ref mut prev_path) =
&mut self.root_node_candidate
{
let closest_parent =
dom_adapter.closest_common_parent(&parent_id, root_candidate);
if let Some((closest_parent, path)) = closest_parent {
prev_path.extend(path);
*root_candidate = closest_parent;
}
}
}
}
}
}
}
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,
suggested_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, root_path) =
if let RootNodeCandidate::Valid(id, path) = self.root_node_candidate.take() {
(id, path)
} else {
(suggested_root_id, FxHashSet::from_iter([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: suggested_root_area,
inner_area: suggested_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 (root_revalidated, root_areas) = measure_node(
root_id,
&root,
self,
&areas.inner_area,
&areas.inner_area,
measurer,
true,
dom_adapter,
&root_path,
);
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);
}
}
#[allow(clippy::too_many_arguments)]
#[inline(always)]
fn measure_node<Key: NodeKey>(
node_id: Key,
node: &Node,
layout: &mut Torin<Key>,
parent_area: &Area,
available_parent_area: &Area,
measurer: &mut Option<impl LayoutMeasurer<Key>>,
must_cache: bool,
dom_adapter: &mut impl DOMAdapter<Key>,
root_path: &FxHashSet<Key>,
) -> (bool, NodeAreas) {
let must_run = layout.dirty.contains(&node_id) || layout.results.get(&node_id).is_none();
if must_run {
let horizontal_padding = node.padding.horizontal();
let vertical_padding = node.padding.vertical();
let mut area = Rect::new(
available_parent_area.origin,
Size2D::new(horizontal_padding, vertical_padding),
);
area.size.width = node.width.min_max(
area.size.width,
parent_area.size.width,
node.margin.horizontal(),
&node.minimum_width,
&node.maximum_width,
);
area.size.height = node.height.min_max(
area.size.height,
parent_area.size.height,
node.margin.vertical(),
&node.minimum_height,
&node.maximum_height,
);
let skip_inner = if let Some(measurer) = measurer {
let custom_measure =
measurer.measure(node_id, node, &area, parent_area, available_parent_area);
if let Some(new_area) = custom_measure {
if Size::Inner == node.width {
area.size.width = node.width.min_max(
new_area.width(),
parent_area.size.width,
node.margin.horizontal(),
&node.minimum_width,
&node.maximum_width,
);
}
if Size::Inner == node.height {
area.size.height = node.height.min_max(
new_area.height(),
parent_area.size.height,
node.margin.vertical(),
&node.minimum_height,
&node.maximum_height,
);
}
}
custom_measure.is_some()
} else {
false
};
let mut inner_sizes = Size2D::default();
let mut inner_area = {
let mut inner_area = area.box_area(&node.margin);
if Size::Inner == node.width {
inner_area.size.width = available_parent_area.width()
}
if Size::Inner == node.height {
inner_area.size.height = available_parent_area.height()
}
inner_area
};
inner_area.origin.x += node.padding.left();
inner_area.origin.y += node.padding.top();
inner_area.size.width -= horizontal_padding;
inner_area.size.height -= vertical_padding;
let mut available_area = inner_area;
available_area.origin.x += node.offset_x.get();
available_area.origin.y += node.offset_y.get();
let mut measurement_mode = MeasureMode::ParentIsNotCached {
area: &mut area,
inner_area: &mut inner_area,
vertical_padding,
horizontal_padding,
};
if !skip_inner {
measure_inner_nodes(
&node_id,
node,
layout,
&mut available_area,
&mut inner_sizes,
measurer,
must_cache,
&mut measurement_mode,
dom_adapter,
root_path,
);
}
(
must_cache,
NodeAreas {
area,
margin: node.margin,
inner_area,
inner_sizes,
},
)
} else {
let areas = layout.get(node_id).unwrap().clone();
if root_path.contains(&node_id) {
let mut inner_sizes = areas.inner_sizes;
let mut available_area = areas.inner_area;
available_area.origin.x += node.offset_x.get();
available_area.origin.y += node.offset_y.get();
let mut measurement_mode = MeasureMode::ParentIsCached {
inner_area: &areas.inner_area,
};
measure_inner_nodes(
&node_id,
node,
layout,
&mut available_area,
&mut inner_sizes,
measurer,
must_cache,
&mut measurement_mode,
dom_adapter,
root_path,
);
}
(false, areas)
}
}
#[derive(Debug)]
enum MeasureMode<'a> {
ParentIsCached {
inner_area: &'a Area,
},
ParentIsNotCached {
area: &'a mut Area,
inner_area: &'a mut Area,
vertical_padding: f32,
horizontal_padding: f32,
},
}
impl<'a> MeasureMode<'a> {
pub fn inner_area(&'a self) -> &'a Area {
match self {
Self::ParentIsCached { inner_area } => inner_area,
Self::ParentIsNotCached { inner_area, .. } => inner_area,
}
}
}
#[allow(clippy::too_many_arguments)]
#[inline(always)]
fn measure_inner_nodes<Key: NodeKey>(
node_id: &Key,
node: &Node,
layout: &mut Torin<Key>,
available_area: &mut Area,
inner_sizes: &mut Size2D,
measurer: &mut Option<impl LayoutMeasurer<Key>>,
must_cache: bool,
mode: &mut MeasureMode,
dom_adapter: &mut impl DOMAdapter<Key>,
root_path: &FxHashSet<Key>,
) {
let children = dom_adapter.children_of(node_id);
if node.display == DisplayMode::Center {
let child_id = children.first();
if let Some(child_id) = child_id {
let inner_area = *mode.inner_area();
let child_data = dom_adapter.get_node(child_id).unwrap();
let (_, child_areas) = measure_node(
*child_id,
&child_data,
layout,
&inner_area,
available_area,
measurer,
false,
dom_adapter,
root_path,
);
match node.direction {
DirectionMode::Horizontal => {
let new_origin_x =
(inner_area.width() / 2.0) - (child_areas.area.width() / 2.0);
available_area.origin.x = inner_area.min_x() + new_origin_x;
}
DirectionMode::Vertical => {
let new_origin_y =
(inner_area.height() / 2.0) - (child_areas.area.height() / 2.0);
available_area.origin.y = inner_area.min_y() + new_origin_y;
}
DirectionMode::Both => {
let new_origin_x =
(inner_area.width() / 2.0) - (child_areas.area.width() / 2.0);
let new_origin_y =
(inner_area.height() / 2.0) - (child_areas.area.height() / 2.0);
available_area.origin.x = inner_area.min_x() + new_origin_x;
available_area.origin.y = inner_area.min_y() + new_origin_y;
}
}
}
}
for child_id in children {
let inner_area = *mode.inner_area();
let child_data = dom_adapter.get_node(&child_id).unwrap().clone();
let (child_revalidated, child_areas) = measure_node(
child_id,
&child_data,
layout,
&inner_area,
available_area,
measurer,
must_cache,
dom_adapter,
root_path,
);
match node.direction {
DirectionMode::Horizontal => {
available_area.origin.x = child_areas.area.max_x();
available_area.size.width -= child_areas.area.size.width;
if let MeasureMode::ParentIsNotCached {
area,
vertical_padding,
inner_area,
..
} = mode
{
inner_sizes.height = child_areas.area.height();
inner_sizes.width += child_areas.area.width();
if node.height == Size::Inner {
area.size.height = area
.size
.height
.max(child_areas.area.size.height + *vertical_padding);
inner_area.size.height = area.size.height - *vertical_padding;
}
if node.width == Size::Inner {
area.size.width += child_areas.area.size.width;
}
}
}
DirectionMode::Vertical => {
available_area.origin.y = child_areas.area.max_y();
available_area.size.height -= child_areas.area.size.height;
if let MeasureMode::ParentIsNotCached {
area,
horizontal_padding,
inner_area,
..
} = mode
{
inner_sizes.width = child_areas.area.width();
inner_sizes.height += child_areas.area.height();
if node.width == Size::Inner {
area.size.width = area
.size
.width
.max(child_areas.area.size.width + *horizontal_padding);
inner_area.size.width = area.size.width - *horizontal_padding;
}
if node.height == Size::Inner {
area.size.height += child_areas.area.size.height;
}
}
}
DirectionMode::Both => {
available_area.origin.x = child_areas.area.max_x();
available_area.origin.y = child_areas.area.max_y();
available_area.size.width -= child_areas.area.size.width;
available_area.size.height -= child_areas.area.size.height;
if let MeasureMode::ParentIsNotCached { area, .. } = mode {
inner_sizes.width += child_areas.area.width();
inner_sizes.height += child_areas.area.height();
if node.width == Size::Inner {
area.size.width += child_areas.area.size.width;
}
if node.height == Size::Inner {
area.size.height += child_areas.area.size.height;
}
}
}
}
if child_revalidated && must_cache {
layout.cache_node(child_id, child_areas);
}
}
}