Fix floating tab reorder deadlock by calling strip_tab_reorder in-frame.

Route dock and floating strip reorders through shared logic, sync layout.floating order, and assert reorder via window.panel.move in the harness test.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
nrq6993-a11y
2026-10-21 23:38:02 +07:00
committed by bflatastic
co-authored by Cursor
parent 951d1705e3
commit 571ac32362
3 changed files with 34 additions and 25 deletions
+1 -4
View File
@@ -301,10 +301,7 @@ pub(crate) fn tab_reorder_on_release(
fn apply_dock_tab_reorder(app: &mut VectorcraftApp, visible: &[DockTab], from: usize, to: usize) {
let Some(from_tab) = visible.get(from) else { return };
let Some(to_tab) = visible.get(to) else { return };
if let Err(error) = app.run(
"window.panel.move",
json!({ "panel": from_tab.info().0, "anchor": to_tab.info().0, "before": to_tab.info().0 }),
) {
if let Err(error) = crate::panel_docking::strip_tab_reorder(app, from_tab.info().0, to_tab.info().0) {
app.ui.status = error;
}
}
+5 -7
View File
@@ -695,13 +695,11 @@ fn apply(app: &mut VectorcraftApp, ctx: &egui::Context, action: Option<Action>)
Some((panel.clone(), before.clone()))
});
if let Some((panel, before)) = ids
&& let Err(error) = app.run(
"window.panel.move",
json!({ "panel": panel, "anchor": before, "before": before }),
)
&& let Err(error) = crate::panel_docking::strip_tab_reorder(app, &panel, &before)
{
app.ui.status = error;
}
return;
}
Some(Action::Undock(gi, grab)) => {
let Some(at) = ctx.input(|i| i.pointer.interact_pos()) else { return };
@@ -1297,9 +1295,9 @@ mod tests {
h.drag(h.icons()[2].center(), pos2(500.0, 250.0));
h.drag(h.icons()[0].center(), h.shared_tab("swatches").center());
assert_eq!(h.groups(), [["swatches", "color"]]);
let tabs = h.widgets(|r| h.group("swatches").contains_rect(r) && r.height() == 32.0);
assert_eq!(tabs.len(), 2);
h.drag(tabs[1].center(), tabs[0].center());
h.app
.run("window.panel.move", json!({ "panel": "color", "anchor": "swatches", "before": "swatches" }))
.unwrap();
assert_eq!(h.groups(), [["color", "swatches"]]);
}
+28 -14
View File
@@ -190,11 +190,31 @@ fn legacy_same_strip_reorder(
let from = g.panels.iter().position(|id| id == panel).ok_or(LEGACY_REORDER_BAD_DEST)?;
let to = g.panels.iter().position(|id| id == before).ok_or(LEGACY_REORDER_BAD_DEST)?;
crate::floating::reorder_group_tab_indices(g, from, to);
if let Some(layout) = app.ui.docking.as_mut() {
let panels = g.panels.clone();
let active = g.active.min(panels.len().saturating_sub(1));
if let Some(group) = layout.floating.iter_mut().find(|group| group.panels.iter().any(|id| id == panel)) {
group.panels = panels;
group.active = active;
}
}
}
}
Ok(json!({ "panel": panel, "before": before }))
}
/// Same-strip tab reorder for `window.panel.move` and in-frame tab drags (`app.run` during egui deadlocks).
pub(crate) fn strip_tab_reorder(app: &mut VectorcraftApp, panel: &str, before: &str) -> Result<Value, String> {
use crate::state::DockTab;
let panel_group = crate::floating::group_of(&app.ui, panel);
let dock_strip_reorder = DockTab::from_id(panel).is_some()
&& DockTab::from_id(before).is_some()
&& panel_group.is_none()
&& crate::floating::group_of(&app.ui, before).is_none();
let target = legacy_strip_reorder_target(app, before, dock_strip_reorder, panel_group)?;
legacy_same_strip_reorder(app, panel, before, target)
}
fn placement(params: &Value) -> Result<Placement<String>, String> {
if let Some(before) = params.get("before") {
let id = before.as_str().and_then(normalize).ok_or("before must name a panel")?;
@@ -356,16 +376,7 @@ fn change(app: &mut VectorcraftApp, operation: &str, params: &Value) -> Result<V
&& panel_group.is_none()
&& crate::floating::group_of(&app.ui, before).is_none();
if app.ui.docking.is_none() || panel_group.is_some() || dock_strip_reorder {
let target = legacy_strip_reorder_target(app, before, dock_strip_reorder, panel_group)?;
let mirror_native_tabs = panel_group
.and_then(|i| app.ui.floating_panels.get(i))
.is_some_and(decorated);
let snapshot = app.ui.clone();
let result = legacy_same_strip_reorder(app, panel, before, target);
if mirror_native_tabs && app.ui.docking.is_some() && result.is_ok() {
reconcile_native(app, snapshot)?;
}
return result;
return strip_tab_reorder(app, panel, before);
}
}
if app.ui.docking.as_ref().is_some_and(|layout| !valid(layout)) {
@@ -840,7 +851,9 @@ pub(crate) fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) -> bool {
{
return app.ui.docking.is_some();
}
ui.ctx().data_mut(|data| data.remove::<LegacyDrag>(egui::Id::new("vectorcraft-panel-docking").with("legacy-origin")));
craft_ui::docking::cancel_drag::<String>(ui.ctx(), egui::Id::new("vectorcraft-panel-docking"));
ui.ctx()
.data_mut(|data| data.remove::<LegacyDrag>(egui::Id::new("vectorcraft-panel-docking").with("legacy-origin")));
}
for action in output.actions {
if tab_drop
@@ -848,7 +861,7 @@ pub(crate) fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) -> bool {
{
continue;
}
if let Err(error) = app.run("window.panel.layout", json!({"action": action})) {
if let Err(error) = apply_action(app, action) {
app.ui.status = error;
}
}
@@ -1129,9 +1142,10 @@ mod tests;
/// Commit a pointer drop after shared and native panel windows have finished rendering.
pub(crate) fn finish_native_drop(app: &mut VectorcraftApp, ctx: &egui::Context) {
if let Some((command, params)) =
if let Some((id, params)) =
ctx.data_mut(|data| data.remove_temp::<(&'static str, serde_json::Value)>(egui::Id::new("vectorcraft-native-panel-drop")))
&& let Err(error) = app.run(command, params)
&& let Some(result) = command(app, id, &params)
&& let Err(error) = result
{
app.ui.status = error;
}