1use rapidhash::fast::RapidHasher;
4use repose_core::{
5 Brush, Color, Dp, Modifier, Px, Sp, TextOverflow, View, ViewKind,
6 animation::{AnimationSpec, Easing},
7 scroll::ScrollBinding,
8 text::{Shadow, SpanStyle, TextStyle},
9};
10use std::hash::{Hash, Hasher};
11
12#[allow(dead_code)]
13fn hash_view_content_inner(view: &View, hasher: &mut impl Hasher) {
14 view.id.hash(hasher);
15 hash_view_kind(&view.kind, hasher);
16 hash_modifier(&view.modifier, hasher);
17 view.scope_key.hash(hasher);
18 if let Some(semantics) = &view.semantics {
19 1u8.hash(hasher);
20 std::mem::discriminant(&semantics.role).hash(hasher);
21 semantics.label.hash(hasher);
22 semantics.focused.hash(hasher);
23 semantics.enabled.hash(hasher);
24 semantics.selectable_group.hash(hasher);
25 semantics.checked.hash(hasher);
26 semantics.selected.hash(hasher);
27 semantics.value.hash(hasher);
28 } else {
29 0u8.hash(hasher);
30 }
31 match view.modifier.key {
32 Some(key) => {
33 1u8.hash(hasher);
34 key.hash(hasher);
35 }
36 None => 0u8.hash(hasher),
37 }
38}
39
40pub fn hash_view_content(view: &View) -> u64 {
41 hash_view_facets(view).combined()
42}
43
44#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
45pub struct ViewHashes {
46 pub layout: u64,
47 pub measurement: u64,
48 pub paint: u64,
49 pub semantics: u64,
50}
51
52impl ViewHashes {
53 pub fn combined(self) -> u64 {
54 let mut hasher = RapidHasher::default();
55 0x5245_504f_5345_2d48u64.hash(&mut hasher);
56 self.layout.hash(&mut hasher);
57 self.measurement.hash(&mut hasher);
58 self.paint.hash(&mut hasher);
59 self.semantics.hash(&mut hasher);
60 hasher.finish()
61 }
62}
63
64#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
65pub struct SubtreeHashes {
66 pub layout: u64,
67 pub measurement: u64,
68 pub paint: u64,
69 pub semantics: u64,
70}
71
72impl SubtreeHashes {
73 pub fn combined(self) -> u64 {
74 let mut hasher = RapidHasher::default();
75 0x5245_504f_5345_2d53u64.hash(&mut hasher);
76 self.layout.hash(&mut hasher);
77 self.measurement.hash(&mut hasher);
78 self.paint.hash(&mut hasher);
79 self.semantics.hash(&mut hasher);
80 hasher.finish()
81 }
82}
83
84pub(crate) struct SubtreeHashBuilder {
85 layout: RapidHasher<'static>,
86 measurement: RapidHasher<'static>,
87 paint: RapidHasher<'static>,
88 semantics: RapidHasher<'static>,
89}
90
91impl SubtreeHashBuilder {
92 pub fn new(child_count: usize) -> Self {
93 let mut builder = Self {
94 layout: RapidHasher::default(),
95 measurement: RapidHasher::default(),
96 paint: RapidHasher::default(),
97 semantics: RapidHasher::default(),
98 };
99 builder.layout.write_u64(0x5355_4254_5245_45);
100 builder.measurement.write_u64(0x534d_4541_5355_5245);
101 builder.paint.write_u64(0x5041_494e_545f_5355);
102 builder.semantics.write_u64(0x5345_4d41_4e54_4943);
103 builder.layout.write_usize(child_count);
104 builder.measurement.write_usize(child_count);
105 builder.paint.write_usize(child_count);
106 builder.semantics.write_usize(child_count);
107 builder
108 }
109
110 pub fn push(&mut self, child: SubtreeHashes) {
111 self.layout.write_u64(child.layout);
112 self.measurement.write_u64(child.measurement);
113 self.paint.write_u64(child.paint);
114 self.semantics.write_u64(child.semantics);
115 }
116
117 pub fn finish(mut self, content: ViewHashes) -> SubtreeHashes {
118 self.layout.write_u64(content.layout);
119 self.measurement.write_u64(content.measurement);
120 self.paint.write_u64(content.paint);
121 self.semantics.write_u64(content.semantics);
122 SubtreeHashes {
123 layout: self.layout.finish(),
124 measurement: self.measurement.finish(),
125 paint: self.paint.finish(),
126 semantics: self.semantics.finish(),
127 }
128 }
129}
130
131const FACET_LAYOUT: u8 = 1;
132const FACET_MEASUREMENT: u8 = 2;
133const FACET_PAINT: u8 = 4;
134const FACET_SEMANTICS: u8 = 8;
135const FACET_LAYOUT_MEASUREMENT: u8 = FACET_LAYOUT | FACET_MEASUREMENT;
136const FACET_LAYOUT_PAINT: u8 = FACET_LAYOUT | FACET_PAINT;
137const FACET_LAYOUT_MEASUREMENT_PAINT: u8 = FACET_LAYOUT | FACET_MEASUREMENT | FACET_PAINT;
138const FACET_ALL: u8 = FACET_LAYOUT | FACET_MEASUREMENT | FACET_PAINT | FACET_SEMANTICS;
139
140type FacetHasher = RapidHasher<'static>;
141
142#[derive(Default)]
143struct FacetHashers {
144 hashers: [FacetHasher; 4],
145}
146
147impl FacetHashers {
148 fn hash(&mut self, mask: u8, write: impl Fn(&mut FacetHasher)) {
149 for (index, hasher) in self.hashers.iter_mut().enumerate() {
150 if mask & (1 << index) != 0 {
151 write(hasher);
152 }
153 }
154 }
155
156 fn finish(self) -> ViewHashes {
157 ViewHashes {
158 layout: self.hashers[0].finish(),
159 measurement: self.hashers[1].finish(),
160 paint: self.hashers[2].finish(),
161 semantics: self.hashers[3].finish(),
162 }
163 }
164}
165
166pub fn hash_view_facets(view: &View) -> ViewHashes {
167 let mut hashers = FacetHashers::default();
168 hashers.hash(FACET_ALL, |hasher| {
169 0x5245_504f_5345_2d56u64.hash(hasher);
170 });
171 facet_hash_view_kind(&view.kind, &mut hashers);
172 facet_hash_modifier(&view.modifier, &mut hashers);
173 hashers.hash(FACET_LAYOUT_PAINT, |hasher| view.id.hash(hasher));
174 hashers.hash(FACET_ALL ^ FACET_SEMANTICS, |hasher| {
175 view.scope_key.hash(hasher);
176 });
177 facet_hash_semantics(view.semantics.as_ref(), &mut hashers, FACET_SEMANTICS);
178 hashers.finish()
179}
180
181pub fn hash_view_layout(view: &View) -> u64 {
182 hash_view_facets(view).layout
183}
184
185pub fn hash_view_measurement(view: &View) -> u64 {
186 hash_view_facets(view).measurement
187}
188
189pub fn hash_view_paint(view: &View) -> u64 {
190 hash_view_facets(view).paint
191}
192
193pub fn hash_view_semantics(view: &View) -> u64 {
194 hash_view_facets(view).semantics
195}
196
197pub fn hash_subtree_facets(content: ViewHashes, children: &[SubtreeHashes]) -> SubtreeHashes {
198 let mut builder = SubtreeHashBuilder::new(children.len());
199 for child in children {
200 builder.push(*child);
201 }
202 builder.finish(content)
203}
204
205fn facet_hash_view_kind(kind: &ViewKind, hashers: &mut FacetHashers) {
206 hashers.hash(FACET_ALL, |hasher| {
207 std::mem::discriminant(kind).hash(hasher);
208 });
209 match kind {
210 ViewKind::Text {
211 text,
212 color,
213 font_size,
214 soft_wrap,
215 max_lines,
216 overflow,
217 font_family,
218 annotations,
219 text_align,
220 font_weight,
221 font_style,
222 text_decoration,
223 letter_spacing,
224 line_height,
225 url,
226 font_variation_settings,
227 draw_style,
228 } => {
229 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| text.hash(h));
230 hashers.hash(FACET_PAINT, |h| hash_color(color, h));
231 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| hash_sp(*font_size, h));
232 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| soft_wrap.hash(h));
233 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| max_lines.hash(h));
234 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| {
235 hash_text_overflow(overflow, h)
236 });
237 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| font_family.hash(h));
238 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| text_align.hash(h));
239 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| font_weight.0.hash(h));
240 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| font_style.hash(h));
241 hashers.hash(FACET_PAINT, |h| hash_text_decoration(text_decoration, h));
242 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| hash_sp(*letter_spacing, h));
243 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| hash_sp(*line_height, h));
244 hashers.hash(FACET_PAINT, |h| url.hash(h));
245 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| {
246 font_variation_settings.hash(h)
247 });
248 hashers.hash(FACET_LAYOUT_MEASUREMENT_PAINT, |h| {
249 hash_draw_style(draw_style, h)
250 });
251 if let Some(annotations) = annotations {
252 hashers.hash(FACET_LAYOUT_MEASUREMENT_PAINT, |h| {
253 1u8.hash(h);
254 annotations.len().hash(h);
255 for span in annotations.iter() {
256 span.start.hash(h);
257 span.end.hash(h);
258 }
259 });
260 for span in annotations.iter() {
261 facet_hash_span_style(&span.style, hashers, FACET_ALL);
262 if let Some(url) = &span.url {
263 hashers.hash(FACET_PAINT, |h| url.hash(h));
264 } else {
265 hashers.hash(FACET_PAINT, |h| 0u8.hash(h));
266 }
267 }
268 } else {
269 hashers.hash(FACET_LAYOUT_MEASUREMENT_PAINT, |h| 0u8.hash(h));
270 }
271 }
272 ViewKind::Image {
273 handle,
274 tint,
275 fit,
276 filter,
277 source_rect,
278 alignment,
279 } => {
280 hashers.hash(FACET_LAYOUT_MEASUREMENT_PAINT, |h| handle.hash(h));
281 hashers.hash(FACET_MEASUREMENT, |h| {
285 repose_core::image_intrinsic_size(*handle).hash(h)
286 });
287 hashers.hash(FACET_PAINT, |h| hash_color(tint, h));
288 hashers.hash(FACET_PAINT, |h| std::mem::discriminant(fit).hash(h));
289 hashers.hash(FACET_PAINT, |h| std::mem::discriminant(filter).hash(h));
290 hashers.hash(FACET_PAINT, |h| source_rect.hash(h));
291 hashers.hash(FACET_PAINT, |h| std::mem::discriminant(alignment).hash(h));
292 }
293 ViewKind::Box
294 | ViewKind::Row
295 | ViewKind::Column
296 | ViewKind::ZStack
297 | ViewKind::OverlayHost
298 | ViewKind::SubcomposeLayout { .. } => {}
299 _ => {}
300 }
301}
302
303fn facet_hash_modifier(modifier: &Modifier, hashers: &mut FacetHashers) {
304 macro_rules! hash_value {
305 ($mask:expr, $value:expr) => {
306 hashers.hash($mask, |hasher| $value.hash(hasher));
307 };
308 }
309 macro_rules! hash_f32_value {
310 ($mask:expr, $value:expr) => {
311 hashers.hash($mask, |hasher| hash_f32($value, hasher));
312 };
313 }
314 macro_rules! hash_opt_f32_value {
315 ($mask:expr, $value:expr) => {
316 hashers.hash($mask, |hasher| hash_opt_f32($value, hasher));
317 };
318 }
319 macro_rules! hash_opt_dp_value {
320 ($mask:expr, $value:expr) => {
321 hashers.hash($mask, |hasher| hash_opt_dp($value, hasher));
322 };
323 }
324
325 hash_value!(FACET_LAYOUT_PAINT, modifier.key);
326 match modifier.size {
327 Some(size) => {
328 hashers.hash(FACET_LAYOUT, |h| {
329 hash_dp(size.width, h);
330 hash_dp(size.height, h);
331 });
332 }
333 None => hashers.hash(FACET_LAYOUT, |h| 0u8.hash(h)),
334 }
335 hash_opt_dp_value!(FACET_LAYOUT, modifier.width);
336 hash_opt_dp_value!(FACET_LAYOUT, modifier.height);
337 match modifier.required_size {
338 Some(size) => hashers.hash(FACET_LAYOUT, |h| {
339 hash_dp(size.width, h);
340 hash_dp(size.height, h);
341 }),
342 None => hashers.hash(FACET_LAYOUT, |h| 0u8.hash(h)),
343 }
344 hash_opt_dp_value!(FACET_LAYOUT, modifier.required_min_width);
345 hash_opt_dp_value!(FACET_LAYOUT, modifier.required_max_width);
346 hash_opt_dp_value!(FACET_LAYOUT, modifier.required_min_height);
347 hash_opt_dp_value!(FACET_LAYOUT, modifier.required_max_height);
348 hash_opt_dp_value!(FACET_LAYOUT, modifier.default_min_width);
349 hash_opt_dp_value!(FACET_LAYOUT, modifier.default_min_height);
350 hash_opt_f32_value!(FACET_LAYOUT, modifier.fill_max);
351 hash_opt_f32_value!(FACET_LAYOUT, modifier.fill_max_w);
352 hash_opt_f32_value!(FACET_LAYOUT, modifier.fill_max_h);
353 hashers.hash(FACET_PAINT, |h| modifier.repaint_boundary.hash(h));
354 hash_opt_dp_value!(FACET_LAYOUT, modifier.padding);
355 match modifier.padding_values {
356 Some(padding) => hashers.hash(FACET_LAYOUT, |h| {
357 hash_dp(padding.left, h);
358 hash_dp(padding.right, h);
359 hash_dp(padding.top, h);
360 hash_dp(padding.bottom, h);
361 }),
362 None => hashers.hash(FACET_LAYOUT, |h| 0u8.hash(h)),
363 }
364 hash_opt_dp_value!(FACET_LAYOUT, modifier.min_width);
365 hash_opt_dp_value!(FACET_LAYOUT, modifier.min_height);
366 hash_opt_dp_value!(FACET_LAYOUT, modifier.max_width);
367 hash_opt_dp_value!(FACET_LAYOUT, modifier.max_height);
368 if let Some(background) = &modifier.background {
369 hashers.hash(FACET_PAINT, |h| hash_brush(background, h));
370 } else {
371 hashers.hash(FACET_PAINT, |h| 0u8.hash(h));
372 }
373 if let Some(border) = &modifier.border {
374 hashers.hash(FACET_PAINT, |h| {
375 hash_dp(border.width, h);
376 hash_brush(&border.brush, h);
377 for radius in border.radius {
378 hash_dp(radius, h);
379 }
380 });
381 } else {
382 hashers.hash(FACET_PAINT, |h| 0u8.hash(h));
383 }
384 hash_opt_f32_value!(FACET_LAYOUT, modifier.flex_grow);
385 hash_opt_f32_value!(FACET_LAYOUT, modifier.flex_shrink);
386 hash_opt_dp_value!(FACET_LAYOUT, modifier.flex_basis);
387 hashers.hash(FACET_LAYOUT, |h| {
388 modifier
389 .flex_wrap
390 .map(|value| std::mem::discriminant(&value))
391 .hash(h);
392 modifier.flex_basis_content.hash(h);
393 modifier.flex_line_count.hash(h);
394 modifier
395 .flex_dir
396 .map(|value| std::mem::discriminant(&value))
397 .hash(h);
398 });
399 hashers.hash(FACET_LAYOUT, |h| {
400 modifier
401 .align_self
402 .map(|value| (value.keyword as u8, value.safety as u8))
403 .hash(h);
404 modifier
405 .justify_content
406 .map(|value| (value.keyword as u8, value.safety as u8))
407 .hash(h);
408 modifier
409 .align_items_container
410 .map(|value| (value.keyword as u8, value.safety as u8))
411 .hash(h);
412 modifier
413 .align_content
414 .map(|value| (value.keyword as u8, value.safety as u8))
415 .hash(h);
416 modifier.baseline_align.hash(h);
417 });
418 if let Some(radius) = modifier.clip_rounded {
419 hashers.hash(FACET_PAINT, |h| {
420 for value in radius {
421 hash_dp(value, h);
422 }
423 });
424 } else {
425 hashers.hash(FACET_PAINT, |h| 0u8.hash(h));
426 }
427 if let Some(transform) = &modifier.transform {
428 hashers.hash(FACET_PAINT, |h| {
429 hash_f32(transform.translate_x, h);
430 hash_f32(transform.translate_y, h);
431 hash_f32(transform.scale_x, h);
432 hash_f32(transform.scale_y, h);
433 hash_f32(transform.rotate, h);
434 hash_f32(transform.shear_x, h);
435 hash_f32(transform.shear_y, h);
436 hash_f32(transform.origin_x, h);
437 hash_f32(transform.origin_y, h);
438 for value in transform.perspective {
439 hash_f32(value, h);
440 }
441 });
442 } else {
443 hashers.hash(FACET_PAINT, |h| 0u8.hash(h));
444 }
445 hash_opt_f32_value!(FACET_PAINT, modifier.alpha);
446 hashers.hash(FACET_LAYOUT, |h| {
447 modifier
448 .position_type
449 .map(|value| std::mem::discriminant(&value))
450 .hash(h);
451 });
452 hash_opt_dp_value!(FACET_LAYOUT, modifier.offset_left);
453 hash_opt_dp_value!(FACET_LAYOUT, modifier.offset_right);
454 hash_opt_dp_value!(FACET_LAYOUT, modifier.offset_top);
455 hash_opt_dp_value!(FACET_LAYOUT, modifier.offset_bottom);
456 if let Some(grid) = &modifier.grid {
457 hashers.hash(FACET_LAYOUT, |h| {
458 grid.columns.hash(h);
459 hash_dp(grid.row_gap, h);
460 hash_dp(grid.column_gap, h);
461 });
462 } else {
463 hashers.hash(FACET_LAYOUT, |h| 0u8.hash(h));
464 }
465 hashers.hash(FACET_LAYOUT, |h| {
466 modifier.grid_col_span.hash(h);
467 modifier.grid_row_span.hash(h);
468 });
469 hash_opt_f32_value!(FACET_LAYOUT, modifier.aspect_ratio);
470 hashers.hash(FACET_LAYOUT, |h| modifier.intrinsic_width.hash(h));
471 hashers.hash(FACET_LAYOUT, |h| modifier.intrinsic_height.hash(h));
472 hash_opt_dp_value!(FACET_LAYOUT, modifier.fit_content_width);
473 hash_opt_dp_value!(FACET_LAYOUT, modifier.fit_content_height);
474 hashers.hash(FACET_LAYOUT, |h| match modifier.contain {
475 None => 0u8.hash(h),
476 Some(value) if value == taffy::Contain::CONTENT => 3u8.hash(h),
477 Some(value) if value == taffy::Contain::LAYOUT => 1u8.hash(h),
478 Some(value) if value == taffy::Contain::PAINT => 2u8.hash(h),
479 Some(_) => 4u8.hash(h),
480 });
481 hash_f32_value!(FACET_PAINT, modifier.z_index);
482 hash_opt_f32_value!(FACET_PAINT, modifier.render_z_index);
483 hashers.hash(FACET_PAINT, |h| {
484 modifier.input_blocker.hash(h);
485 modifier.click.hash(h);
486 modifier.disabled.hash(h);
487 modifier.focusable.hash(h);
488 modifier.hit_passthrough.hash(h);
489 });
490 hashers.hash(FACET_LAYOUT, |h| modifier.propagate_min.hash(h));
491 hashers.hash(FACET_PAINT, |h| modifier.focus_group.hash(h));
492 if let Some(blur) = &modifier.blur {
493 hashers.hash(FACET_PAINT, |h| {
494 hash_dp(blur.radius_x, h);
495 hash_dp(blur.radius_y, h);
496 std::mem::discriminant(&blur.edge_treatment).hash(h);
497 });
498 } else {
499 hashers.hash(FACET_PAINT, |h| 0u8.hash(h));
500 }
501 facet_hash_scroll_binding(modifier.scroll.as_ref(), hashers);
502 facet_hash_nested_scroll_connection(modifier.nested_scroll_connection.as_ref(), hashers);
503 facet_hash_scrollbar_style(modifier.scrollbar_style.as_ref(), hashers);
504 hashers.hash(FACET_PAINT, |h| {
505 modifier
506 .overflow
507 .map(|value| std::mem::discriminant(&value))
508 .hash(h);
509 });
510 if let Some(rect) = modifier.clip_rect {
511 hashers.hash(FACET_PAINT, |h| {
512 hash_dp(rect.left, h);
513 hash_dp(rect.top, h);
514 hash_dp(rect.right, h);
515 hash_dp(rect.bottom, h);
516 std::mem::discriminant(&rect.op).hash(h);
517 });
518 } else {
519 hashers.hash(FACET_PAINT, |h| 0u8.hash(h));
520 }
521 hash_opt_dp_value!(FACET_LAYOUT, modifier.gap);
522 hash_opt_dp_value!(FACET_LAYOUT, modifier.row_gap);
523 hash_opt_dp_value!(FACET_LAYOUT, modifier.column_gap);
524 hash_opt_dp_value!(FACET_LAYOUT, modifier.margin_top);
525 hash_opt_dp_value!(FACET_LAYOUT, modifier.margin_left);
526 hash_opt_dp_value!(FACET_LAYOUT, modifier.margin_right);
527 hash_opt_dp_value!(FACET_LAYOUT, modifier.margin_bottom);
528 hash_opt_f32_value!(FACET_PAINT, modifier.graphics_layer);
529 if let Some(shadow) = &modifier.shadow {
530 hashers.hash(FACET_PAINT, |h| {
531 hash_dp(shadow.blur_radius, h);
532 hash_dp(shadow.offset_y, h);
533 hash_color(&shadow.color, h);
534 });
535 } else {
536 hashers.hash(FACET_PAINT, |h| 0u8.hash(h));
537 }
538 if let Some(radius) = modifier.shadow_shape {
539 hashers.hash(FACET_PAINT, |h| {
540 for value in radius {
541 hash_dp(value, h);
542 }
543 });
544 } else {
545 hashers.hash(FACET_PAINT, |h| 0u8.hash(h));
546 }
547 facet_hash_semantics(modifier.semantics.as_ref(), hashers, FACET_SEMANTICS);
548 hashers.hash(FACET_PAINT, |h| {
549 hash_cursor(&modifier.cursor, h);
550 modifier.painter.is_some().hash(h);
551 modifier.text_selection.is_some().hash(h);
552 modifier.paint_callback.is_some().hash(h);
553 modifier.indication.is_some().hash(h);
554 modifier.drag_preview.is_some().hash(h);
555 modifier.on_scroll.is_some().hash(h);
556 modifier.on_pointer_down.is_some().hash(h);
557 modifier.on_pointer_move.is_some().hash(h);
558 modifier.on_pointer_up.is_some().hash(h);
559 modifier.on_pointer_cancel.is_some().hash(h);
560 modifier.on_pointer_enter.is_some().hash(h);
561 modifier.on_pointer_leave.is_some().hash(h);
562 modifier.on_click.is_some().hash(h);
563 modifier.on_double_click.is_some().hash(h);
564 modifier.on_long_click.is_some().hash(h);
565 modifier.on_globally_positioned.is_some().hash(h);
566 modifier.on_size_changed.is_some().hash(h);
567 modifier.on_key_event.is_some().hash(h);
568 modifier.on_preview_key_event.is_some().hash(h);
569 modifier.on_ime.is_some().hash(h);
570 modifier.on_drag_start.is_some().hash(h);
571 modifier.on_drag_end.is_some().hash(h);
572 modifier.on_drag_enter.is_some().hash(h);
573 modifier.on_drag_over.is_some().hash(h);
574 modifier.on_drag_leave.is_some().hash(h);
575 modifier.on_drop.is_some().hash(h);
576 modifier.on_action.is_some().hash(h);
577 modifier.focus_requester.is_some().hash(h);
578 modifier.on_focus_changed.is_some().hash(h);
579 modifier.interaction_source.is_some().hash(h);
580 });
581 hashers.hash(FACET_LAYOUT, |h| {
582 if let Some(layout) = &modifier.layout {
583 1u8.hash(h);
584 hash_rc_ptr_identity(layout, h);
585 } else {
586 0u8.hash(h);
587 }
588 });
589 if let Some(input) = &modifier.text_input {
590 facet_hash_text_input(input, hashers);
591 } else {
592 hashers.hash(FACET_ALL, |h| 0u8.hash(h));
593 }
594 if let Some(colors) = &modifier.state_colors {
595 hashers.hash(FACET_PAINT, |h| {
596 hash_color(&colors.default, h);
597 hash_color(&colors.hovered, h);
598 hash_color(&colors.focused, h);
599 hash_color(&colors.pressed, h);
600 hash_color(&colors.disabled, h);
601 hash_color(&colors.dragged, h);
602 });
603 } else {
604 hashers.hash(FACET_PAINT, |h| 0u8.hash(h));
605 }
606 if let Some(elevation) = &modifier.state_elevation {
607 hashers.hash(FACET_PAINT, |h| {
608 hash_dp(elevation.default, h);
609 hash_dp(elevation.hovered, h);
610 hash_dp(elevation.focused, h);
611 hash_dp(elevation.pressed, h);
612 hash_dp(elevation.disabled, h);
613 hash_dp(elevation.dragged, h);
614 });
615 } else {
616 hashers.hash(FACET_PAINT, |h| 0u8.hash(h));
617 }
618 if let Some(spec) = &modifier.animate_content_size {
619 facet_hash_animation_spec(spec, hashers, FACET_LAYOUT);
620 } else {
621 hashers.hash(FACET_LAYOUT, |h| 0u8.hash(h));
622 }
623}
624
625fn facet_hash_semantics(
626 semantics: Option<&repose_core::Semantics>,
627 hashers: &mut FacetHashers,
628 mask: u8,
629) {
630 hashers.hash(mask, |hasher| {
631 if let Some(semantics) = semantics {
632 1u8.hash(hasher);
633 std::mem::discriminant(&semantics.role).hash(hasher);
634 semantics.label.hash(hasher);
635 semantics.focused.hash(hasher);
636 semantics.enabled.hash(hasher);
637 semantics.selectable_group.hash(hasher);
638 semantics.checked.hash(hasher);
639 semantics.selected.hash(hasher);
640 semantics.value.hash(hasher);
641 } else {
642 0u8.hash(hasher);
643 }
644 });
645}
646
647fn facet_hash_span_style(style: &SpanStyle, hashers: &mut FacetHashers, mask: u8) {
648 hashers.hash(mask & FACET_PAINT, |h| {
649 hash_opt_color(style.color.as_ref(), h)
650 });
651 hashers.hash(mask & FACET_LAYOUT_MEASUREMENT, |h| {
652 if let Some(size) = style.font_size {
653 1u8.hash(h);
654 hash_sp(size, h);
655 } else {
656 0u8.hash(h);
657 }
658 });
659 hashers.hash(mask & FACET_LAYOUT_MEASUREMENT, |h| {
660 style.font_weight.hash(h);
661 style.font_family.hash(h);
662 style.font_style.hash(h);
663 style.text_align.hash(h);
664 if let Some(value) = style.letter_spacing {
665 1u8.hash(h);
666 hash_sp(value, h);
667 } else {
668 0u8.hash(h);
669 }
670 if let Some(value) = style.line_height {
671 1u8.hash(h);
672 hash_sp(value, h);
673 } else {
674 0u8.hash(h);
675 }
676 });
677 hashers.hash(mask & FACET_PAINT, |h| {
678 hash_opt_color(style.background.as_ref(), h);
679 if let Some(decoration) = &style.text_decoration {
680 1u8.hash(h);
681 hash_text_decoration(decoration, h);
682 } else {
683 0u8.hash(h);
684 }
685 hash_f32(style.alpha, h);
686 });
687 hashers.hash(mask & FACET_LAYOUT_MEASUREMENT, |h| {
688 hash_discriminant_option(&style.text_direction, h);
689 hash_discriminant_option(&style.font_synthesis, h);
690 if let Some(value) = style.baseline_shift {
691 1u8.hash(h);
692 hash_f32(value.0, h);
693 } else {
694 0u8.hash(h);
695 }
696 hash_discriminant_option(&style.hyphens, h);
697 hash_discriminant_option(&style.line_break, h);
698 if let Some(indent) = &style.text_indent {
699 1u8.hash(h);
700 hash_dp(indent.first_line, h);
701 hash_dp(indent.rest_lines, h);
702 } else {
703 0u8.hash(h);
704 }
705 });
706 hashers.hash(mask & FACET_LAYOUT_MEASUREMENT_PAINT, |h| {
707 if let Some(style) = &style.draw_style {
708 1u8.hash(h);
709 hash_draw_style(style, h);
710 } else {
711 0u8.hash(h);
712 }
713 });
714 hashers.hash(mask & FACET_LAYOUT_MEASUREMENT, |h| {
715 style.font_variation_settings.hash(h);
716 });
717}
718
719fn facet_hash_text_style(style: &TextStyle, hashers: &mut FacetHashers) {
720 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| {
721 hash_sp(style.font_size, h);
722 style.font_weight.hash(h);
723 style.font_family.hash(h);
724 style.font_style.hash(h);
725 style.text_align.hash(h);
726 hash_sp(style.letter_spacing, h);
727 hash_sp(style.line_height, h);
728 hash_discriminant_option(&style.text_direction, h);
729 std::mem::discriminant(&style.font_synthesis).hash(h);
730 hash_f32(style.baseline_shift.0, h);
731 std::mem::discriminant(&style.hyphens).hash(h);
732 std::mem::discriminant(&style.line_break).hash(h);
733 if let Some(indent) = &style.text_indent {
734 1u8.hash(h);
735 hash_dp(indent.first_line, h);
736 hash_dp(indent.rest_lines, h);
737 } else {
738 0u8.hash(h);
739 }
740 hash_draw_style(&style.draw_style, h);
741 style.locale_list.hash(h);
742 style.font_feature_settings.hash(h);
743 style.font_variation_settings.hash(h);
744 });
745 hashers.hash(FACET_PAINT, |h| {
746 hash_opt_color(style.color.as_ref(), h);
747 hash_opt_color(style.background.as_ref(), h);
748 if let Some(decoration) = &style.text_decoration {
749 1u8.hash(h);
750 hash_text_decoration(decoration, h);
751 } else {
752 0u8.hash(h);
753 }
754 if let Some(shadow) = &style.shadow {
755 1u8.hash(h);
756 hash_shadow(shadow, h);
757 } else {
758 0u8.hash(h);
759 }
760 hash_f32(style.alpha, h);
761 hash_draw_style(&style.draw_style, h);
762 });
763}
764
765fn facet_hash_text_input(input: &repose_core::TextInputConfig, hashers: &mut FacetHashers) {
766 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| {
767 input.hint.hash(h);
768 input.multiline.hash(h);
769 input.value.hash(h);
770 input.max_lines.hash(h);
771 input.min_lines.hash(h);
772 match &input.line_limits {
773 Some(repose_core::text::TextFieldLineLimits::SingleLine) => 1u8.hash(h),
774 Some(repose_core::text::TextFieldLineLimits::MultiLine {
775 min_height_in_lines,
776 max_height_in_lines,
777 }) => {
778 2u8.hash(h);
779 min_height_in_lines.hash(h);
780 max_height_in_lines.hash(h);
781 }
782 None => 0u8.hash(h),
783 }
784 });
785 hashers.hash(FACET_PAINT, |h| {
786 input.on_change.is_some().hash(h);
787 input.on_submit.is_some().hash(h);
788 input.focus_tracker.is_some().hash(h);
789 input.enabled.hash(h);
790 input.read_only.hash(h);
791 input.sensitive.hash(h);
792 std::mem::discriminant(&input.keyboard_type).hash(h);
793 std::mem::discriminant(&input.capitalization).hash(h);
794 std::mem::discriminant(&input.ime_action).hash(h);
795 input.auto_correct_enabled.hash(h);
796 if let Some(color) = input.cursor_color {
797 1u8.hash(h);
798 hash_color(&color, h);
799 } else {
800 0u8.hash(h);
801 }
802 input.on_text_layout.is_some().hash(h);
803 input.interaction_source.is_some().hash(h);
804 input.visual_transformation.is_some().hash(h);
805 if let Some(actions) = &input.keyboard_actions {
806 1u8.hash(h);
807 actions.on_done.is_some().hash(h);
808 actions.on_go.is_some().hash(h);
809 actions.on_next.is_some().hash(h);
810 actions.on_previous.is_some().hash(h);
811 actions.on_search.is_some().hash(h);
812 actions.on_send.is_some().hash(h);
813 } else {
814 0u8.hash(h);
815 }
816 });
817 if let Some(style) = &input.text_style {
818 hashers.hash(FACET_LAYOUT_MEASUREMENT_PAINT, |h| 1u8.hash(h));
819 facet_hash_text_style(style, hashers);
820 } else {
821 hashers.hash(FACET_LAYOUT_MEASUREMENT_PAINT, |h| 0u8.hash(h));
822 }
823}
824
825fn facet_hash_nested_scroll_connection(
826 connection: Option<&repose_core::nested_scroll::NestedScrollConnection>,
827 hashers: &mut FacetHashers,
828) {
829 hashers.hash(FACET_PAINT, |h| match connection {
830 Some(connection) => {
831 1u8.hash(h);
832 if let Some(callback) = &connection.on_pre_scroll {
833 1u8.hash(h);
834 hash_rc_ptr_identity(callback, h);
835 } else {
836 0u8.hash(h);
837 }
838 if let Some(callback) = &connection.on_post_scroll {
839 1u8.hash(h);
840 hash_rc_ptr_identity(callback, h);
841 } else {
842 0u8.hash(h);
843 }
844 if let Some(callback) = &connection.on_pre_fling {
845 1u8.hash(h);
846 hash_rc_ptr_identity(callback, h);
847 } else {
848 0u8.hash(h);
849 }
850 if let Some(callback) = &connection.on_post_fling {
851 1u8.hash(h);
852 hash_rc_ptr_identity(callback, h);
853 } else {
854 0u8.hash(h);
855 }
856 }
857 None => 0u8.hash(h),
858 });
859}
860
861fn facet_hash_scrollbar_style(
862 style: Option<&repose_core::ScrollbarStyle>,
863 hashers: &mut FacetHashers,
864) {
865 let Some(style) = style else {
866 hashers.hash(FACET_PAINT, |h| 0u8.hash(h));
867 return;
868 };
869 hashers.hash(FACET_PAINT, |h| {
870 1u8.hash(h);
871 style.thickness.0.to_bits().hash(h);
872 style.track_inset.0.to_bits().hash(h);
873 style.min_thumb_length.0.to_bits().hash(h);
874 style
875 .fixed_thumb_length
876 .map(|value| value.0.to_bits())
877 .hash(h);
878 style.radius.map(|value| value.0.to_bits()).hash(h);
879 });
880 facet_hash_control_visual_set(&style.track_visuals, hashers);
881 facet_hash_control_visual_set(&style.thumb_visuals, hashers);
882}
883
884fn facet_hash_control_visual_set(set: &repose_core::ControlVisualSet, hashers: &mut FacetHashers) {
885 for visual in [
886 &set.normal,
887 &set.hovered,
888 &set.pressed,
889 &set.dragged,
890 &set.focused,
891 &set.disabled,
892 ] {
893 match visual {
894 Some(visual) => {
895 hashers.hash(FACET_PAINT, |h| 1u8.hash(h));
896 facet_hash_control_visual(visual, hashers);
897 }
898 None => hashers.hash(FACET_PAINT, |h| 0u8.hash(h)),
899 }
900 }
901}
902
903fn facet_hash_control_visual(visual: &repose_core::ControlVisual, hashers: &mut FacetHashers) {
904 hashers.hash(FACET_PAINT, |h| std::mem::discriminant(visual).hash(h));
905 match visual {
906 repose_core::ControlVisual::Rect { brush, radius } => {
907 hashers.hash(FACET_PAINT, |h| {
908 hash_brush(brush, h);
909 for value in radius {
910 value.0.to_bits().hash(h);
911 }
912 });
913 }
914 repose_core::ControlVisual::Image {
915 handle,
916 source_rect,
917 tint,
918 fit,
919 filter,
920 alignment,
921 } => {
922 hashers.hash(FACET_PAINT, |h| {
923 handle.hash(h);
924 source_rect.hash(h);
925 hash_color(tint, h);
926 std::mem::discriminant(fit).hash(h);
927 std::mem::discriminant(filter).hash(h);
928 std::mem::discriminant(alignment).hash(h);
929 });
930 }
931 repose_core::ControlVisual::Custom(_) => {
932 hashers.hash(FACET_PAINT, |h| {
933 visual.custom_identity().unwrap_or_default().hash(h)
934 });
935 }
936 _ => {}
937 }
938}
939
940fn facet_hash_scroll_binding(binding: Option<&ScrollBinding>, hashers: &mut FacetHashers) {
941 match binding {
942 Some(ScrollBinding::Vertical(binding)) => {
943 hashers.hash(FACET_LAYOUT_PAINT, |h| 1u8.hash(h));
944 facet_hash_scroll_axis(binding, hashers);
945 }
946 Some(ScrollBinding::Horizontal(binding)) => {
947 hashers.hash(FACET_LAYOUT_PAINT, |h| 2u8.hash(h));
948 facet_hash_scroll_axis(binding, hashers);
949 }
950 Some(ScrollBinding::Both(binding)) => {
951 hashers.hash(FACET_LAYOUT_PAINT, |h| 3u8.hash(h));
952 hashers.hash(FACET_LAYOUT, |h| binding.show_scrollbar.hash(h));
953 hashers.hash(FACET_PAINT, |h| {
954 binding.show_scrollbar.hash(h);
955 hash_rc_identity(&binding.on_scroll, h);
956 hash_rc_identity(&binding.set_viewport_width, h);
957 hash_rc_identity(&binding.set_viewport_height, h);
958 hash_rc_identity(&binding.set_content_width, h);
959 hash_rc_identity(&binding.set_content_height, h);
960 hash_rc_identity(&binding.get_offset_xy, h);
961 hash_rc_identity(&binding.set_offset_xy, h);
962 hash_rc_identity(&binding.tick, h);
963 hash_rc_identity(&binding.set_nested_scroll_parent, h);
964 });
965 }
966 None => hashers.hash(FACET_LAYOUT_PAINT, |h| 0u8.hash(h)),
967 }
968}
969
970fn facet_hash_scroll_axis(
971 binding: &repose_core::scroll::ScrollAxisBinding,
972 hashers: &mut FacetHashers,
973) {
974 hashers.hash(FACET_LAYOUT, |h| binding.show_scrollbar.hash(h));
975 hashers.hash(FACET_PAINT, |h| {
976 binding.show_scrollbar.hash(h);
977 hash_rc_identity(&binding.on_scroll, h);
978 hash_rc_identity(&binding.set_viewport_main, h);
979 hash_rc_identity(&binding.set_content_main, h);
980 hash_rc_identity(&binding.get_offset_main, h);
981 hash_rc_identity(&binding.set_offset_main, h);
982 hash_rc_identity(&binding.tick, h);
983 hash_rc_identity(&binding.set_nested_scroll_parent, h);
984 });
985}
986
987fn facet_hash_animation_spec(spec: &AnimationSpec, hashers: &mut FacetHashers, mask: u8) {
988 hashers.hash(mask, |h| {
989 spec.duration.as_nanos().hash(h);
990 hash_easing(&spec.easing, h);
991 spec.delay.as_nanos().hash(h);
992 if let Some(spring) = &spec.spring {
993 1u8.hash(h);
994 hash_f32(spring.damping_ratio, h);
995 hash_f32(spring.stiffness, h);
996 hash_f32(spring.settle_progress, h);
997 hash_f32(spring.settle_velocity, h);
998 } else {
999 0u8.hash(h);
1000 }
1001 if let Some(repeat) = &spec.repeat {
1002 1u8.hash(h);
1003 repeat.iterations.hash(h);
1004 repeat.reverse.hash(h);
1005 repeat.delay_between.as_nanos().hash(h);
1006 } else {
1007 0u8.hash(h);
1008 }
1009 });
1010}
1011
1012pub fn hash_subtree(content_hash: u64, children_hashes: &[u64]) -> u64 {
1013 let mut hasher = RapidHasher::default();
1014 content_hash.hash(&mut hasher);
1015 children_hashes.len().hash(&mut hasher);
1016 for hash in children_hashes {
1017 hash.hash(&mut hasher);
1018 }
1019 hasher.finish()
1020}
1021
1022fn hash_opt_color(color: Option<&Color>, hasher: &mut impl Hasher) {
1023 match color {
1024 Some(color) => {
1025 1u8.hash(hasher);
1026 hash_color(color, hasher);
1027 }
1028 None => 0u8.hash(hasher),
1029 }
1030}
1031
1032fn hash_path_effect(path_effect: &Option<repose_core::PathEffect>, hasher: &mut impl Hasher) {
1033 match path_effect {
1034 Some(repose_core::PathEffect::Corner { radius }) => {
1035 0u8.hash(hasher);
1036 hash_f32(*radius, hasher);
1037 }
1038 Some(repose_core::PathEffect::Dash { intervals, phase }) => {
1039 1u8.hash(hasher);
1040 intervals.len().hash(hasher);
1041 for interval in intervals {
1042 hash_f32(*interval, hasher);
1043 }
1044 hash_f32(*phase, hasher);
1045 }
1046 None => 2u8.hash(hasher),
1047 }
1048}
1049
1050fn hash_draw_style(style: &repose_core::DrawStyle, hasher: &mut impl Hasher) {
1051 match style {
1052 repose_core::DrawStyle::Fill => 0u8.hash(hasher),
1053 repose_core::DrawStyle::Stroke {
1054 width,
1055 cap,
1056 join,
1057 miter,
1058 path_effect,
1059 }
1060 | repose_core::DrawStyle::FillAndStroke {
1061 width,
1062 cap,
1063 join,
1064 miter,
1065 path_effect,
1066 ..
1067 } => {
1068 std::mem::discriminant(style).hash(hasher);
1069 if let repose_core::DrawStyle::FillAndStroke { outline_color, .. } = style {
1070 hash_opt_color(outline_color.as_ref(), hasher);
1071 }
1072 hash_f32(*width, hasher);
1073 (*cap as u8).hash(hasher);
1074 (*join as u8).hash(hasher);
1075 hash_f32(*miter, hasher);
1076 hash_path_effect(path_effect, hasher);
1077 }
1078 }
1079}
1080
1081fn hash_text_decoration(decoration: &repose_core::TextDecoration, hasher: &mut impl Hasher) {
1082 decoration.underline.hash(hasher);
1083 decoration.strikethrough.hash(hasher);
1084 hash_opt_color(decoration.color.as_ref(), hasher);
1085}
1086
1087fn hash_shadow(shadow: &Shadow, hasher: &mut impl Hasher) {
1088 hash_color(&shadow.color, hasher);
1089 hash_dp(shadow.offset_x, hasher);
1090 hash_dp(shadow.offset_y, hasher);
1091 hash_dp(shadow.blur_radius, hasher);
1092}
1093
1094#[allow(dead_code)]
1095fn hash_text_style(style: &TextStyle, hasher: &mut impl Hasher) {
1096 hash_sp(style.font_size, hasher);
1097 hash_opt_color(style.color.as_ref(), hasher);
1098 style.font_weight.hash(hasher);
1099 style.font_family.hash(hasher);
1100 style.font_style.hash(hasher);
1101 style.text_align.hash(hasher);
1102 hash_sp(style.letter_spacing, hasher);
1103 hash_sp(style.line_height, hasher);
1104 hash_opt_color(style.background.as_ref(), hasher);
1105 match &style.text_decoration {
1106 Some(decoration) => {
1107 1u8.hash(hasher);
1108 hash_text_decoration(decoration, hasher);
1109 }
1110 None => 0u8.hash(hasher),
1111 }
1112 match &style.shadow {
1113 Some(shadow) => {
1114 1u8.hash(hasher);
1115 hash_shadow(shadow, hasher);
1116 }
1117 None => 0u8.hash(hasher),
1118 }
1119 hash_discriminant_option(&style.text_direction, hasher);
1120 std::mem::discriminant(&style.font_synthesis).hash(hasher);
1121 hash_f32(style.baseline_shift.0, hasher);
1122 std::mem::discriminant(&style.hyphens).hash(hasher);
1123 std::mem::discriminant(&style.line_break).hash(hasher);
1124 match &style.text_indent {
1125 Some(indent) => {
1126 1u8.hash(hasher);
1127 hash_dp(indent.first_line, hasher);
1128 hash_dp(indent.rest_lines, hasher);
1129 }
1130 None => 0u8.hash(hasher),
1131 }
1132 hash_draw_style(&style.draw_style, hasher);
1133 hash_f32(style.alpha, hasher);
1134 style.locale_list.hash(hasher);
1135 style.font_feature_settings.hash(hasher);
1136 style.font_variation_settings.hash(hasher);
1137}
1138
1139#[allow(dead_code)]
1140fn hash_span_style(style: &SpanStyle, hasher: &mut impl Hasher) {
1141 hash_opt_color(style.color.as_ref(), hasher);
1142 match style.font_size {
1143 Some(size) => {
1144 1u8.hash(hasher);
1145 hash_sp(size, hasher);
1146 }
1147 None => 0u8.hash(hasher),
1148 }
1149 style.font_weight.hash(hasher);
1150 style.font_family.hash(hasher);
1151 style.font_style.hash(hasher);
1152 style.text_align.hash(hasher);
1153 match style.letter_spacing {
1154 Some(value) => {
1155 1u8.hash(hasher);
1156 hash_sp(value, hasher);
1157 }
1158 None => 0u8.hash(hasher),
1159 }
1160 match style.line_height {
1161 Some(value) => {
1162 1u8.hash(hasher);
1163 hash_sp(value, hasher);
1164 }
1165 None => 0u8.hash(hasher),
1166 }
1167 hash_opt_color(style.background.as_ref(), hasher);
1168 match &style.text_decoration {
1169 Some(decoration) => {
1170 1u8.hash(hasher);
1171 hash_text_decoration(decoration, hasher);
1172 }
1173 None => 0u8.hash(hasher),
1174 }
1175 hash_discriminant_option(&style.text_direction, hasher);
1176 hash_discriminant_option(&style.font_synthesis, hasher);
1177 match style.baseline_shift {
1178 Some(value) => {
1179 1u8.hash(hasher);
1180 hash_f32(value.0, hasher);
1181 }
1182 None => 0u8.hash(hasher),
1183 }
1184 hash_discriminant_option(&style.hyphens, hasher);
1185 hash_discriminant_option(&style.line_break, hasher);
1186 match &style.text_indent {
1187 Some(indent) => {
1188 1u8.hash(hasher);
1189 hash_dp(indent.first_line, hasher);
1190 hash_dp(indent.rest_lines, hasher);
1191 }
1192 None => 0u8.hash(hasher),
1193 }
1194 match &style.draw_style {
1195 Some(style) => {
1196 1u8.hash(hasher);
1197 hash_draw_style(style, hasher);
1198 }
1199 None => 0u8.hash(hasher),
1200 }
1201 hash_f32(style.alpha, hasher);
1202 style.font_variation_settings.hash(hasher);
1203}
1204
1205#[allow(dead_code)]
1206fn hash_view_kind(kind: &ViewKind, hasher: &mut impl Hasher) {
1207 std::mem::discriminant(kind).hash(hasher);
1208 match kind {
1209 ViewKind::Text {
1210 text,
1211 color,
1212 font_size,
1213 soft_wrap,
1214 max_lines,
1215 overflow,
1216 font_family,
1217 annotations,
1218 text_align,
1219 font_weight,
1220 font_style,
1221 text_decoration,
1222 letter_spacing,
1223 line_height,
1224 url,
1225 font_variation_settings,
1226 draw_style,
1227 } => {
1228 text.hash(hasher);
1229 hash_color(color, hasher);
1230 hash_sp(*font_size, hasher);
1231 soft_wrap.hash(hasher);
1232 max_lines.hash(hasher);
1233 hash_text_overflow(overflow, hasher);
1234 font_family.hash(hasher);
1235 text_align.hash(hasher);
1236 font_weight.0.hash(hasher);
1237 font_style.hash(hasher);
1238 hash_text_decoration(text_decoration, hasher);
1239 hash_sp(*letter_spacing, hasher);
1240 hash_sp(*line_height, hasher);
1241 url.hash(hasher);
1242 font_variation_settings.hash(hasher);
1243 hash_draw_style(draw_style, hasher);
1244 match annotations {
1245 Some(annotations) => {
1246 1u8.hash(hasher);
1247 annotations.len().hash(hasher);
1248 for span in annotations.iter() {
1249 span.start.hash(hasher);
1250 span.end.hash(hasher);
1251 hash_span_style(&span.style, hasher);
1252 match &span.url {
1253 Some(url) => {
1254 1u8.hash(hasher);
1255 url.hash(hasher);
1256 }
1257 None => 0u8.hash(hasher),
1258 }
1259 }
1260 }
1261 None => 0u8.hash(hasher),
1262 }
1263 }
1264 ViewKind::Image {
1265 handle,
1266 tint,
1267 fit,
1268 filter,
1269 source_rect,
1270 alignment,
1271 } => {
1272 handle.hash(hasher);
1273 repose_core::image_intrinsic_size(*handle).hash(hasher);
1274 hash_color(tint, hasher);
1275 std::mem::discriminant(fit).hash(hasher);
1276 std::mem::discriminant(filter).hash(hasher);
1277 source_rect.hash(hasher);
1278 std::mem::discriminant(alignment).hash(hasher);
1279 }
1280 ViewKind::OverlayHost
1281 | ViewKind::Box
1282 | ViewKind::Row
1283 | ViewKind::Column
1284 | ViewKind::ZStack
1285 | ViewKind::SubcomposeLayout { .. } => {}
1286 _ => {}
1287 }
1288}
1289
1290fn hash_discriminant_option<T>(value: &Option<T>, hasher: &mut impl Hasher) {
1291 match value {
1292 Some(value) => {
1293 1u8.hash(hasher);
1294 std::mem::discriminant(value).hash(hasher);
1295 }
1296 None => 0u8.hash(hasher),
1297 }
1298}
1299
1300fn hash_f32(v: f32, hasher: &mut impl Hasher) {
1301 v.to_bits().hash(hasher);
1302}
1303
1304fn hash_rc_identity<T: ?Sized>(value: &Option<std::rc::Rc<T>>, hasher: &mut impl Hasher) {
1305 value.is_some().hash(hasher);
1306}
1307
1308fn hash_rc_ptr_identity<T: ?Sized>(value: &std::rc::Rc<T>, hasher: &mut impl Hasher) {
1309 (std::rc::Rc::as_ptr(value) as *const () as usize).hash(hasher);
1310}
1311
1312#[allow(dead_code)]
1313fn hash_control_visual(visual: &repose_core::ControlVisual, hasher: &mut impl Hasher) {
1314 std::mem::discriminant(visual).hash(hasher);
1315 match visual {
1316 repose_core::ControlVisual::Rect { brush, radius } => {
1317 hash_brush(brush, hasher);
1318 for value in radius {
1319 value.0.to_bits().hash(hasher);
1320 }
1321 }
1322 repose_core::ControlVisual::Image {
1323 handle,
1324 source_rect,
1325 tint,
1326 fit,
1327 filter,
1328 alignment,
1329 } => {
1330 handle.hash(hasher);
1331 source_rect.hash(hasher);
1332 hash_color(tint, hasher);
1333 std::mem::discriminant(fit).hash(hasher);
1334 std::mem::discriminant(filter).hash(hasher);
1335 std::mem::discriminant(alignment).hash(hasher);
1336 }
1337 repose_core::ControlVisual::Custom(_) => {
1338 visual.custom_identity().unwrap_or_default().hash(hasher)
1339 }
1340 _ => {}
1341 }
1342}
1343
1344#[allow(dead_code)]
1345fn hash_control_visual_set(set: &repose_core::ControlVisualSet, hasher: &mut impl Hasher) {
1346 for visual in [
1347 &set.normal,
1348 &set.hovered,
1349 &set.pressed,
1350 &set.dragged,
1351 &set.focused,
1352 &set.disabled,
1353 ] {
1354 match visual {
1355 Some(visual) => {
1356 true.hash(hasher);
1357 hash_control_visual(visual, hasher);
1358 }
1359 None => false.hash(hasher),
1360 }
1361 }
1362}
1363
1364#[allow(dead_code)]
1365fn hash_scrollbar_style(style: Option<&repose_core::ScrollbarStyle>, hasher: &mut impl Hasher) {
1366 let Some(style) = style else {
1367 0u8.hash(hasher);
1368 return;
1369 };
1370 1u8.hash(hasher);
1371 style.thickness.0.to_bits().hash(hasher);
1372 style.track_inset.0.to_bits().hash(hasher);
1373 style.min_thumb_length.0.to_bits().hash(hasher);
1374 style
1375 .fixed_thumb_length
1376 .map(|value| value.0.to_bits())
1377 .hash(hasher);
1378 style.radius.map(|value| value.0.to_bits()).hash(hasher);
1379 hash_control_visual_set(&style.track_visuals, hasher);
1380 hash_control_visual_set(&style.thumb_visuals, hasher);
1381}
1382
1383#[allow(dead_code)]
1384fn hash_scroll_axis_binding(
1385 binding: &repose_core::scroll::ScrollAxisBinding,
1386 hasher: &mut impl Hasher,
1387) {
1388 binding.show_scrollbar.hash(hasher);
1389 hash_rc_identity(&binding.on_scroll, hasher);
1390 hash_rc_identity(&binding.set_viewport_main, hasher);
1391 hash_rc_identity(&binding.set_content_main, hasher);
1392 hash_rc_identity(&binding.get_offset_main, hasher);
1393 hash_rc_identity(&binding.set_offset_main, hasher);
1394 hash_rc_identity(&binding.tick, hasher);
1395 hash_rc_identity(&binding.set_nested_scroll_parent, hasher);
1396}
1397
1398#[allow(dead_code)]
1399fn hash_scroll_binding(binding: &ScrollBinding, hasher: &mut impl Hasher) {
1400 match binding {
1401 ScrollBinding::Vertical(binding) => {
1402 1u8.hash(hasher);
1403 hash_scroll_axis_binding(binding, hasher);
1404 }
1405 ScrollBinding::Horizontal(binding) => {
1406 2u8.hash(hasher);
1407 hash_scroll_axis_binding(binding, hasher);
1408 }
1409 ScrollBinding::Both(binding) => {
1410 3u8.hash(hasher);
1411 binding.show_scrollbar.hash(hasher);
1412 hash_rc_identity(&binding.on_scroll, hasher);
1413 hash_rc_identity(&binding.set_viewport_width, hasher);
1414 hash_rc_identity(&binding.set_viewport_height, hasher);
1415 hash_rc_identity(&binding.set_content_width, hasher);
1416 hash_rc_identity(&binding.set_content_height, hasher);
1417 hash_rc_identity(&binding.get_offset_xy, hasher);
1418 hash_rc_identity(&binding.set_offset_xy, hasher);
1419 hash_rc_identity(&binding.tick, hasher);
1420 hash_rc_identity(&binding.set_nested_scroll_parent, hasher);
1421 }
1422 }
1423}
1424
1425#[allow(dead_code)]
1426fn hash_cursor(cursor: &Option<repose_core::CursorIcon>, hasher: &mut impl Hasher) {
1427 match cursor {
1428 Some(repose_core::CursorIcon::Custom(image)) => {
1429 10u8.hash(hasher);
1430 image.size.hash(hasher);
1431 image.hotspot.hash(hasher);
1432 image.rgba.hash(hasher);
1433 }
1434 Some(cursor) => {
1435 1u8.hash(hasher);
1436 std::mem::discriminant(cursor).hash(hasher);
1437 }
1438 None => 0u8.hash(hasher),
1439 }
1440}
1441
1442fn hash_opt_f32(v: Option<f32>, hasher: &mut impl Hasher) {
1443 match v {
1444 Some(value) => {
1445 1u8.hash(hasher);
1446 hash_f32(value, hasher);
1447 }
1448 None => 0u8.hash(hasher),
1449 }
1450}
1451
1452fn hash_dp(v: Dp, hasher: &mut impl Hasher) {
1453 hash_f32(v.0, hasher);
1454}
1455
1456fn hash_opt_dp(v: Option<Dp>, hasher: &mut impl Hasher) {
1457 match v {
1458 Some(value) => {
1459 1u8.hash(hasher);
1460 hash_dp(value, hasher);
1461 }
1462 None => 0u8.hash(hasher),
1463 }
1464}
1465
1466fn hash_sp(v: Sp, hasher: &mut impl Hasher) {
1467 hash_f32(v.0, hasher);
1468}
1469
1470#[allow(dead_code)]
1471fn hash_px(v: Px, hasher: &mut impl Hasher) {
1472 hash_f32(v.0, hasher);
1473}
1474
1475#[allow(dead_code)]
1476fn hash_modifier(m: &Modifier, hasher: &mut impl Hasher) {
1477 match m.size {
1478 Some(size) => {
1479 1u8.hash(hasher);
1480 hash_dp(size.width, hasher);
1481 hash_dp(size.height, hasher);
1482 }
1483 None => 0u8.hash(hasher),
1484 }
1485 hash_opt_dp(m.width, hasher);
1486 hash_opt_dp(m.height, hasher);
1487 match m.required_size {
1488 Some(size) => {
1489 1u8.hash(hasher);
1490 hash_dp(size.width, hasher);
1491 hash_dp(size.height, hasher);
1492 }
1493 None => 0u8.hash(hasher),
1494 }
1495 hash_opt_dp(m.required_min_width, hasher);
1496 hash_opt_dp(m.required_max_width, hasher);
1497 hash_opt_dp(m.required_min_height, hasher);
1498 hash_opt_dp(m.required_max_height, hasher);
1499 hash_opt_dp(m.default_min_width, hasher);
1500 hash_opt_dp(m.default_min_height, hasher);
1501 hash_opt_f32(m.fill_max, hasher);
1502 hash_opt_f32(m.fill_max_w, hasher);
1503 hash_opt_f32(m.fill_max_h, hasher);
1504 m.repaint_boundary.hash(hasher);
1505 hash_opt_dp(m.padding, hasher);
1506 match m.padding_values {
1507 Some(padding) => {
1508 1u8.hash(hasher);
1509 hash_dp(padding.left, hasher);
1510 hash_dp(padding.right, hasher);
1511 hash_dp(padding.top, hasher);
1512 hash_dp(padding.bottom, hasher);
1513 }
1514 None => 0u8.hash(hasher),
1515 }
1516 hash_opt_dp(m.min_width, hasher);
1517 hash_opt_dp(m.min_height, hasher);
1518 hash_opt_dp(m.max_width, hasher);
1519 hash_opt_dp(m.max_height, hasher);
1520 match &m.background {
1521 Some(background) => {
1522 1u8.hash(hasher);
1523 hash_brush(background, hasher);
1524 }
1525 None => 0u8.hash(hasher),
1526 }
1527 match &m.border {
1528 Some(border) => {
1529 1u8.hash(hasher);
1530 hash_dp(border.width, hasher);
1531 hash_brush(&border.brush, hasher);
1532 for &radius in &border.radius {
1533 hash_dp(radius, hasher);
1534 }
1535 }
1536 None => 0u8.hash(hasher),
1537 }
1538 hash_opt_f32(m.flex_grow, hasher);
1539 hash_opt_f32(m.flex_shrink, hasher);
1540 hash_opt_dp(m.flex_basis, hasher);
1541 m.flex_wrap
1542 .map(|value| std::mem::discriminant(&value))
1543 .hash(hasher);
1544 m.flex_basis_content.hash(hasher);
1545 m.flex_line_count.hash(hasher);
1546 m.flex_dir
1547 .map(|value| std::mem::discriminant(&value))
1548 .hash(hasher);
1549 m.align_self
1550 .map(|value| (value.keyword as u8, value.safety as u8))
1551 .hash(hasher);
1552 m.justify_content
1553 .map(|value| (value.keyword as u8, value.safety as u8))
1554 .hash(hasher);
1555 m.align_items_container
1556 .map(|value| (value.keyword as u8, value.safety as u8))
1557 .hash(hasher);
1558 m.align_content
1559 .map(|value| (value.keyword as u8, value.safety as u8))
1560 .hash(hasher);
1561 m.baseline_align.hash(hasher);
1562 match m.clip_rounded {
1563 Some(radius) => {
1564 1u8.hash(hasher);
1565 for value in radius {
1566 hash_dp(value, hasher);
1567 }
1568 }
1569 None => 0u8.hash(hasher),
1570 }
1571 match m.transform {
1572 Some(transform) => {
1573 1u8.hash(hasher);
1574 hash_f32(transform.translate_x, hasher);
1575 hash_f32(transform.translate_y, hasher);
1576 hash_f32(transform.scale_x, hasher);
1577 hash_f32(transform.scale_y, hasher);
1578 hash_f32(transform.rotate, hasher);
1579 hash_f32(transform.shear_x, hasher);
1580 hash_f32(transform.shear_y, hasher);
1581 hash_f32(transform.origin_x, hasher);
1582 hash_f32(transform.origin_y, hasher);
1583 for value in transform.perspective {
1584 hash_f32(value, hasher);
1585 }
1586 }
1587 None => 0u8.hash(hasher),
1588 }
1589 hash_opt_f32(m.alpha, hasher);
1590 m.position_type
1591 .map(|value| std::mem::discriminant(&value))
1592 .hash(hasher);
1593 hash_opt_dp(m.offset_left, hasher);
1594 hash_opt_dp(m.offset_right, hasher);
1595 hash_opt_dp(m.offset_top, hasher);
1596 hash_opt_dp(m.offset_bottom, hasher);
1597 match &m.grid {
1598 Some(grid) => {
1599 1u8.hash(hasher);
1600 grid.columns.hash(hasher);
1601 hash_dp(grid.row_gap, hasher);
1602 hash_dp(grid.column_gap, hasher);
1603 }
1604 None => 0u8.hash(hasher),
1605 }
1606 m.grid_col_span.hash(hasher);
1607 m.grid_row_span.hash(hasher);
1608 hash_opt_f32(m.aspect_ratio, hasher);
1609 m.intrinsic_width.hash(hasher);
1610 m.intrinsic_height.hash(hasher);
1611 hash_opt_dp(m.fit_content_width, hasher);
1612 hash_opt_dp(m.fit_content_height, hasher);
1613 match m.contain {
1614 None => 0u8.hash(hasher),
1615 Some(value) if value == taffy::Contain::CONTENT => 3u8.hash(hasher),
1616 Some(value) if value == taffy::Contain::LAYOUT => 1u8.hash(hasher),
1617 Some(value) if value == taffy::Contain::PAINT => 2u8.hash(hasher),
1618 Some(_) => 4u8.hash(hasher),
1619 }
1620 hash_f32(m.z_index, hasher);
1621 hash_opt_f32(m.render_z_index, hasher);
1622 m.input_blocker.hash(hasher);
1623 m.click.hash(hasher);
1624 m.disabled.hash(hasher);
1625 m.focusable.hash(hasher);
1626 m.hit_passthrough.hash(hasher);
1627 m.propagate_min.hash(hasher);
1628 m.focus_group.hash(hasher);
1629 match &m.blur {
1630 Some(blur) => {
1631 1u8.hash(hasher);
1632 hash_dp(blur.radius_x, hasher);
1633 hash_dp(blur.radius_y, hasher);
1634 std::mem::discriminant(&blur.edge_treatment).hash(hasher);
1635 }
1636 None => 0u8.hash(hasher),
1637 }
1638 match &m.scroll {
1639 Some(binding) => hash_scroll_binding(binding, hasher),
1640 None => 0u8.hash(hasher),
1641 }
1642 hash_scrollbar_style(m.scrollbar_style.as_ref(), hasher);
1643 m.overflow
1644 .map(|value| std::mem::discriminant(&value))
1645 .hash(hasher);
1646 match m.clip_rect {
1647 Some(rect) => {
1648 1u8.hash(hasher);
1649 hash_dp(rect.left, hasher);
1650 hash_dp(rect.top, hasher);
1651 hash_dp(rect.right, hasher);
1652 hash_dp(rect.bottom, hasher);
1653 std::mem::discriminant(&rect.op).hash(hasher);
1654 }
1655 None => 0u8.hash(hasher),
1656 }
1657 hash_opt_dp(m.gap, hasher);
1658 hash_opt_dp(m.row_gap, hasher);
1659 hash_opt_dp(m.column_gap, hasher);
1660 hash_opt_dp(m.margin_top, hasher);
1661 hash_opt_dp(m.margin_left, hasher);
1662 hash_opt_dp(m.margin_right, hasher);
1663 hash_opt_dp(m.margin_bottom, hasher);
1664 hash_opt_f32(m.graphics_layer, hasher);
1665 match &m.shadow {
1666 Some(shadow) => {
1667 1u8.hash(hasher);
1668 hash_dp(shadow.blur_radius, hasher);
1669 hash_dp(shadow.offset_y, hasher);
1670 hash_color(&shadow.color, hasher);
1671 }
1672 None => 0u8.hash(hasher),
1673 }
1674 match m.shadow_shape {
1675 Some(radius) => {
1676 1u8.hash(hasher);
1677 for value in radius {
1678 hash_dp(value, hasher);
1679 }
1680 }
1681 None => 0u8.hash(hasher),
1682 }
1683 match &m.semantics {
1684 Some(semantics) => {
1685 1u8.hash(hasher);
1686 std::mem::discriminant(&semantics.role).hash(hasher);
1687 semantics.label.hash(hasher);
1688 semantics.focused.hash(hasher);
1689 semantics.enabled.hash(hasher);
1690 semantics.selectable_group.hash(hasher);
1691 semantics.checked.hash(hasher);
1692 semantics.selected.hash(hasher);
1693 semantics.value.hash(hasher);
1694 }
1695 None => 0u8.hash(hasher),
1696 }
1697 hash_cursor(&m.cursor, hasher);
1698 match &m.text_input {
1699 Some(input) => {
1700 1u8.hash(hasher);
1701 input.hint.hash(hasher);
1702 input.multiline.hash(hasher);
1703 input.value.hash(hasher);
1704 input.enabled.hash(hasher);
1705 input.read_only.hash(hasher);
1706 input.sensitive.hash(hasher);
1707 input.max_lines.hash(hasher);
1708 input.min_lines.hash(hasher);
1709 std::mem::discriminant(&input.keyboard_type).hash(hasher);
1710 std::mem::discriminant(&input.capitalization).hash(hasher);
1711 std::mem::discriminant(&input.ime_action).hash(hasher);
1712 input.auto_correct_enabled.hash(hasher);
1713 match input.cursor_color {
1714 Some(color) => {
1715 1u8.hash(hasher);
1716 hash_color(&color, hasher);
1717 }
1718 None => 0u8.hash(hasher),
1719 }
1720 match &input.text_style {
1721 Some(style) => {
1722 1u8.hash(hasher);
1723 hash_text_style(style, hasher);
1724 }
1725 None => 0u8.hash(hasher),
1726 }
1727 match &input.line_limits {
1728 Some(repose_core::text::TextFieldLineLimits::SingleLine) => 1u8.hash(hasher),
1729 Some(repose_core::text::TextFieldLineLimits::MultiLine {
1730 min_height_in_lines,
1731 max_height_in_lines,
1732 }) => {
1733 2u8.hash(hasher);
1734 min_height_in_lines.hash(hasher);
1735 max_height_in_lines.hash(hasher);
1736 }
1737 None => 0u8.hash(hasher),
1738 }
1739 }
1740 None => 0u8.hash(hasher),
1741 }
1742 m.text_selection.is_some().hash(hasher);
1743 match &m.state_colors {
1744 Some(colors) => {
1745 1u8.hash(hasher);
1746 hash_color(&colors.default, hasher);
1747 hash_color(&colors.hovered, hasher);
1748 hash_color(&colors.focused, hasher);
1749 hash_color(&colors.pressed, hasher);
1750 hash_color(&colors.disabled, hasher);
1751 hash_color(&colors.dragged, hasher);
1752 }
1753 None => 0u8.hash(hasher),
1754 }
1755 match &m.state_elevation {
1756 Some(elevation) => {
1757 1u8.hash(hasher);
1758 hash_dp(elevation.default, hasher);
1759 hash_dp(elevation.hovered, hasher);
1760 hash_dp(elevation.focused, hasher);
1761 hash_dp(elevation.pressed, hasher);
1762 hash_dp(elevation.disabled, hasher);
1763 hash_dp(elevation.dragged, hasher);
1764 }
1765 None => 0u8.hash(hasher),
1766 }
1767 match &m.animate_content_size {
1768 Some(spec) => {
1769 1u8.hash(hasher);
1770 hash_animation_spec(spec, hasher);
1771 }
1772 None => 0u8.hash(hasher),
1773 }
1774}
1775
1776#[allow(dead_code)]
1777fn hash_animation_spec(spec: &AnimationSpec, hasher: &mut impl Hasher) {
1778 spec.duration.as_nanos().hash(hasher);
1779 hash_easing(&spec.easing, hasher);
1780 spec.delay.as_nanos().hash(hasher);
1781 match &spec.spring {
1782 Some(spring) => {
1783 1u8.hash(hasher);
1784 hash_f32(spring.damping_ratio, hasher);
1785 hash_f32(spring.stiffness, hasher);
1786 hash_f32(spring.settle_progress, hasher);
1787 hash_f32(spring.settle_velocity, hasher);
1788 }
1789 None => 0u8.hash(hasher),
1790 }
1791 match &spec.repeat {
1792 Some(repeat) => {
1793 1u8.hash(hasher);
1794 repeat.iterations.hash(hasher);
1795 repeat.reverse.hash(hasher);
1796 repeat.delay_between.as_nanos().hash(hasher);
1797 }
1798 None => 0u8.hash(hasher),
1799 }
1800}
1801
1802fn hash_easing(easing: &Easing, hasher: &mut impl Hasher) {
1803 std::mem::discriminant(easing).hash(hasher);
1804 match easing {
1805 Easing::SpringCrit { omega } => hash_f32(*omega, hasher),
1806 Easing::Custom(cb) => {
1807 hash_f32(cb.p1x, hasher);
1808 hash_f32(cb.p1y, hasher);
1809 hash_f32(cb.p2x, hasher);
1810 hash_f32(cb.p2y, hasher);
1811 }
1812 _ => {}
1813 }
1814}
1815
1816fn hash_color(c: &Color, hasher: &mut impl Hasher) {
1817 c.0.hash(hasher);
1818 c.1.hash(hasher);
1819 c.2.hash(hasher);
1820 c.3.hash(hasher);
1821}
1822
1823fn hash_brush(b: &Brush, hasher: &mut impl Hasher) {
1824 std::mem::discriminant(b).hash(hasher);
1825 match b {
1826 Brush::Solid(c) => hash_color(c, hasher),
1827 Brush::Linear {
1828 start,
1829 end,
1830 start_color,
1831 end_color,
1832 } => {
1833 hash_f32(start.x, hasher);
1834 hash_f32(start.y, hasher);
1835 hash_f32(end.x, hasher);
1836 hash_f32(end.y, hasher);
1837 hash_color(start_color, hasher);
1838 hash_color(end_color, hasher);
1839 }
1840 Brush::LinearNormalized {
1841 start,
1842 end,
1843 start_color,
1844 end_color,
1845 } => {
1846 hash_f32(start.x, hasher);
1847 hash_f32(start.y, hasher);
1848 hash_f32(end.x, hasher);
1849 hash_f32(end.y, hasher);
1850 hash_color(start_color, hasher);
1851 hash_color(end_color, hasher);
1852 }
1853 Brush::Radial {
1854 center,
1855 radius,
1856 start_color,
1857 end_color,
1858 } => {
1859 hash_f32(center.x, hasher);
1860 hash_f32(center.y, hasher);
1861 hash_f32(*radius, hasher);
1862 hash_color(start_color, hasher);
1863 hash_color(end_color, hasher);
1864 }
1865 Brush::Sweep {
1866 center,
1867 start_color,
1868 end_color,
1869 } => {
1870 hash_f32(center.x, hasher);
1871 hash_f32(center.y, hasher);
1872 hash_color(start_color, hasher);
1873 hash_color(end_color, hasher);
1874 }
1875 _ => {}
1876 }
1877}
1878
1879fn hash_text_overflow(o: &TextOverflow, hasher: &mut impl Hasher) {
1880 std::mem::discriminant(o).hash(hasher);
1881}
1882
1883#[cfg(test)]
1884mod tests {
1885 use super::*;
1886 use repose_core::{
1887 DrawStyle, FontStyle, FontWeight, ImageAlignment, ImageFilter, ImageFit, ImageSourceRect,
1888 Modifier, TextAlign, TextDecoration, UnitExt, View, ViewKind,
1889 };
1890
1891 #[test]
1892 fn test_same_view_same_hash() {
1893 let v1 = View::new(0, ViewKind::Box).modifier(Modifier::new().width(100.0.dp()));
1894 let v2 = View::new(0, ViewKind::Box).modifier(Modifier::new().width(100.0.dp()));
1895
1896 assert_eq!(hash_view_content(&v1), hash_view_content(&v2));
1897 }
1898
1899 #[test]
1900 fn image_source_rect_changes_hash() {
1901 let image = |source_rect| {
1902 View::new(
1903 0,
1904 ViewKind::Image {
1905 handle: 1,
1906 tint: Color::WHITE,
1907 fit: ImageFit::FillBounds,
1908 filter: ImageFilter::Nearest,
1909 source_rect,
1910 alignment: ImageAlignment::Center,
1911 },
1912 )
1913 };
1914 let full = image(None);
1915 let first = image(Some(ImageSourceRect::new(0, 0, 8, 8)));
1916 let second = image(Some(ImageSourceRect::new(8, 0, 8, 8)));
1917 assert_ne!(hash_view_content(&full), hash_view_content(&first));
1918 assert_ne!(hash_view_content(&first), hash_view_content(&second));
1919 }
1920
1921 #[test]
1922 fn test_scroll_presence_changes_hash() {
1923 use repose_core::scroll::ScrollAxisBinding;
1924
1925 let v1 = View::new(0, ViewKind::Box).modifier(Modifier::new());
1926 let v2 = View::new(0, ViewKind::Box).modifier(Modifier::new().vertical_scroll(
1927 ScrollAxisBinding {
1928 show_scrollbar: true,
1929 ..Default::default()
1930 },
1931 ));
1932
1933 assert_ne!(hash_view_content(&v1), hash_view_content(&v2));
1934 }
1935
1936 #[test]
1937 fn test_different_view_different_hash() {
1938 let v1 = View::new(0, ViewKind::Box).modifier(Modifier::new().width(100.0.dp()));
1939 let v2 = View::new(0, ViewKind::Box).modifier(Modifier::new().width(200.0.dp()));
1940
1941 assert_ne!(hash_view_content(&v1), hash_view_content(&v2));
1942 }
1943
1944 #[test]
1945 fn test_text_content_hash() {
1946 let v1 = View::new(
1947 0,
1948 ViewKind::Text {
1949 text: "Hello".to_string(),
1950 color: Color::WHITE,
1951 font_size: 16.0.sp(),
1952 soft_wrap: true,
1953 max_lines: None,
1954 overflow: TextOverflow::Visible,
1955 font_family: None,
1956 annotations: None,
1957 text_align: TextAlign::Unspecified,
1958 font_weight: FontWeight::NORMAL,
1959 font_style: FontStyle::Normal,
1960 text_decoration: TextDecoration::default(),
1961 letter_spacing: Sp::ZERO,
1962 line_height: Sp::ZERO,
1963 url: None,
1964 font_variation_settings: None,
1965 draw_style: DrawStyle::Fill,
1966 },
1967 );
1968 let v2 = View::new(
1969 0,
1970 ViewKind::Text {
1971 text: "Hello".to_string(),
1972 color: Color::WHITE,
1973 font_size: 16.0.sp(),
1974 soft_wrap: true,
1975 max_lines: None,
1976 overflow: TextOverflow::Visible,
1977 font_family: None,
1978 annotations: None,
1979 text_align: TextAlign::Unspecified,
1980 font_weight: FontWeight::NORMAL,
1981 font_style: FontStyle::Normal,
1982 text_decoration: TextDecoration::default(),
1983 letter_spacing: Sp::ZERO,
1984 line_height: Sp::ZERO,
1985 url: None,
1986 font_variation_settings: None,
1987 draw_style: DrawStyle::Fill,
1988 },
1989 );
1990 let v3 = View::new(
1991 0,
1992 ViewKind::Text {
1993 text: "World".to_string(),
1994 color: Color::WHITE,
1995 font_size: 16.0.sp(),
1996 soft_wrap: true,
1997 max_lines: None,
1998 overflow: TextOverflow::Visible,
1999 font_family: None,
2000 annotations: None,
2001 text_align: TextAlign::Unspecified,
2002 font_weight: FontWeight::NORMAL,
2003 font_style: FontStyle::Normal,
2004 text_decoration: TextDecoration::default(),
2005 letter_spacing: Sp::ZERO,
2006 line_height: Sp::ZERO,
2007 url: None,
2008 font_variation_settings: None,
2009 draw_style: DrawStyle::Fill,
2010 },
2011 );
2012
2013 assert_eq!(hash_view_content(&v1), hash_view_content(&v2));
2014 assert_ne!(hash_view_content(&v1), hash_view_content(&v3));
2015 }
2016
2017 fn text_view_with_url(url: Option<&str>) -> View {
2018 View::new(
2019 0,
2020 ViewKind::Text {
2021 text: "link".to_string(),
2022 color: Color::WHITE,
2023 font_size: 16.0.sp(),
2024 soft_wrap: true,
2025 max_lines: None,
2026 overflow: TextOverflow::Visible,
2027 font_family: None,
2028 annotations: None,
2029 text_align: TextAlign::Unspecified,
2030 font_weight: FontWeight::NORMAL,
2031 font_style: FontStyle::Normal,
2032 text_decoration: TextDecoration::default(),
2033 letter_spacing: Sp::ZERO,
2034 line_height: Sp::ZERO,
2035 url: url.map(std::sync::Arc::from),
2036 font_variation_settings: None,
2037 draw_style: DrawStyle::Fill,
2038 },
2039 )
2040 }
2041
2042 #[test]
2043 fn view_id_and_span_baseline_change_hash() {
2044 let first = View::new(1, ViewKind::Box);
2045 let second = View::new(2, ViewKind::Box);
2046 assert_ne!(hash_view_content(&first), hash_view_content(&second));
2047
2048 let mut a = text_view_with_url(None);
2049 let mut b = a.clone();
2050 let span = repose_core::TextSpan {
2051 start: 0,
2052 end: 4,
2053 style: repose_core::SpanStyle::default()
2054 .baseline_shift(repose_core::BaselineShift::Superscript),
2055 url: None,
2056 };
2057 if let ViewKind::Text { annotations, .. } = &mut a.kind {
2058 *annotations = Some(std::sync::Arc::from([span.clone()]));
2059 }
2060 if let ViewKind::Text { annotations, .. } = &mut b.kind {
2061 *annotations = Some(std::sync::Arc::from([repose_core::TextSpan {
2062 style: repose_core::SpanStyle::default()
2063 .baseline_shift(repose_core::BaselineShift::Subscript),
2064 ..span
2065 }]));
2066 }
2067 assert_ne!(hash_view_content(&a), hash_view_content(&b));
2068 }
2069
2070 #[test]
2071 fn test_text_url_change_invalidates() {
2072 let a = text_view_with_url(None);
2073 let b = text_view_with_url(Some("https://a.example"));
2074 let c = text_view_with_url(Some("https://b.example"));
2075 assert_ne!(hash_view_content(&a), hash_view_content(&b));
2076 assert_ne!(hash_view_content(&b), hash_view_content(&c));
2077 }
2078
2079 #[test]
2080 fn test_semantics_state_invalidates() {
2081 let mk = |checked: Option<bool>| {
2082 View::new(0, ViewKind::Box).semantics(
2083 repose_core::Semantics::new(repose_core::Role::Checkbox)
2084 .with_checked(checked.unwrap_or(false)),
2085 )
2086 };
2087 assert_ne!(
2088 hash_view_content(&mk(Some(true))),
2089 hash_view_content(&mk(Some(false)))
2090 );
2091 let m1 = View::new(0, ViewKind::Box).modifier(
2092 Modifier::new().semantics(repose_core::Semantics::new(repose_core::Role::Button)),
2093 );
2094 let m2 = View::new(0, ViewKind::Box).modifier(
2095 Modifier::new()
2096 .semantics(repose_core::Semantics::new(repose_core::Role::Button).with_value("v")),
2097 );
2098 assert_ne!(hash_view_content(&m1), hash_view_content(&m2));
2099 }
2100
2101 #[test]
2102 fn test_then_preserves_layer_fields() {
2103 let base = Modifier::new().width(10.0.dp());
2104 let overlay = Modifier::new()
2105 .blur(4.0.dp())
2106 .shadow_with_color(8.0.dp(), 2.0.dp(), Color::BLACK)
2107 .graphics_layer(0.5);
2108 let merged = base.then(overlay);
2109 assert!(merged.blur.is_some());
2110 assert!(merged.shadow.is_some());
2111 assert!(merged.graphics_layer.is_some());
2112 assert_eq!(
2113 Modifier::new()
2114 .z_index(5.0)
2115 .then(Modifier::new().z_index(0.0))
2116 .z_index,
2117 0.0
2118 );
2119 }
2120}