Keep state canvas focus tree stable when placement controls change
This commit is contained in:
@@ -1046,6 +1046,16 @@ async fn mounted_state_delete_uses_state_placement_and_preserves_architecture_pl
|
|||||||
assert_eq!(app.selected, state);
|
assert_eq!(app.selected, state);
|
||||||
assert_eq!(app.behavior.diagram.placement, Some(node));
|
assert_eq!(app.behavior.diagram.placement, Some(node));
|
||||||
let before = app.document.snapshot();
|
let before = app.document.snapshot();
|
||||||
|
assert!(crate::behavior::state_edit::unplaced(app.project(), diagram).is_empty());
|
||||||
|
let owner = app.state_context().unwrap();
|
||||||
|
let focused = |mounted: &mut Mounted, app: &App| {
|
||||||
|
mounted.geometry(app).focused.iter().any(|(id, _, focused)| {
|
||||||
|
*focused
|
||||||
|
&& id.as_ref()
|
||||||
|
== Some(&iced::advanced::widget::Id::from("diagram-keyboard-owner"))
|
||||||
|
})
|
||||||
|
};
|
||||||
|
assert!(focused(&mut mounted, &app));
|
||||||
let messages = delete(&mut mounted, &mut app, false).await;
|
let messages = delete(&mut mounted, &mut app, false).await;
|
||||||
assert!(messages.iter().any(|message| matches!(
|
assert!(messages.iter().any(|message| matches!(
|
||||||
message,
|
message,
|
||||||
@@ -1053,13 +1063,35 @@ async fn mounted_state_delete_uses_state_placement_and_preserves_architecture_pl
|
|||||||
)));
|
)));
|
||||||
assert!(!app.project().contains(node));
|
assert!(!app.project().contains(node));
|
||||||
assert!(app.project().contains(architecture_node) && app.project().contains(state));
|
assert!(app.project().contains(architecture_node) && app.project().contains(state));
|
||||||
|
assert_eq!(
|
||||||
|
crate::behavior::state_edit::unplaced(app.project(), diagram),
|
||||||
|
vec![state]
|
||||||
|
);
|
||||||
|
mounted.settle(&mut app).await;
|
||||||
|
let hidden = app.state_context().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
(hidden.document, hidden.diagram, hidden.epoch),
|
||||||
|
(owner.document, owner.diagram, owner.epoch),
|
||||||
|
"hiding one placement does not change the logical canvas owner"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
focused(&mut mounted, &app),
|
||||||
|
"showing the unplaced-element picker must retain the actual canvas focus tree"
|
||||||
|
);
|
||||||
consistent(&app);
|
consistent(&app);
|
||||||
let directory = tempfile::tempdir().unwrap();
|
let directory = tempfile::tempdir().unwrap();
|
||||||
let path = directory.path().join(&app.document.bundle().entry);
|
let path = directory.path().join(&app.document.bundle().entry);
|
||||||
save_with_busy_view(&mut mounted, &mut app, &path).await;
|
save_with_busy_view(&mut mounted, &mut app, &path).await;
|
||||||
|
assert!(focused(&mut mounted, &app), "Save retains the same owner");
|
||||||
let task = app.update(Message::Undo);
|
let task = app.update(Message::Undo);
|
||||||
mounted.perform(&mut app, task).await;
|
mounted.perform(&mut app, task).await;
|
||||||
assert_eq!(app.document.bundle(), &before.bundle);
|
assert_eq!(app.document.bundle(), &before.bundle);
|
||||||
|
assert!(crate::behavior::state_edit::unplaced(app.project(), diagram).is_empty());
|
||||||
|
mounted.settle(&mut app).await;
|
||||||
|
assert!(
|
||||||
|
focused(&mut mounted, &app),
|
||||||
|
"Undo removes the picker without replacing the actual canvas focus tree"
|
||||||
|
);
|
||||||
delete(&mut mounted, &mut app, true).await;
|
delete(&mut mounted, &mut app, true).await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.delete_target.as_ref().unwrap().snapshot.request.object,
|
app.delete_target.as_ref().unwrap().snapshot.request.object,
|
||||||
|
|||||||
@@ -141,12 +141,16 @@ pub fn view<'a>(app: &'a App, machine: Id, mut out: Column<'a, Message>) -> Elem
|
|||||||
label: format!("{} · {}", p.named(element), p.elements[&element].kind()),
|
label: format!("{} · {}", p.named(element), p.elements[&element].kind()),
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
// Keep the optional picker in its own stable slot. Inserting it as a
|
||||||
|
// sibling before the canvas would replace the positional Iced focus tree.
|
||||||
|
let missing_spacing = if missing.is_empty() { 0.0_f32 } else { 8.0 };
|
||||||
|
let mut missing_controls = Column::new();
|
||||||
if !missing.is_empty() {
|
if !missing.is_empty() {
|
||||||
let position = Position {
|
let position = Position {
|
||||||
x: (48. - state.pan.x) / state.zoom,
|
x: (48. - state.pan.x) / state.zoom,
|
||||||
y: (90. - state.pan.y) / state.zoom,
|
y: (90. - state.pan.y) / state.zoom,
|
||||||
};
|
};
|
||||||
out = out.push(
|
missing_controls = missing_controls.push(
|
||||||
pick_list(missing, None::<Choice>, move |c| {
|
pick_list(missing, None::<Choice>, move |c| {
|
||||||
event(Event::Place {
|
event(Event::Place {
|
||||||
context,
|
context,
|
||||||
@@ -173,13 +177,20 @@ pub fn view<'a>(app: &'a App, machine: Id, mut out: Column<'a, Message>) -> Elem
|
|||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.into();
|
.into();
|
||||||
out = out.push(crate::diagram::focus::wrap(
|
out = out.push(
|
||||||
canvas.map(event),
|
column![
|
||||||
context,
|
missing_controls,
|
||||||
crate::app::diagram_removal::focus_enabled(app),
|
crate::diagram::focus::wrap(
|
||||||
app.busy,
|
canvas.map(event),
|
||||||
Message::DiagramTraverse,
|
context,
|
||||||
));
|
crate::app::diagram_removal::focus_enabled(app),
|
||||||
|
app.busy,
|
||||||
|
Message::DiagramTraverse,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
.spacing(missing_spacing)
|
||||||
|
.height(Fill),
|
||||||
|
);
|
||||||
let base: Element<'_, Message> = container(out.spacing(8))
|
let base: Element<'_, Message> = container(out.spacing(8))
|
||||||
.padding(14)
|
.padding(14)
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|||||||
Reference in New Issue
Block a user