use crate::dirty::DirtyFlags;
use crate::style::Overflow;
use crate::tree::{NodeId, UiTree};
use astrelis_render::Color;
use std::hash::{Hash, Hasher};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct LayoutHash(u64);
impl LayoutHash {
fn hash_dimension(hasher: &mut impl Hasher, dim: &taffy::Dimension) {
match dim {
taffy::Dimension::Length(v) => {
0u8.hash(hasher);
v.to_bits().hash(hasher);
}
taffy::Dimension::Percent(v) => {
1u8.hash(hasher);
v.to_bits().hash(hasher);
}
taffy::Dimension::Auto => {
2u8.hash(hasher);
}
}
}
fn hash_length_percentage(hasher: &mut impl Hasher, lp: &taffy::LengthPercentage) {
match lp {
taffy::LengthPercentage::Length(v) => {
0u8.hash(hasher);
v.to_bits().hash(hasher);
}
taffy::LengthPercentage::Percent(v) => {
1u8.hash(hasher);
v.to_bits().hash(hasher);
}
}
}
fn hash_length_percentage_auto(hasher: &mut impl Hasher, lpa: &taffy::LengthPercentageAuto) {
match lpa {
taffy::LengthPercentageAuto::Length(v) => {
0u8.hash(hasher);
v.to_bits().hash(hasher);
}
taffy::LengthPercentageAuto::Percent(v) => {
1u8.hash(hasher);
v.to_bits().hash(hasher);
}
taffy::LengthPercentageAuto::Auto => {
2u8.hash(hasher);
}
}
}
fn from_style(style: &taffy::Style) -> Self {
let mut hasher = ahash::AHasher::default();
Self::hash_dimension(&mut hasher, &style.size.width);
Self::hash_dimension(&mut hasher, &style.size.height);
Self::hash_dimension(&mut hasher, &style.min_size.width);
Self::hash_dimension(&mut hasher, &style.min_size.height);
Self::hash_dimension(&mut hasher, &style.max_size.width);
Self::hash_dimension(&mut hasher, &style.max_size.height);
Self::hash_length_percentage(&mut hasher, &style.padding.left);
Self::hash_length_percentage(&mut hasher, &style.padding.right);
Self::hash_length_percentage(&mut hasher, &style.padding.top);
Self::hash_length_percentage(&mut hasher, &style.padding.bottom);
Self::hash_length_percentage_auto(&mut hasher, &style.margin.left);
Self::hash_length_percentage_auto(&mut hasher, &style.margin.right);
Self::hash_length_percentage_auto(&mut hasher, &style.margin.top);
Self::hash_length_percentage_auto(&mut hasher, &style.margin.bottom);
Self::hash_length_percentage(&mut hasher, &style.border.left);
Self::hash_length_percentage(&mut hasher, &style.border.right);
Self::hash_length_percentage(&mut hasher, &style.border.top);
Self::hash_length_percentage(&mut hasher, &style.border.bottom);
std::mem::discriminant(&style.flex_direction).hash(&mut hasher);
std::mem::discriminant(&style.flex_wrap).hash(&mut hasher);
style
.align_items
.map(|v| std::mem::discriminant(&v))
.hash(&mut hasher);
style
.align_content
.map(|v| std::mem::discriminant(&v))
.hash(&mut hasher);
style
.align_self
.map(|v| std::mem::discriminant(&v))
.hash(&mut hasher);
style
.justify_content
.map(|v| std::mem::discriminant(&v))
.hash(&mut hasher);
Self::hash_length_percentage(&mut hasher, &style.gap.width);
Self::hash_length_percentage(&mut hasher, &style.gap.height);
style.flex_grow.to_bits().hash(&mut hasher);
style.flex_shrink.to_bits().hash(&mut hasher);
Self::hash_dimension(&mut hasher, &style.flex_basis);
style.aspect_ratio.map(|v| v.to_bits()).hash(&mut hasher);
std::mem::discriminant(&style.position).hash(&mut hasher);
Self::hash_length_percentage_auto(&mut hasher, &style.inset.left);
Self::hash_length_percentage_auto(&mut hasher, &style.inset.right);
Self::hash_length_percentage_auto(&mut hasher, &style.inset.top);
Self::hash_length_percentage_auto(&mut hasher, &style.inset.bottom);
std::mem::discriminant(&style.display).hash(&mut hasher);
LayoutHash(hasher.finish())
}
}
pub struct StyleGuard<'a> {
node_id: NodeId,
tree: &'a mut UiTree,
layout_hash: LayoutHash,
bg_color: Option<Color>,
border_color: Option<Color>,
border_width: f32,
border_radius: f32,
overflow: (Overflow, Overflow),
}
impl<'a> StyleGuard<'a> {
pub(crate) fn new(tree: &'a mut UiTree, node_id: NodeId) -> Self {
let (layout_hash, bg_color, border_color, border_width, border_radius, overflow) =
if let Some(node) = tree.get_node(node_id) {
let style = node.widget.style();
(
LayoutHash::from_style(&style.layout),
style.background_color,
style.border_color,
style.border_width,
style.border_radius,
(style.overflow_x, style.overflow_y),
)
} else {
(
LayoutHash(0),
None,
None,
0.0,
0.0,
(Overflow::default(), Overflow::default()),
)
};
Self {
node_id,
tree,
layout_hash,
bg_color,
border_color,
border_width,
border_radius,
overflow,
}
}
pub fn style_mut(&mut self) -> Option<&mut crate::style::Style> {
self.tree
.get_node_mut(self.node_id)
.map(|node| node.widget.style_mut())
}
pub fn layout_mut(&mut self) -> Option<&mut taffy::Style> {
self.tree
.get_node_mut(self.node_id)
.map(|node| &mut node.widget.style_mut().layout)
}
}
impl<'a> Drop for StyleGuard<'a> {
fn drop(&mut self) {
let Some(node) = self.tree.get_node(self.node_id) else {
return;
};
let style = node.widget.style();
let mut flags = DirtyFlags::NONE;
let after_hash = LayoutHash::from_style(&style.layout);
if after_hash != self.layout_hash {
flags |= DirtyFlags::LAYOUT;
}
if style.background_color != self.bg_color || style.border_color != self.border_color {
flags |= DirtyFlags::COLOR;
}
if (style.border_width - self.border_width).abs() > f32::EPSILON
|| (style.border_radius - self.border_radius).abs() > f32::EPSILON
{
flags |= DirtyFlags::GEOMETRY;
}
if (style.overflow_x, style.overflow_y) != self.overflow {
flags |= DirtyFlags::CLIP;
}
if !flags.is_empty() {
self.tree.mark_dirty_flags(self.node_id, flags);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_layout_hash() {
let style1 = taffy::Style {
size: taffy::Size {
width: taffy::Dimension::Length(100.0),
height: taffy::Dimension::Length(50.0),
},
..Default::default()
};
let style2 = taffy::Style {
size: taffy::Size {
width: taffy::Dimension::Length(100.0),
height: taffy::Dimension::Length(50.0),
},
..Default::default()
};
let style3 = taffy::Style {
size: taffy::Size {
width: taffy::Dimension::Length(200.0),
height: taffy::Dimension::Length(50.0),
},
..Default::default()
};
let hash1 = LayoutHash::from_style(&style1);
let hash2 = LayoutHash::from_style(&style2);
let hash3 = LayoutHash::from_style(&style3);
assert_eq!(hash1, hash2);
assert_ne!(hash1, hash3);
}
#[test]
fn test_layout_hash_flex_fields() {
let style1 = taffy::Style {
flex_grow: 1.0,
..Default::default()
};
let style2 = taffy::Style {
flex_grow: 2.0,
..Default::default()
};
let hash1 = LayoutHash::from_style(&style1);
let hash2 = LayoutHash::from_style(&style2);
assert_ne!(hash1, hash2);
}
#[test]
fn test_layout_hash_aspect_ratio() {
let style1 = taffy::Style {
aspect_ratio: Some(16.0 / 9.0),
..Default::default()
};
let style2 = taffy::Style {
aspect_ratio: None,
..Default::default()
};
let hash1 = LayoutHash::from_style(&style1);
let hash2 = LayoutHash::from_style(&style2);
assert_ne!(hash1, hash2);
}
}