@@ -4,6 +4,9 @@ use super::widgets::label_widgets::*;
44use crate :: application:: generate_uuid;
55use crate :: messages:: input_mapper:: utility_types:: input_keyboard:: KeysGroup ;
66use crate :: messages:: prelude:: * ;
7+ use std:: collections:: HashMap ;
8+ use std:: collections:: hash_map:: DefaultHasher ;
9+ use std:: hash:: { Hash , Hasher } ;
710use std:: sync:: Arc ;
811
912#[ repr( transparent) ]
@@ -111,6 +114,43 @@ pub trait Diffable: Clone + PartialEq {
111114
112115 /// Computes the diff between self (old) and new, updating self and recording changes.
113116 fn diff ( & mut self , new : Self , widget_path : & mut Vec < usize > , widget_diffs : & mut Vec < WidgetDiff > ) ;
117+
118+ /// Collects all CheckboxIds currently in use in this layout, computing stable replacements.
119+ fn collect_checkbox_ids ( & self , layout_target : LayoutTarget , widget_path : & mut Vec < usize > , checkbox_map : & mut HashMap < CheckboxId , CheckboxId > ) ;
120+
121+ /// Replaces all widget IDs with deterministic IDs based on position and type.
122+ /// Also replaces CheckboxIds using the provided mapping.
123+ fn replace_widget_ids ( & mut self , layout_target : LayoutTarget , widget_path : & mut Vec < usize > , checkbox_map : & HashMap < CheckboxId , CheckboxId > ) ;
124+ }
125+
126+ /// Computes a deterministic WidgetId based on layout target, path, and widget type.
127+ fn compute_widget_id ( layout_target : LayoutTarget , widget_path : & [ usize ] , widget : & Widget ) -> WidgetId {
128+ let mut hasher = DefaultHasher :: new ( ) ;
129+
130+ // Hash the layout target
131+ ( layout_target as u8 ) . hash ( & mut hasher) ;
132+
133+ // Hash the widget path
134+ widget_path. hash ( & mut hasher) ;
135+
136+ // Hash the widget type discriminant
137+ std:: mem:: discriminant ( widget) . hash ( & mut hasher) ;
138+
139+ WidgetId ( hasher. finish ( ) )
140+ }
141+
142+ /// Computes a deterministic CheckboxId based on the same WidgetId algorithm.
143+ fn compute_checkbox_id ( layout_target : LayoutTarget , widget_path : & [ usize ] , widget : & Widget ) -> CheckboxId {
144+ let mut hasher = DefaultHasher :: new ( ) ;
145+
146+ ( layout_target as u8 ) . hash ( & mut hasher) ;
147+ widget_path. hash ( & mut hasher) ;
148+ std:: mem:: discriminant ( widget) . hash ( & mut hasher) ;
149+
150+ // Add extra salt for checkbox to differentiate from widget ID
151+ "checkbox" . hash ( & mut hasher) ;
152+
153+ CheckboxId ( hasher. finish ( ) )
114154}
115155
116156/// Contains an arrangement of widgets mounted somewhere specific in the frontend.
@@ -159,6 +199,22 @@ impl Diffable for Layout {
159199 widget_path. pop ( ) ;
160200 }
161201 }
202+
203+ fn collect_checkbox_ids ( & self , layout_target : LayoutTarget , widget_path : & mut Vec < usize > , checkbox_map : & mut HashMap < CheckboxId , CheckboxId > ) {
204+ for ( index, child) in self . 0 . iter ( ) . enumerate ( ) {
205+ widget_path. push ( index) ;
206+ child. collect_checkbox_ids ( layout_target, widget_path, checkbox_map) ;
207+ widget_path. pop ( ) ;
208+ }
209+ }
210+
211+ fn replace_widget_ids ( & mut self , layout_target : LayoutTarget , widget_path : & mut Vec < usize > , checkbox_map : & HashMap < CheckboxId , CheckboxId > ) {
212+ for ( index, child) in self . 0 . iter_mut ( ) . enumerate ( ) {
213+ widget_path. push ( index) ;
214+ child. replace_widget_ids ( layout_target, widget_path, checkbox_map) ;
215+ widget_path. pop ( ) ;
216+ }
217+ }
162218}
163219
164220#[ derive( Debug , Default ) ]
@@ -474,6 +530,58 @@ impl Diffable for LayoutGroup {
474530 }
475531 }
476532 }
533+
534+ fn collect_checkbox_ids ( & self , layout_target : LayoutTarget , widget_path : & mut Vec < usize > , checkbox_map : & mut HashMap < CheckboxId , CheckboxId > ) {
535+ match self {
536+ Self :: Column { widgets } | Self :: Row { widgets } => {
537+ for ( index, widget) in widgets. iter ( ) . enumerate ( ) {
538+ widget_path. push ( index) ;
539+ widget. collect_checkbox_ids ( layout_target, widget_path, checkbox_map) ;
540+ widget_path. pop ( ) ;
541+ }
542+ }
543+ Self :: Table { rows, .. } => {
544+ for ( row_idx, row) in rows. iter ( ) . enumerate ( ) {
545+ for ( col_idx, widget) in row. iter ( ) . enumerate ( ) {
546+ widget_path. push ( row_idx) ;
547+ widget_path. push ( col_idx) ;
548+ widget. collect_checkbox_ids ( layout_target, widget_path, checkbox_map) ;
549+ widget_path. pop ( ) ;
550+ widget_path. pop ( ) ;
551+ }
552+ }
553+ }
554+ Self :: Section { layout, .. } => {
555+ layout. collect_checkbox_ids ( layout_target, widget_path, checkbox_map) ;
556+ }
557+ }
558+ }
559+
560+ fn replace_widget_ids ( & mut self , layout_target : LayoutTarget , widget_path : & mut Vec < usize > , checkbox_map : & HashMap < CheckboxId , CheckboxId > ) {
561+ match self {
562+ Self :: Column { widgets } | Self :: Row { widgets } => {
563+ for ( index, widget) in widgets. iter_mut ( ) . enumerate ( ) {
564+ widget_path. push ( index) ;
565+ widget. replace_widget_ids ( layout_target, widget_path, checkbox_map) ;
566+ widget_path. pop ( ) ;
567+ }
568+ }
569+ Self :: Table { rows, .. } => {
570+ for ( row_idx, row) in rows. iter_mut ( ) . enumerate ( ) {
571+ for ( col_idx, widget) in row. iter_mut ( ) . enumerate ( ) {
572+ widget_path. push ( row_idx) ;
573+ widget_path. push ( col_idx) ;
574+ widget. replace_widget_ids ( layout_target, widget_path, checkbox_map) ;
575+ widget_path. pop ( ) ;
576+ widget_path. pop ( ) ;
577+ }
578+ }
579+ }
580+ Self :: Section { layout, .. } => {
581+ layout. replace_widget_ids ( layout_target, widget_path, checkbox_map) ;
582+ }
583+ }
584+ }
477585}
478586
479587#[ derive( Debug , Clone , serde:: Serialize , serde:: Deserialize , specta:: Type ) ]
@@ -485,7 +593,7 @@ pub struct WidgetInstance {
485593
486594impl PartialEq for WidgetInstance {
487595 fn eq ( & self , other : & Self ) -> bool {
488- self . widget == other. widget
596+ self . widget_id == other . widget_id && self . widget == other. widget
489597 }
490598}
491599
@@ -537,6 +645,62 @@ impl Diffable for WidgetInstance {
537645 self . widget = new. widget ;
538646 }
539647 }
648+
649+ fn collect_checkbox_ids ( & self , layout_target : LayoutTarget , widget_path : & mut Vec < usize > , checkbox_map : & mut HashMap < CheckboxId , CheckboxId > ) {
650+ match & self . widget {
651+ Widget :: CheckboxInput ( checkbox) => {
652+ // Compute stable ID based on position and insert mapping
653+ let checkbox_id = checkbox. for_label ;
654+ let stable_id = compute_checkbox_id ( layout_target, widget_path, & self . widget ) ;
655+ checkbox_map. entry ( checkbox_id) . or_insert ( stable_id) ;
656+ }
657+ Widget :: TextLabel ( label) => {
658+ // Compute stable ID based on position and insert mapping
659+ let checkbox_id = label. for_checkbox ;
660+ let stable_id = compute_checkbox_id ( layout_target, widget_path, & self . widget ) ;
661+ checkbox_map. entry ( checkbox_id) . or_insert ( stable_id) ;
662+ }
663+ Widget :: PopoverButton ( button) => {
664+ // Recursively collect from nested popover layout
665+ for ( index, child) in button. popover_layout . 0 . iter ( ) . enumerate ( ) {
666+ widget_path. push ( index) ;
667+ child. collect_checkbox_ids ( layout_target, widget_path, checkbox_map) ;
668+ widget_path. pop ( ) ;
669+ }
670+ }
671+ _ => { }
672+ }
673+ }
674+
675+ fn replace_widget_ids ( & mut self , layout_target : LayoutTarget , widget_path : & mut Vec < usize > , checkbox_map : & HashMap < CheckboxId , CheckboxId > ) {
676+ // 1. Generate deterministic WidgetId
677+ self . widget_id = compute_widget_id ( layout_target, widget_path, & self . widget ) ;
678+
679+ // 2. Replace CheckboxIds if present
680+ match & mut self . widget {
681+ Widget :: CheckboxInput ( checkbox) => {
682+ let old_id = checkbox. for_label ;
683+ if let Some ( & new_id) = checkbox_map. get ( & old_id) {
684+ checkbox. for_label = new_id;
685+ }
686+ }
687+ Widget :: TextLabel ( label) => {
688+ let old_id = label. for_checkbox ;
689+ if let Some ( & new_id) = checkbox_map. get ( & old_id) {
690+ label. for_checkbox = new_id;
691+ }
692+ }
693+ Widget :: PopoverButton ( button) => {
694+ // Recursively replace in nested popover layout
695+ for ( index, child) in button. popover_layout . 0 . iter_mut ( ) . enumerate ( ) {
696+ widget_path. push ( index) ;
697+ child. replace_widget_ids ( layout_target, widget_path, checkbox_map) ;
698+ widget_path. pop ( ) ;
699+ }
700+ }
701+ _ => { }
702+ }
703+ }
540704}
541705
542706#[ derive( Clone , specta:: Type ) ]
0 commit comments