use rapidhash::fast::RapidHasher;
use repose_core::{
Brush, Color, Modifier, TextOverflow, View, ViewKind,
animation::{AnimationSpec, Easing},
};
use std::hash::{Hash, Hasher};
pub fn hash_view_content(view: &View) -> u64 {
let mut hasher = RapidHasher::default();
hash_view_kind(&view.kind, &mut hasher);
hash_modifier(&view.modifier, &mut hasher);
if let Some(key) = view.modifier.key {
key.hash(&mut hasher);
}
hasher.finish()
}
pub fn hash_subtree(content_hash: u64, children_hashes: &[u64]) -> u64 {
let mut hasher = RapidHasher::default();
content_hash.hash(&mut hasher);
children_hashes.len().hash(&mut hasher);
for &h in children_hashes {
h.hash(&mut hasher);
}
hasher.finish()
}
fn hash_view_kind(kind: &ViewKind, hasher: &mut impl Hasher) {
std::mem::discriminant(kind).hash(hasher);
match kind {
ViewKind::Text {
text,
color,
font_size,
soft_wrap,
max_lines,
overflow,
font_family,
annotations,
} => {
font_family.hash(hasher);
text.hash(hasher);
hash_color(color, hasher);
((font_size * 100.0) as u32).hash(hasher);
soft_wrap.hash(hasher);
max_lines.hash(hasher);
hash_text_overflow(overflow, hasher);
if let Some(annos) = annotations {
annos.len().hash(hasher);
for span in annos.iter() {
span.start.hash(hasher);
span.end.hash(hasher);
if let Some(c) = &span.style.color {
hash_color(c, hasher);
}
if let Some(fs) = span.style.font_size {
((fs * 100.0) as u32).hash(hasher);
}
}
}
}
ViewKind::Image { handle, tint, fit } => {
handle.hash(hasher);
hash_color(tint, hasher);
std::mem::discriminant(fit).hash(hasher);
}
ViewKind::ScrollV { .. } | ViewKind::ScrollXY { .. } => {
}
ViewKind::OverlayHost
| ViewKind::Box
| ViewKind::Row
| ViewKind::Column
| ViewKind::Stack
| ViewKind::ZStack => {
}
ViewKind::SubcomposeLayout { .. } => {
}
_ => {} }
}
fn hash_modifier(m: &Modifier, hasher: &mut impl Hasher) {
if let Some(s) = &m.size {
((s.width * 100.0) as i32).hash(hasher);
((s.height * 100.0) as i32).hash(hasher);
}
m.width.map(|w| (w * 100.0) as i32).hash(hasher);
m.height.map(|h| (h * 100.0) as i32).hash(hasher);
m.fill_max.hash(hasher);
m.fill_max_w.hash(hasher);
m.fill_max_h.hash(hasher);
m.repaint_boundary.hash(hasher);
m.padding.map(|p| (p * 100.0) as i32).hash(hasher);
if let Some(pv) = &m.padding_values {
((pv.left * 100.0) as i32).hash(hasher);
((pv.right * 100.0) as i32).hash(hasher);
((pv.top * 100.0) as i32).hash(hasher);
((pv.bottom * 100.0) as i32).hash(hasher);
}
m.min_width.map(|v| (v * 100.0) as i32).hash(hasher);
m.min_height.map(|v| (v * 100.0) as i32).hash(hasher);
m.max_width.map(|v| (v * 100.0) as i32).hash(hasher);
m.max_height.map(|v| (v * 100.0) as i32).hash(hasher);
if let Some(bg) = &m.background {
hash_brush(bg, hasher);
}
if let Some(b) = &m.border {
((b.width * 100.0) as i32).hash(hasher);
hash_color(&b.color, hasher);
((b.radius * 100.0) as i32).hash(hasher);
}
m.flex_grow.map(|v| (v * 100.0) as i32).hash(hasher);
m.flex_shrink.map(|v| (v * 100.0) as i32).hash(hasher);
m.flex_basis.map(|v| (v * 100.0) as i32).hash(hasher);
m.flex_wrap.map(|v| std::mem::discriminant(&v)).hash(hasher);
m.flex_dir.map(|v| std::mem::discriminant(&v)).hash(hasher);
m.align_self
.map(|v| std::mem::discriminant(&v))
.hash(hasher);
m.justify_content
.map(|v| std::mem::discriminant(&v))
.hash(hasher);
m.align_items_container
.map(|v| std::mem::discriminant(&v))
.hash(hasher);
m.align_content
.map(|v| std::mem::discriminant(&v))
.hash(hasher);
m.clip_rounded.map(|v| (v * 100.0) as i32).hash(hasher);
if let Some(t) = &m.transform {
((t.translate_x * 100.0) as i32).hash(hasher);
((t.translate_y * 100.0) as i32).hash(hasher);
((t.scale_x * 100.0) as i32).hash(hasher);
((t.scale_y * 100.0) as i32).hash(hasher);
((t.rotate * 1000.0) as i32).hash(hasher);
}
m.alpha.map(|a| (a * 255.0) as u8).hash(hasher);
m.position_type
.map(|v| std::mem::discriminant(&v))
.hash(hasher);
m.offset_left.map(|v| (v * 100.0) as i32).hash(hasher);
m.offset_right.map(|v| (v * 100.0) as i32).hash(hasher);
m.offset_top.map(|v| (v * 100.0) as i32).hash(hasher);
m.offset_bottom.map(|v| (v * 100.0) as i32).hash(hasher);
if let Some(g) = &m.grid {
g.columns.hash(hasher);
((g.row_gap * 100.0) as i32).hash(hasher);
((g.column_gap * 100.0) as i32).hash(hasher);
}
m.grid_col_span.hash(hasher);
m.grid_row_span.hash(hasher);
m.aspect_ratio.map(|v| (v * 100.0) as i32).hash(hasher);
((m.z_index * 100.0) as i32).hash(hasher);
m.render_z_index.map(|v| (v * 100.0) as i32).hash(hasher);
m.input_blocker.hash(hasher);
m.click.hash(hasher);
m.disabled.hash(hasher);
(m.on_action.is_some()).hash(hasher);
(m.on_drag_start.is_some()).hash(hasher);
(m.on_drag_end.is_some()).hash(hasher);
(m.on_drag_enter.is_some()).hash(hasher);
(m.on_drag_over.is_some()).hash(hasher);
(m.on_drag_leave.is_some()).hash(hasher);
(m.on_drop.is_some()).hash(hasher);
if let Some(sc) = &m.state_colors {
hash_color(&sc.default, hasher);
hash_color(&sc.hovered, hasher);
hash_color(&sc.pressed, hasher);
hash_color(&sc.disabled, hasher);
}
if let Some(se) = &m.state_elevation {
((se.default * 100.0) as i32).hash(hasher);
((se.hovered * 100.0) as i32).hash(hasher);
((se.pressed * 100.0) as i32).hash(hasher);
((se.disabled * 100.0) as i32).hash(hasher);
}
if let Some(spec) = &m.animate_content_size {
hash_animation_spec(spec, hasher);
}
m.focus_requester.is_some().hash(hasher);
m.on_focus_changed.is_some().hash(hasher);
}
fn hash_animation_spec(spec: &AnimationSpec, hasher: &mut impl Hasher) {
spec.duration.as_millis().hash(hasher);
hash_easing(&spec.easing, hasher);
spec.delay.as_millis().hash(hasher);
if let Some(spring) = &spec.spring {
((spring.damping_ratio * 100.0) as i32).hash(hasher);
((spring.stiffness * 100.0) as i32).hash(hasher);
}
if let Some(repeat) = &spec.repeat {
repeat.iterations.hash(hasher);
repeat.reverse.hash(hasher);
repeat.delay_between.as_millis().hash(hasher);
}
}
fn hash_easing(easing: &Easing, hasher: &mut impl Hasher) {
std::mem::discriminant(easing).hash(hasher);
if let Easing::SpringCrit { omega } = easing {
((omega * 100.0) as i32).hash(hasher);
}
}
fn hash_color(c: &Color, hasher: &mut impl Hasher) {
c.0.hash(hasher);
c.1.hash(hasher);
c.2.hash(hasher);
c.3.hash(hasher);
}
fn hash_brush(b: &Brush, hasher: &mut impl Hasher) {
std::mem::discriminant(b).hash(hasher);
match b {
Brush::Solid(c) => hash_color(c, hasher),
Brush::Linear {
start,
end,
start_color,
end_color,
} => {
((start.x * 100.0) as i32).hash(hasher);
((start.y * 100.0) as i32).hash(hasher);
((end.x * 100.0) as i32).hash(hasher);
((end.y * 100.0) as i32).hash(hasher);
hash_color(start_color, hasher);
hash_color(end_color, hasher);
}
_ => {} }
}
fn hash_rect(r: &repose_core::Rect, hasher: &mut impl Hasher) {
((r.x * 100.0) as i32).hash(hasher);
((r.y * 100.0) as i32).hash(hasher);
((r.w * 100.0) as i32).hash(hasher);
((r.h * 100.0) as i32).hash(hasher);
}
fn hash_text_overflow(o: &TextOverflow, hasher: &mut impl Hasher) {
std::mem::discriminant(o).hash(hasher);
}
#[cfg(test)]
mod tests {
use super::*;
use repose_core::{Modifier, View, ViewKind};
#[test]
fn test_same_view_same_hash() {
let v1 = View::new(0, ViewKind::Box).modifier(Modifier::new().width(100.0));
let v2 = View::new(0, ViewKind::Box).modifier(Modifier::new().width(100.0));
assert_eq!(hash_view_content(&v1), hash_view_content(&v2));
}
#[test]
fn test_different_view_different_hash() {
let v1 = View::new(0, ViewKind::Box).modifier(Modifier::new().width(100.0));
let v2 = View::new(0, ViewKind::Box).modifier(Modifier::new().width(200.0));
assert_ne!(hash_view_content(&v1), hash_view_content(&v2));
}
#[test]
fn test_text_content_hash() {
let v1 = View::new(
0,
ViewKind::Text {
text: "Hello".to_string(),
color: Color::WHITE,
font_size: 16.0,
soft_wrap: true,
max_lines: None,
overflow: TextOverflow::Visible,
font_family: None,
annotations: None,
},
);
let v2 = View::new(
0,
ViewKind::Text {
text: "Hello".to_string(),
color: Color::WHITE,
font_size: 16.0,
soft_wrap: true,
max_lines: None,
overflow: TextOverflow::Visible,
font_family: None,
annotations: None,
},
);
let v3 = View::new(
0,
ViewKind::Text {
text: "World".to_string(),
color: Color::WHITE,
font_size: 16.0,
soft_wrap: true,
max_lines: None,
overflow: TextOverflow::Visible,
font_family: None,
annotations: None,
},
);
assert_eq!(hash_view_content(&v1), hash_view_content(&v2));
assert_ne!(hash_view_content(&v1), hash_view_content(&v3));
}
}