Merge ordinary and State multiselect into the desktop candidate
This commit is contained in:
@@ -15,6 +15,9 @@ mod diagram_reveal;
|
||||
mod diagram_selection_tests;
|
||||
mod diagram_usages;
|
||||
mod hosted;
|
||||
pub(crate) mod multiselect;
|
||||
#[cfg(test)]
|
||||
mod multiselect_tests;
|
||||
mod reference_authoring;
|
||||
#[cfg(test)]
|
||||
mod review_navigation_tests;
|
||||
@@ -26,6 +29,10 @@ mod scenario_spacing;
|
||||
mod source_indentation_tests;
|
||||
pub(crate) mod source_rename;
|
||||
mod source_symbols;
|
||||
#[cfg(test)]
|
||||
mod state_multiselect_focus_tests;
|
||||
#[cfg(test)]
|
||||
mod state_multiselect_tests;
|
||||
pub(crate) mod text_workspace;
|
||||
#[cfg(test)]
|
||||
mod transition_context_tests;
|
||||
@@ -371,6 +378,7 @@ pub enum Message {
|
||||
Select(Id),
|
||||
EditProperties(Id),
|
||||
SelectNode(Id),
|
||||
DiagramSelection(multiselect::Event),
|
||||
SelectProject,
|
||||
Search(String),
|
||||
StartTransition,
|
||||
@@ -515,6 +523,7 @@ pub struct App {
|
||||
pub layer: Layer,
|
||||
pub selected: Id,
|
||||
pub selected_node: Option<Id>,
|
||||
pub diagram_selection: Option<crate::diagram_selection::Selection>,
|
||||
pub draft: Option<Draft>,
|
||||
pub active_diagram: Option<Id>,
|
||||
pub search: String,
|
||||
@@ -636,6 +645,7 @@ impl App {
|
||||
layer,
|
||||
selected,
|
||||
selected_node: None,
|
||||
diagram_selection: None,
|
||||
draft,
|
||||
active_diagram,
|
||||
search: String::new(),
|
||||
@@ -1337,6 +1347,38 @@ impl App {
|
||||
])
|
||||
}
|
||||
pub fn update(&mut self, message: Message) -> Task<Message> {
|
||||
let selection_boundary = self.multiselect_boundary();
|
||||
// Reject delayed gestures before they can retire newer input ownership.
|
||||
if let Message::DiagramSelection(event)
|
||||
| Message::StateDiagram(crate::behavior::state_edit::Event::Selection(event)) = &message
|
||||
&& !self.multiselect_envelope(event)
|
||||
{
|
||||
return Task::none();
|
||||
}
|
||||
if let Message::StateDiagram(event) = &message
|
||||
&& let Some(captured) = event.context()
|
||||
&& self.state_context().is_none_or(|current| {
|
||||
crate::behavior::state_edit::validate_context(self.project(), captured, current)
|
||||
.is_err()
|
||||
})
|
||||
{
|
||||
return Task::none();
|
||||
}
|
||||
if matches!(
|
||||
&message,
|
||||
Message::StateDiagram(
|
||||
crate::behavior::state_edit::Event::Select(..)
|
||||
| crate::behavior::state_edit::Event::Move { .. }
|
||||
| crate::behavior::state_edit::Event::Remove(_)
|
||||
)
|
||||
) && self
|
||||
.multiselect_snapshot()
|
||||
.is_some_and(|s| s.targets().len() > 1)
|
||||
{
|
||||
// A queued singleton cannot retire the current group's navigation
|
||||
// or allocation ownership before its edit is refused.
|
||||
return Task::none();
|
||||
}
|
||||
// Captured Text commands validate their origin before they can
|
||||
// invalidate that same origin or pass into existing command handlers.
|
||||
if let Message::TextWorkspace(event) = message {
|
||||
@@ -1439,7 +1481,11 @@ impl App {
|
||||
self.reconcile_pending_diagram_reveal(&message);
|
||||
self.allocation_before(&message);
|
||||
if let Message::WorkDrag(event) = message {
|
||||
return self.allocation_event(event);
|
||||
let task = self.allocation_event(event);
|
||||
if self.multiselect_boundary() != selection_boundary {
|
||||
self.reconcile_multiselect();
|
||||
}
|
||||
return task;
|
||||
}
|
||||
let allocation_before = self.allocation_boundary();
|
||||
if let Message::BehaviorControl(intent) = message {
|
||||
@@ -1480,6 +1526,11 @@ impl App {
|
||||
self.camera_epoch = self.camera_epoch.wrapping_add(1);
|
||||
self.camera_boundary_epoch = self.canvas_epoch;
|
||||
}
|
||||
if self.multiselect_boundary() != selection_boundary
|
||||
|| (self.diagram_selection.is_none() && self.multiselect_context().is_some())
|
||||
{
|
||||
self.reconcile_multiselect();
|
||||
}
|
||||
task
|
||||
}
|
||||
fn behavior_control(&mut self, intent: behavior::canvas_workspace::Intent) -> Task<Message> {
|
||||
@@ -1847,6 +1898,7 @@ impl App {
|
||||
message,
|
||||
Message::Page(_) | Message::Layer(_) | Message::Diagram(_)
|
||||
) {
|
||||
self.diagram_selection = None;
|
||||
self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
|
||||
self.selected_bend = None;
|
||||
}
|
||||
@@ -2563,8 +2615,17 @@ impl App {
|
||||
"Apply or revert pending edits before using modeling actions.".into();
|
||||
return Task::none();
|
||||
}
|
||||
if matches!(*action, Message::DiagramSelection(_)) {
|
||||
return self.update(*action);
|
||||
}
|
||||
self.select(menu.request.object);
|
||||
self.selected_node = menu.request.placement;
|
||||
if self.page == Page::Behavior && menu.request.canvas.is_some() {
|
||||
self.behavior.diagram.placement = menu.request.placement;
|
||||
self.selected_node = None;
|
||||
} else {
|
||||
self.selected_node = menu.request.placement;
|
||||
}
|
||||
self.reconcile_multiselect();
|
||||
self.selected_bend = menu.request.bend.map(|i| (menu.request.object, i));
|
||||
return self.update(*action);
|
||||
}
|
||||
@@ -2722,6 +2783,7 @@ impl App {
|
||||
self.status = "Property draft reverted".into();
|
||||
}
|
||||
Message::Select(id) => self.select(id),
|
||||
Message::DiagramSelection(event) => self.multiselect_event(event),
|
||||
Message::EditProperties(id) => {
|
||||
self.select(id);
|
||||
self.inspector_tab = crate::navigation::InspectorTab::Properties;
|
||||
@@ -3395,7 +3457,21 @@ impl App {
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::DiagramRemove(intent) => self.remove_from_diagram(intent),
|
||||
Message::DiagramRemove(intent) => {
|
||||
if self
|
||||
.multiselect_snapshot()
|
||||
.is_some_and(|s| s.targets().len() > 1)
|
||||
{
|
||||
// A pre-redraw singleton Delete cannot remove just one
|
||||
// member of a selection changed earlier in the same batch.
|
||||
if Some(intent.context) == self.route_context() {
|
||||
self.status =
|
||||
"Selection changed. Use Hide selected or press Delete again.".into();
|
||||
}
|
||||
} else {
|
||||
self.remove_from_diagram(intent);
|
||||
}
|
||||
}
|
||||
Message::DiagramTraverse(back) => {
|
||||
return if back {
|
||||
iced::widget::operation::focus_previous()
|
||||
@@ -3404,6 +3480,13 @@ impl App {
|
||||
};
|
||||
}
|
||||
Message::Delete => {
|
||||
if self
|
||||
.multiselect_snapshot()
|
||||
.is_some_and(|s| s.targets().len() > 1)
|
||||
{
|
||||
self.status = "Group model-deletion review is not available yet. Select one element or use Hide selected.".into();
|
||||
return Task::none();
|
||||
}
|
||||
if self.selected != self.project().id && self.apply() {
|
||||
self.begin_model_delete(None);
|
||||
}
|
||||
@@ -3556,6 +3639,13 @@ impl App {
|
||||
}
|
||||
}
|
||||
Message::HideSelected => {
|
||||
if let Some(snapshot) = self
|
||||
.multiselect_snapshot()
|
||||
.filter(|s| s.targets().len() > 1)
|
||||
{
|
||||
self.multiselect_event(multiselect::Event::Hide(snapshot));
|
||||
return Task::none();
|
||||
}
|
||||
if self.apply()
|
||||
&& let Some(node) = self.selected_node
|
||||
{
|
||||
@@ -3585,6 +3675,13 @@ impl App {
|
||||
}
|
||||
Message::Route(event) => self.edit_route(event),
|
||||
Message::MoveNode(node, position) => {
|
||||
if self
|
||||
.multiselect_snapshot()
|
||||
.is_some_and(|s| s.targets().len() > 1)
|
||||
{
|
||||
self.status = "Selection changed. Start the group drag again.".into();
|
||||
return Task::none();
|
||||
}
|
||||
let original = self.selected;
|
||||
if self.apply()
|
||||
&& let Some(diagram) = self.active_diagram
|
||||
|
||||
Reference in New Issue
Block a user