Merge Allocation drawing, Traceability guidance, and picker Escape candidate
This commit is contained in:
@@ -14,6 +14,7 @@ mod diagram_reveal;
|
||||
#[cfg(test)]
|
||||
mod diagram_selection_tests;
|
||||
mod diagram_usages;
|
||||
pub(crate) mod escape;
|
||||
mod hosted;
|
||||
pub(crate) mod multiselect;
|
||||
#[cfg(test)]
|
||||
@@ -35,6 +36,8 @@ mod state_multiselect_focus_tests;
|
||||
mod state_multiselect_tests;
|
||||
pub(crate) mod text_workspace;
|
||||
#[cfg(test)]
|
||||
mod traceability_navigation_tests;
|
||||
#[cfg(test)]
|
||||
mod transition_context_tests;
|
||||
#[cfg(test)]
|
||||
mod view_copy_tests;
|
||||
@@ -374,6 +377,7 @@ pub enum Message {
|
||||
Page(Page),
|
||||
Guide(crate::guide::Event),
|
||||
Traceability(crate::views::traceability::Event),
|
||||
ReviewLayerTraces,
|
||||
Behavior(crate::behavior::Event),
|
||||
Layer(Layer),
|
||||
Select(Id),
|
||||
@@ -1235,7 +1239,7 @@ impl App {
|
||||
crate::model_menu::popup(self).or_else(|| self.text_workspace_menu()),
|
||||
)
|
||||
.into();
|
||||
crate::source_navigation::interaction::observe(
|
||||
let content = crate::source_navigation::interaction::observe(
|
||||
crate::behavior::scenario::focus::root(
|
||||
behavior::canvas_workspace::focus_root(
|
||||
crate::hosted::view::overlay(
|
||||
@@ -1247,7 +1251,8 @@ impl App {
|
||||
self.scenario_focus_owner(),
|
||||
),
|
||||
self.source_symbols.raw_interaction.clone(),
|
||||
)
|
||||
);
|
||||
escape::root(self, content)
|
||||
}
|
||||
pub fn subscription(&self) -> Subscription<Message> {
|
||||
Subscription::batch([
|
||||
@@ -1289,7 +1294,6 @@ impl App {
|
||||
iced::time::every(std::time::Duration::from_secs(10)).map(|_| Message::CheckpointTick),
|
||||
window::close_requests().map(|id| Message::Request(Pending::Close(id))),
|
||||
window::resize_events().map(|(_, size)| Message::WindowResized(size)),
|
||||
architecture_authoring::subscription(self),
|
||||
iced::event::listen_with(|event, _, _| {
|
||||
matches!(
|
||||
event,
|
||||
@@ -1297,20 +1301,6 @@ impl App {
|
||||
)
|
||||
.then_some(Message::WorkDrag(allocation_drag::Event::KeyboardBoundary))
|
||||
}),
|
||||
// Use one captured-Escape bridge: a focused Find input consumes
|
||||
// Escape, while modal priority remains in the ordinary handler.
|
||||
behavior::creation::subscription(
|
||||
self.text_workspace.menu().is_none()
|
||||
&& (self.behavior.creation.has_pending()
|
||||
|| self.diagram_usages.is_some()
|
||||
|| self.behavior.source_active()
|
||||
|| (self.page == Page::Text
|
||||
&& (self.text_workspace.drawer().is_some()
|
||||
|| self.source_navigation.open
|
||||
|| self.source_rename.open())
|
||||
&& self.architecture.is_none())),
|
||||
)
|
||||
.map(|()| Message::Escape),
|
||||
iced::event::listen_with(|event, status, _| {
|
||||
if matches!(event, iced::Event::Window(window::Event::Unfocused)) {
|
||||
return Some(Message::WorkDrag(allocation_drag::Event::Unfocused));
|
||||
@@ -2690,7 +2680,30 @@ impl App {
|
||||
{
|
||||
return self.update(*action);
|
||||
}
|
||||
self.select(menu.request.object);
|
||||
// Show acts on the diagram captured by the validated menu.
|
||||
// Ordinary cross-layer selection would switch to that element's
|
||||
// preferred same-layer view before the placement command runs.
|
||||
let show_in_mixed_view = matches!(&*action, Message::PlaceSelected)
|
||||
&& self
|
||||
.active_diagram
|
||||
.and_then(|id| self.project().diagrams.get(&id))
|
||||
.is_some_and(|d| {
|
||||
d.projection == ProjectionKind::Traceability
|
||||
&& self
|
||||
.project()
|
||||
.elements
|
||||
.get(&menu.request.object)
|
||||
.is_some_and(|e| {
|
||||
e.kind() != Kind::Port && d.projection.includes(e.kind())
|
||||
})
|
||||
});
|
||||
if show_in_mixed_view {
|
||||
self.selected = menu.request.object;
|
||||
self.selected_attachment = None;
|
||||
self.refresh_draft();
|
||||
} else {
|
||||
self.select(menu.request.object);
|
||||
}
|
||||
if self.page == Page::Behavior && menu.request.canvas.is_some() {
|
||||
self.behavior.diagram.placement = menu.request.placement;
|
||||
self.selected_node = None;
|
||||
@@ -2895,6 +2908,23 @@ impl App {
|
||||
iced::widget::scrollable::RelativeOffset::START,
|
||||
);
|
||||
}
|
||||
Message::ReviewLayerTraces => {
|
||||
// Reuse Page navigation so modal, placement and behavior-form
|
||||
// guards run before changing the transient inventory filter.
|
||||
let navigation = self.update(Message::Page(Page::Traces));
|
||||
if self.page == Page::Traces && !self.behavior.has_pending() {
|
||||
self.traceability.filter = crate::views::traceability::Filter::Traces;
|
||||
self.traceability.query.clear();
|
||||
return Task::batch([
|
||||
navigation,
|
||||
iced::widget::operation::snap_to(
|
||||
"traceability-list",
|
||||
iced::widget::scrollable::RelativeOffset::START,
|
||||
),
|
||||
]);
|
||||
}
|
||||
return navigation;
|
||||
}
|
||||
Message::Search(value) => {
|
||||
self.search = value;
|
||||
self.explorer_page = 0;
|
||||
@@ -4957,6 +4987,14 @@ impl App {
|
||||
if !crate::connection_tool::Tool::for_layer(self.layer).contains(&tool) {
|
||||
return;
|
||||
}
|
||||
if tool == crate::connection_tool::Tool::Allocation
|
||||
&& !tool.available_in(
|
||||
self.layer,
|
||||
self.project().diagrams[&context.diagram].projection,
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
|
||||
let context = self.route_context().unwrap();
|
||||
self.connection = Some(Session::new(
|
||||
@@ -4987,14 +5025,32 @@ impl App {
|
||||
if let Some(source) = session.source {
|
||||
match session.tool.command(self.project(), source, id) {
|
||||
Ok(command) => {
|
||||
let previous_layer = self.layer;
|
||||
if session.tool == crate::connection_tool::Tool::Allocation {
|
||||
// Accepted edits belong to the captured view. A
|
||||
// retained mixed view can differ from the current
|
||||
// perspective; sync must not choose another view.
|
||||
self.layer = self.project().diagrams[&context.diagram].layer;
|
||||
}
|
||||
if self.execute(command) {
|
||||
self.selected_node = None;
|
||||
self.inspector_tab = Default::default();
|
||||
self.status = format!(
|
||||
"{} created · edit its name and carried items in Properties · Undo restores the complete operation",
|
||||
session.tool.label()
|
||||
);
|
||||
self.status = if session.tool
|
||||
== crate::connection_tool::Tool::Allocation
|
||||
{
|
||||
format!(
|
||||
"Allocated {} to {} · view filters are unchanged · Undo restores the complete operation",
|
||||
self.project().named(id),
|
||||
self.project().named(source)
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"{} created · edit its name and carried items in Properties · Undo restores the complete operation",
|
||||
session.tool.label()
|
||||
)
|
||||
};
|
||||
} else {
|
||||
self.layer = previous_layer;
|
||||
self.connection = Some(session);
|
||||
}
|
||||
}
|
||||
@@ -5009,7 +5065,18 @@ impl App {
|
||||
&project_view(self.project(), context.diagram),
|
||||
id,
|
||||
);
|
||||
self.select(id);
|
||||
if session.tool == crate::connection_tool::Tool::Allocation
|
||||
&& self.project().diagrams[&context.diagram].projection
|
||||
== ProjectionKind::Traceability
|
||||
{
|
||||
// Mixed views can allocate either perspective. Choosing
|
||||
// a visible source must not navigate to its preferred view.
|
||||
self.selected = id;
|
||||
self.selected_attachment = None;
|
||||
self.refresh_draft();
|
||||
} else {
|
||||
self.select(id);
|
||||
}
|
||||
self.status = if session.candidates.is_empty() {
|
||||
"No compatible targets are visible. Expand containers or show a compatible endpoint in this view; Esc cancels drawing.".into()
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user