Skip to content

Commit c3fcb47

Browse files
authored
Merge pull request #656 from multiplex55/codex/add-swap-functionality-to-dashboard-editor
Add slot swap controls to dashboard editor
2 parents 6b19e61 + 60e49ea commit c3fcb47

1 file changed

Lines changed: 115 additions & 0 deletions

File tree

src/gui/dashboard_editor_dialog.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)