175 lines
7.3 KiB
Rust
175 lines
7.3 KiB
Rust
//! Admit spacing packets before ordinary navigation can retire a newer reveal.
|
|
use super::*;
|
|
use crate::diagram_usage::Target;
|
|
use behavior::scenario::{
|
|
selection,
|
|
spacing::{Event, Refusal, controller::Finish},
|
|
};
|
|
|
|
#[cfg(test)]
|
|
mod group_tests;
|
|
#[cfg(test)]
|
|
pub(super) mod tests;
|
|
|
|
fn refusal_reason(refusal: &Refusal) -> String {
|
|
match refusal {
|
|
Refusal::InvalidSelection(reason) => (*reason).into(),
|
|
Refusal::TargetNotRendered(_) => {
|
|
"Every selected object must be a rendered local message or lifeline in this saved view."
|
|
.into()
|
|
}
|
|
Refusal::Geometry(_) => {
|
|
"These positions cannot be displayed together. The saved arrangement is unchanged."
|
|
.into()
|
|
}
|
|
Refusal::Command(reason) => reason.clone(),
|
|
_ => {
|
|
"The drag could not be applied in this view. The saved arrangement is unchanged.".into()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl App {
|
|
pub(super) fn spacing_enabled(&self) -> bool {
|
|
self.pending_diagram_reveal.is_none()
|
|
&& !self.behavior.has_pending()
|
|
&& !self.draft_dirty()
|
|
&& !self.source_dirty()
|
|
&& !self.source_rename.open()
|
|
&& behavior::canvas_workspace::keyboard_enabled(self)
|
|
}
|
|
|
|
pub(super) fn scenario_spacing(&mut self, event: Event) -> Task<Message> {
|
|
if self.scenario_reorder.session.is_some() {
|
|
return Task::none();
|
|
}
|
|
let stamp = event.stamp();
|
|
if self.scenario_context() != Some(stamp.context) || self.camera_epoch != stamp.camera_epoch
|
|
{
|
|
return Task::none();
|
|
}
|
|
let Some(snapshot) = self.multiselect_snapshot() else {
|
|
return Task::none();
|
|
};
|
|
if snapshot.generation() != stamp.selection_generation
|
|
|| snapshot.context() != selection::context(stamp.context)
|
|
{
|
|
return Task::none();
|
|
}
|
|
let state = &self.behavior.scenario;
|
|
let matches =
|
|
state.spacing_token() == Some(event.token()) && state.spacing_stamp() == Some(stamp);
|
|
if match event {
|
|
Event::Begin { token, .. } => token.is_nil() || state.spacing_token().is_some(),
|
|
_ => {
|
|
!matches
|
|
|| !state.spacing_snapshot().is_some_and(|captured| {
|
|
self.diagram_selection.as_ref().is_some_and(|selected| {
|
|
selected.matches(captured, selection::context(stamp.context))
|
|
})
|
|
})
|
|
}
|
|
} {
|
|
return Task::none();
|
|
}
|
|
if matches!(event, Event::Begin { .. }) {
|
|
let visible = selection::visible(self.project(), stamp.context, self.selected);
|
|
if !snapshot
|
|
.targets()
|
|
.iter()
|
|
.all(|target| visible.contains(target))
|
|
{
|
|
// This otherwise current Begin cannot be replayed after repair.
|
|
self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
|
|
self.status =
|
|
"Every selected object must still be visible before moving the group.".into();
|
|
return Task::none();
|
|
}
|
|
}
|
|
if matches!(event, Event::Cancel { .. }) || !self.spacing_enabled() {
|
|
if self.behavior.scenario.cancel_spacing() || matches!(event, Event::Begin { .. }) {
|
|
self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
|
|
}
|
|
return Task::none();
|
|
}
|
|
match event {
|
|
Event::Begin { .. } => {
|
|
let mut state = std::mem::take(&mut self.behavior.scenario);
|
|
let result = state.begin_spacing_with_snapshot(self.project(), event, snapshot);
|
|
self.behavior.scenario = state;
|
|
if let Err(error) = result {
|
|
// Consume admitted failed starts too; a repeated Begin is stale.
|
|
self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
|
|
self.status = refusal_reason(&error);
|
|
}
|
|
}
|
|
Event::Move { screen, .. } => self.behavior.scenario.move_spacing(screen),
|
|
Event::Finish { screen, .. } => {
|
|
// Full captured selection matched above, before taking the session.
|
|
let captured = self
|
|
.behavior
|
|
.scenario
|
|
.spacing_snapshot()
|
|
.cloned()
|
|
.expect("admitted session snapshot");
|
|
let group = captured.targets().len() > 1;
|
|
let finished = self.behavior.scenario.finish_spacing(screen);
|
|
self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
|
|
match finished {
|
|
Ok(Finish::Click(target)) if group => {
|
|
let target = Target::ScenarioObject(target);
|
|
// The controller only captures a pressed member of this set.
|
|
if captured.targets().contains(&target) {
|
|
self.diagram_selection
|
|
.as_mut()
|
|
.expect("admitted selection")
|
|
.replace(target, |_| true);
|
|
self.inspect_scenario_multiselect_primary();
|
|
}
|
|
}
|
|
Ok(Finish::Click(target)) => self.select(target),
|
|
Ok(Finish::Noop) => {}
|
|
Ok(Finish::Commit {
|
|
command,
|
|
selected,
|
|
scale,
|
|
pan,
|
|
}) => match self.document.execute(*command) {
|
|
Ok(_) => {
|
|
self.selected = if group {
|
|
match captured.primary() {
|
|
Some(Target::ScenarioObject(id)) => *id,
|
|
_ => selected,
|
|
}
|
|
} else {
|
|
selected
|
|
};
|
|
self.selected_node = None;
|
|
self.diagram_navigation_focus = None;
|
|
self.behavior.scenario.accept_spacing_camera(scale, pan);
|
|
self.sync();
|
|
if group {
|
|
self.inspect_scenario_multiselect_primary();
|
|
}
|
|
self.status = if group {
|
|
format!(
|
|
"Spacing updated for {} selected objects. Scenario order is unchanged.",
|
|
captured.targets().len()
|
|
)
|
|
} else {
|
|
"Scenario spacing updated.".into()
|
|
};
|
|
}
|
|
Err(error) => self.status = format!("Spacing was not applied: {error}"),
|
|
},
|
|
Err(error) => self.status = refusal_reason(&error),
|
|
}
|
|
self.reconcile_multiselect();
|
|
self.scenario_reorder_reconcile();
|
|
}
|
|
Event::Cancel { .. } => unreachable!(),
|
|
}
|
|
Task::none()
|
|
}
|
|
}
|