@@ -28,6 +28,7 @@ pub struct DashboardEditorDialog {
2828 show_preview : bool ,
2929 blocked_warning : Option < String > ,
3030 drag_anchor : Option < ( usize , usize ) > ,
31+ swap_anchor : Option < usize > ,
3132 slot_expand_all : bool ,
3233 slot_collapse_all : bool ,
3334 snap_on_edit : bool ,
@@ -45,6 +46,7 @@ impl Default for DashboardEditorDialog {
4546 show_preview : false ,
4647 blocked_warning : None ,
4748 drag_anchor : None ,
49+ swap_anchor : None ,
4850 slot_expand_all : false ,
4951 slot_collapse_all : false ,
5052 snap_on_edit : false ,
@@ -219,6 +221,7 @@ impl DashboardEditorDialog {
219221 let mut slot = original_slot. clone ( ) ;
220222 let mut removed = false ;
221223 let mut edited = false ;
224+ let mut swapped = false ;
222225 ui. push_id ( idx, |ui| {
223226 let collapsing_id = ui. id ( ) . with ( ( "slot-collapse" , idx) ) ;
224227 let mut state = CollapsingState :: load_with_default_open (
@@ -248,6 +251,41 @@ impl DashboardEditorDialog {
248251 if ui. button ( "Select" ) . clicked ( ) {
249252 self . selected_slot = Some ( idx) ;
250253 }
254+ if let Some ( selected_idx) = self . selected_slot {
255+ if selected_idx != idx
256+ && ui. button ( "Swap with selected" ) . clicked ( )
257+ {
258+ if let Err ( err) = self . swap_slots (
259+ selected_idx,
260+ idx,
261+ registry,
262+ ) {
263+ self . blocked_warning = Some ( err) ;
264+ }
265+ self . swap_anchor = None ;
266+ swapped = true ;
267+ }
268+ }
269+ let swap_label = if self . swap_anchor == Some ( idx) {
270+ "Swap (source)"
271+ } else {
272+ "Swap"
273+ } ;
274+ if ui. button ( swap_label) . clicked ( ) {
275+ if self . swap_anchor == Some ( idx) {
276+ self . swap_anchor = None ;
277+ } else if let Some ( anchor) = self . swap_anchor {
278+ if let Err ( err) =
279+ self . swap_slots ( anchor, idx, registry)
280+ {
281+ self . blocked_warning = Some ( err) ;
282+ }
283+ self . swap_anchor = None ;
284+ swapped = true ;
285+ } else {
286+ self . swap_anchor = Some ( idx) ;
287+ }
288+ }
251289 } ,
252290 ) ;
253291 } ) ;
@@ -361,7 +399,17 @@ impl DashboardEditorDialog {
361399 self . selected_slot = None ;
362400 }
363401 }
402+ if let Some ( anchor) = self . swap_anchor {
403+ if anchor == idx {
404+ self . swap_anchor = None ;
405+ } else if anchor > idx {
406+ self . swap_anchor = Some ( anchor - 1 ) ;
407+ }
408+ }
364409 self . ensure_selected_slot ( ) ;
410+ self . ensure_swap_anchor ( ) ;
411+ } else if swapped {
412+ idx += 1 ;
365413 } else if edited && slot != original_slot {
366414 if let Err ( err) = self . commit_slot ( idx, slot, registry) {
367415 if self . blocked_warning . is_none ( ) {
@@ -427,6 +475,19 @@ impl DashboardEditorDialog {
427475 self . selected_slot = Some ( self . config . slots . len ( ) . saturating_sub ( 1 ) ) ;
428476 }
429477 }
478+ self . ensure_swap_anchor ( ) ;
479+ }
480+
481+ fn ensure_swap_anchor ( & mut self ) {
482+ if self . config . slots . is_empty ( ) {
483+ self . swap_anchor = None ;
484+ return ;
485+ }
486+ if let Some ( idx) = self . swap_anchor {
487+ if idx >= self . config . slots . len ( ) {
488+ self . swap_anchor = None ;
489+ }
490+ }
430491 }
431492
432493 fn render_selected_settings (
@@ -495,6 +556,60 @@ impl DashboardEditorDialog {
495556 }
496557 }
497558
559+ fn swap_slots (
560+ & mut self ,
561+ first : usize ,
562+ second : usize ,
563+ registry : & WidgetRegistry ,
564+ ) -> Result < ( ) , String > {
565+ if first >= self . config . slots . len ( ) || second >= self . config . slots . len ( ) {
566+ return Err ( "Invalid slot selection" . into ( ) ) ;
567+ }
568+ if first == second {
569+ return Ok ( ( ) ) ;
570+ }
571+ let rows = self . config . grid . rows . max ( 1 ) as usize ;
572+ let cols = self . config . grid . cols . max ( 1 ) as usize ;
573+ let original_first = self . config . slots [ first] . clone ( ) ;
574+ let original_second = self . config . slots [ second] . clone ( ) ;
575+
576+ {
577+ let ( left, right) = if first < second {
578+ let ( left, right) = self . config . slots . split_at_mut ( second) ;
579+ ( & mut left[ first] , & mut right[ 0 ] )
580+ } else {
581+ let ( left, right) = self . config . slots . split_at_mut ( first) ;
582+ ( & mut right[ 0 ] , & mut left[ second] )
583+ } ;
584+ std:: mem:: swap ( & mut left. widget , & mut right. widget ) ;
585+ std:: mem:: swap ( & mut left. settings , & mut right. settings ) ;
586+ std:: mem:: swap ( & mut left. id , & mut right. id ) ;
587+ }
588+
589+ let first_slot = self . config . slots [ first] . clone ( ) ;
590+ let second_slot = self . config . slots [ second] . clone ( ) ;
591+ let occupancy_first = self . occupancy_map ( rows, cols, Some ( first) ) ;
592+ let occupancy_second = self . occupancy_map ( rows, cols, Some ( second) ) ;
593+ let validated_first =
594+ self . validate_slot ( first, first_slot, rows, cols, registry, & occupancy_first) ;
595+ let validated_second =
596+ self . validate_slot ( second, second_slot, rows, cols, registry, & occupancy_second) ;
597+
598+ match ( validated_first, validated_second) {
599+ ( Ok ( first_slot) , Ok ( second_slot) ) => {
600+ self . config . slots [ first] = first_slot;
601+ self . config . slots [ second] = second_slot;
602+ self . blocked_warning = None ;
603+ Ok ( ( ) )
604+ }
605+ ( Err ( err) , _) | ( _, Err ( err) ) => {
606+ self . config . slots [ first] = original_first;
607+ self . config . slots [ second] = original_second;
608+ Err ( err)
609+ }
610+ }
611+ }
612+
498613 fn max_row_span_for ( & self , slot : & SlotConfig ) -> u8 {
499614 let rows = self . config . grid . rows . max ( 1 ) as usize ;
500615 let row = slot. row . max ( 0 ) as usize ;
0 commit comments