1use ahash::AHasher;
4use repose_core::{Brush, Color, Modifier, TextOverflow, View, ViewKind};
5use std::hash::{Hash, Hasher};
6
7pub fn hash_view_content(view: &View) -> u64 {
10 let mut hasher = AHasher::default();
11
12 hash_view_kind(&view.kind, &mut hasher);
14
15 hash_modifier(&view.modifier, &mut hasher);
17
18 if let Some(key) = view.modifier.key {
20 key.hash(&mut hasher);
21 }
22
23 hasher.finish()
24}
25
26pub fn hash_subtree(content_hash: u64, children_hashes: &[u64]) -> u64 {
29 let mut hasher = AHasher::default();
30 content_hash.hash(&mut hasher);
31 children_hashes.len().hash(&mut hasher);
32 for &h in children_hashes {
33 h.hash(&mut hasher);
34 }
35 hasher.finish()
36}
37
38fn hash_view_kind(kind: &ViewKind, hasher: &mut impl Hasher) {
39 std::mem::discriminant(kind).hash(hasher);
41
42 match kind {
43 ViewKind::Text {
44 text,
45 color,
46 font_size,
47 soft_wrap,
48 max_lines,
49 overflow,
50 } => {
51 text.hash(hasher);
52 hash_color(color, hasher);
53 ((font_size * 100.0) as u32).hash(hasher);
54 soft_wrap.hash(hasher);
55 max_lines.hash(hasher);
56 hash_text_overflow(overflow, hasher);
57 }
58 ViewKind::Button { .. } => {
59 }
62 ViewKind::TextField {
63 state_key, hint, ..
64 } => {
65 state_key.hash(hasher);
66 hint.hash(hasher);
67 }
68 ViewKind::Checkbox { checked, .. } => {
69 checked.hash(hasher);
70 }
71 ViewKind::RadioButton { selected, .. } => {
72 selected.hash(hasher);
73 }
74 ViewKind::Switch { checked, .. } => {
75 checked.hash(hasher);
76 }
77 ViewKind::Slider {
78 value,
79 min,
80 max,
81 step,
82 ..
83 } => {
84 ((value * 1000.0) as i32).hash(hasher);
85 ((min * 1000.0) as i32).hash(hasher);
86 ((max * 1000.0) as i32).hash(hasher);
87 step.map(|s| (s * 1000.0) as i32).hash(hasher);
88 }
89 ViewKind::RangeSlider {
90 start,
91 end,
92 min,
93 max,
94 step,
95 ..
96 } => {
97 ((start * 1000.0) as i32).hash(hasher);
98 ((end * 1000.0) as i32).hash(hasher);
99 ((min * 1000.0) as i32).hash(hasher);
100 ((max * 1000.0) as i32).hash(hasher);
101 step.map(|s| (s * 1000.0) as i32).hash(hasher);
102 }
103 ViewKind::ProgressBar {
104 value,
105 min,
106 max,
107 circular,
108 } => {
109 ((value * 1000.0) as i32).hash(hasher);
110 ((min * 1000.0) as i32).hash(hasher);
111 ((max * 1000.0) as i32).hash(hasher);
112 circular.hash(hasher);
113 }
114 ViewKind::Image { handle, tint, fit } => {
115 handle.hash(hasher);
116 hash_color(tint, hasher);
117 std::mem::discriminant(fit).hash(hasher);
118 }
119 ViewKind::Ellipse { rect, color } => {
120 hash_rect(rect, hasher);
121 hash_color(color, hasher);
122 }
123 ViewKind::EllipseBorder { rect, color, width } => {
124 hash_rect(rect, hasher);
125 hash_color(color, hasher);
126 ((width * 100.0) as u32).hash(hasher);
127 }
128 ViewKind::ScrollV { .. } | ViewKind::ScrollXY { .. } => {
129 }
131 ViewKind::OverlayHost
132 | ViewKind::Surface
133 | ViewKind::Box
134 | ViewKind::Row
135 | ViewKind::Column
136 | ViewKind::Stack => {
137 }
139 }
140}
141
142fn hash_modifier(m: &Modifier, hasher: &mut impl Hasher) {
143 if let Some(s) = &m.size {
145 ((s.width * 100.0) as i32).hash(hasher);
146 ((s.height * 100.0) as i32).hash(hasher);
147 }
148 m.width.map(|w| (w * 100.0) as i32).hash(hasher);
149 m.height.map(|h| (h * 100.0) as i32).hash(hasher);
150 m.fill_max.hash(hasher);
151 m.fill_max_w.hash(hasher);
152 m.fill_max_h.hash(hasher);
153 m.repaint_boundary.hash(hasher);
154
155 m.padding.map(|p| (p * 100.0) as i32).hash(hasher);
157 if let Some(pv) = &m.padding_values {
158 ((pv.left * 100.0) as i32).hash(hasher);
159 ((pv.right * 100.0) as i32).hash(hasher);
160 ((pv.top * 100.0) as i32).hash(hasher);
161 ((pv.bottom * 100.0) as i32).hash(hasher);
162 }
163
164 m.min_width.map(|v| (v * 100.0) as i32).hash(hasher);
166 m.min_height.map(|v| (v * 100.0) as i32).hash(hasher);
167 m.max_width.map(|v| (v * 100.0) as i32).hash(hasher);
168 m.max_height.map(|v| (v * 100.0) as i32).hash(hasher);
169
170 if let Some(bg) = &m.background {
172 hash_brush(bg, hasher);
173 }
174
175 if let Some(b) = &m.border {
177 ((b.width * 100.0) as i32).hash(hasher);
178 hash_color(&b.color, hasher);
179 ((b.radius * 100.0) as i32).hash(hasher);
180 }
181
182 m.flex_grow.map(|v| (v * 100.0) as i32).hash(hasher);
184 m.flex_shrink.map(|v| (v * 100.0) as i32).hash(hasher);
185 m.flex_basis.map(|v| (v * 100.0) as i32).hash(hasher);
186 m.flex_wrap.map(|v| std::mem::discriminant(&v)).hash(hasher);
187 m.flex_dir.map(|v| std::mem::discriminant(&v)).hash(hasher);
188 m.align_self
189 .map(|v| std::mem::discriminant(&v))
190 .hash(hasher);
191 m.justify_content
192 .map(|v| std::mem::discriminant(&v))
193 .hash(hasher);
194 m.align_items_container
195 .map(|v| std::mem::discriminant(&v))
196 .hash(hasher);
197 m.align_content
198 .map(|v| std::mem::discriminant(&v))
199 .hash(hasher);
200
201 m.clip_rounded.map(|v| (v * 100.0) as i32).hash(hasher);
203
204 if let Some(t) = &m.transform {
206 ((t.translate_x * 100.0) as i32).hash(hasher);
207 ((t.translate_y * 100.0) as i32).hash(hasher);
208 ((t.scale_x * 100.0) as i32).hash(hasher);
209 ((t.scale_y * 100.0) as i32).hash(hasher);
210 ((t.rotate * 1000.0) as i32).hash(hasher);
211 }
212
213 m.alpha.map(|a| (a * 255.0) as u8).hash(hasher);
215
216 m.position_type
218 .map(|v| std::mem::discriminant(&v))
219 .hash(hasher);
220 m.offset_left.map(|v| (v * 100.0) as i32).hash(hasher);
221 m.offset_right.map(|v| (v * 100.0) as i32).hash(hasher);
222 m.offset_top.map(|v| (v * 100.0) as i32).hash(hasher);
223 m.offset_bottom.map(|v| (v * 100.0) as i32).hash(hasher);
224
225 if let Some(g) = &m.grid {
227 g.columns.hash(hasher);
228 ((g.row_gap * 100.0) as i32).hash(hasher);
229 ((g.column_gap * 100.0) as i32).hash(hasher);
230 }
231 m.grid_col_span.hash(hasher);
232 m.grid_row_span.hash(hasher);
233
234 m.aspect_ratio.map(|v| (v * 100.0) as i32).hash(hasher);
236
237 ((m.z_index * 100.0) as i32).hash(hasher);
239 m.render_z_index.map(|v| (v * 100.0) as i32).hash(hasher);
240 m.input_blocker.hash(hasher);
241
242 m.click.hash(hasher);
244 (m.on_action.is_some()).hash(hasher);
245
246 (m.on_drag_start.is_some()).hash(hasher);
247 (m.on_drag_end.is_some()).hash(hasher);
248 (m.on_drag_enter.is_some()).hash(hasher);
249 (m.on_drag_over.is_some()).hash(hasher);
250 (m.on_drag_leave.is_some()).hash(hasher);
251 (m.on_drop.is_some()).hash(hasher);
252}
253
254fn hash_color(c: &Color, hasher: &mut impl Hasher) {
255 c.0.hash(hasher);
256 c.1.hash(hasher);
257 c.2.hash(hasher);
258 c.3.hash(hasher);
259}
260
261fn hash_brush(b: &Brush, hasher: &mut impl Hasher) {
262 std::mem::discriminant(b).hash(hasher);
263 match b {
264 Brush::Solid(c) => hash_color(c, hasher),
265 Brush::Linear {
266 start,
267 end,
268 start_color,
269 end_color,
270 } => {
271 ((start.x * 100.0) as i32).hash(hasher);
272 ((start.y * 100.0) as i32).hash(hasher);
273 ((end.x * 100.0) as i32).hash(hasher);
274 ((end.y * 100.0) as i32).hash(hasher);
275 hash_color(start_color, hasher);
276 hash_color(end_color, hasher);
277 }
278 }
279}
280
281fn hash_rect(r: &repose_core::Rect, hasher: &mut impl Hasher) {
282 ((r.x * 100.0) as i32).hash(hasher);
283 ((r.y * 100.0) as i32).hash(hasher);
284 ((r.w * 100.0) as i32).hash(hasher);
285 ((r.h * 100.0) as i32).hash(hasher);
286}
287
288fn hash_text_overflow(o: &TextOverflow, hasher: &mut impl Hasher) {
289 std::mem::discriminant(o).hash(hasher);
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295 use repose_core::{Modifier, View, ViewKind};
296
297 #[test]
298 fn test_same_view_same_hash() {
299 let v1 = View::new(0, ViewKind::Box).modifier(Modifier::new().width(100.0));
300 let v2 = View::new(0, ViewKind::Box).modifier(Modifier::new().width(100.0));
301
302 assert_eq!(hash_view_content(&v1), hash_view_content(&v2));
303 }
304
305 #[test]
306 fn test_different_view_different_hash() {
307 let v1 = View::new(0, ViewKind::Box).modifier(Modifier::new().width(100.0));
308 let v2 = View::new(0, ViewKind::Box).modifier(Modifier::new().width(200.0));
309
310 assert_ne!(hash_view_content(&v1), hash_view_content(&v2));
311 }
312
313 #[test]
314 fn test_text_content_hash() {
315 let v1 = View::new(
316 0,
317 ViewKind::Text {
318 text: "Hello".to_string(),
319 color: Color::WHITE,
320 font_size: 16.0,
321 soft_wrap: true,
322 max_lines: None,
323 overflow: TextOverflow::Visible,
324 },
325 );
326 let v2 = View::new(
327 0,
328 ViewKind::Text {
329 text: "Hello".to_string(),
330 color: Color::WHITE,
331 font_size: 16.0,
332 soft_wrap: true,
333 max_lines: None,
334 overflow: TextOverflow::Visible,
335 },
336 );
337 let v3 = View::new(
338 0,
339 ViewKind::Text {
340 text: "World".to_string(),
341 color: Color::WHITE,
342 font_size: 16.0,
343 soft_wrap: true,
344 max_lines: None,
345 overflow: TextOverflow::Visible,
346 },
347 );
348
349 assert_eq!(hash_view_content(&v1), hash_view_content(&v2));
350 assert_ne!(hash_view_content(&v1), hash_view_content(&v3));
351 }
352}