Wire captured saved-model group deletion and shared impact review

This commit is contained in:
2026-09-24 22:12:36 -07:00
parent 8688faf8c5
commit 3ab63ef028
36 changed files with 2205 additions and 365 deletions
+67 -1
View File
@@ -29,6 +29,7 @@ use std::{
pub const GUIDANCE_HEIGHT: f32 = 42.;
pub struct StateCanvas<'a> {
pub removal_input: &'a crate::removal_input::Lifetime,
pub project: &'a Project,
pub diagram: Id,
pub context: Context,
@@ -44,6 +45,8 @@ pub struct StateCanvas<'a> {
#[derive(Default)]
pub struct State {
removal_lease: Option<crate::removal_input::Lease>,
removal_viewport: Option<(f32, Vector, Rectangle)>,
context: Option<Context>,
tool: Option<Tool>,
pointer: Option<Point>,
@@ -877,6 +880,21 @@ impl StateCanvas<'_> {
}
if selection.targets().len() > 1 && *key == Key::Named(Named::Delete) {
let snapshot = selection.snapshot(selection_context(self.context))?;
if *modifiers == keyboard::Modifiers::SHIFT {
let Some(input_generation) = self.removal_input.idle() else {
return Some(Action::capture());
};
return Some(
Action::publish(Event::ModelRemoval(Box::new(
crate::app::saved_removal::Event::Begin {
input_generation,
context: self.context,
snapshot: Box::new(snapshot),
},
)))
.and_capture(),
);
}
let event = if modifiers.is_empty()
&& crate::app::multiselect::placements(&snapshot).is_some()
{
@@ -885,7 +903,7 @@ impl StateCanvas<'_> {
SelectionEvent::Refuse(snapshot, if modifiers.is_empty() {
"Hide requires only placed elements. Rows, ports and transitions cannot be hidden as a placement group."
} else {
"Group model deletion requires an aggregate impact review and is not available yet. Use Hide selected for placements."
"Use Shift+Delete to review model deletion or Hide selected for placements."
}.into())
};
return Some(Action::publish(Event::Selection(event)).and_capture());
@@ -2002,6 +2020,51 @@ impl canvas::Program<Event> for StateCanvas<'_> {
_ => None,
}
}
}
impl canvas::Program<Event> for StateCanvas<'_> {
type State = State;
fn update(
&self,
state: &mut State,
event: &canvas::Event,
bounds: Rectangle,
cursor: mouse::Cursor,
) -> Option<Action<Event>> {
self.removal_input.sync(
&mut state.removal_lease,
state.press.is_some()
|| state.group.is_some()
|| state.source.is_some()
|| state.bend.is_some()
|| state.pan_from.is_some(),
);
self.removal_input.observe(event);
let viewport = (self.zoom, self.pan, bounds);
if state
.removal_viewport
.is_some_and(|previous| previous != viewport)
{
self.removal_input.revoke_idle();
}
state.removal_viewport = Some(viewport);
let action = self.update_input(state, event, bounds, cursor);
if matches!(
event,
canvas::Event::Window(iced::window::Event::Unfocused | iced::window::Event::Resized(_))
| canvas::Event::Mouse(mouse::Event::CursorLeft)
) {
state.reset();
}
self.removal_input.sync(
&mut state.removal_lease,
state.press.is_some()
|| state.group.is_some()
|| state.source.is_some()
|| state.bend.is_some()
|| state.pan_from.is_some(),
);
action
}
fn draw(
&self,
state: &State,
@@ -2597,6 +2660,7 @@ mod tests {
include!("state_multiselect_canvas_tests.rs");
struct Fixture {
removal_input: crate::removal_input::Lifetime,
project: Project,
diagram: Id,
machine: Id,
@@ -2658,6 +2722,7 @@ mod tests {
}
validate_candidate(&project).unwrap();
Fixture {
removal_input: Default::default(),
project,
diagram,
machine,
@@ -2858,6 +2923,7 @@ mod tests {
fn canvas(f: &Fixture, tool: Tool, zoom: f32) -> StateCanvas<'_> {
StateCanvas {
removal_input: &f.removal_input,
project: &f.project,
diagram: f.diagram,
context: Context {