Add exact Process selection and atomic column-slot group reorder

Preserve the full-desktop-1 captured source. Qualified previews inspect their exact subject read-only; group movement preserves source order and other columns. Native acceptance and strict-lint payload-size corrections follow separately.
This commit is contained in:
2026-09-23 05:39:10 -07:00
parent bed7f825fd
commit 1711bff650
25 changed files with 3993 additions and 145 deletions
+72 -8
View File
@@ -18,6 +18,7 @@ pub struct Request {
pub route_segment: Option<(usize, Position)>,
pub creation_point: Option<Position>,
pub canvas: Option<route_edit::Context>,
pub process: Option<crate::behavior::topology::Context>,
pub source_file: Option<String>,
}
impl Request {
@@ -32,6 +33,7 @@ impl Request {
route_segment: None,
creation_point: None,
canvas: None,
process: None,
source_file: None,
}
}
@@ -61,6 +63,9 @@ impl Opened {
|| request
.canvas
.is_some_and(|c| Some(c) != current_canvas(app))
|| request
.process
.is_some_and(|c| Some(c) != app.topology_context())
{
return None;
}
@@ -88,6 +93,10 @@ impl Opened {
.request
.canvas
.is_none_or(|c| Some(c) == current_canvas(app))
&& self
.request
.process
.is_none_or(|c| Some(c) == app.topology_context())
&& self
.request
.source_file
@@ -101,10 +110,24 @@ fn target_matches(app: &App, request: &Request) -> bool {
let Some(target) = request.selection_target.as_deref() else {
return true;
};
if request.canvas.is_none() || !app.multiselect_target_visible(target) {
if (request.canvas.is_none() && request.process.is_none())
|| !app.multiselect_target_visible(target)
{
return false;
}
match target {
Target::ProcessOccurrence(endpoint) => {
request.process.is_some()
&& request.canvas.is_none()
&& request.placement.is_none()
&& endpoint.occurrence == request.object
}
Target::ProcessLink(id) | Target::ProcessPreviewLink { relation: id, .. } => {
request.process.is_some()
&& request.canvas.is_none()
&& request.placement.is_none()
&& *id == request.object
}
Target::Placement(id) => {
request.placement == Some(*id)
&& request
@@ -132,7 +155,9 @@ fn current_canvas(app: &App) -> Option<route_edit::Context> {
fn current_diagram(app: &App) -> Option<Id> {
if app.page == Page::Behavior {
app.state_context().map(|context| context.diagram)
app.topology_context()
.map(|context| context.owner)
.or_else(|| app.state_context().map(|context| context.diagram))
} else {
app.active_diagram
}
@@ -156,6 +181,7 @@ pub fn trigger(app: &App) -> iced::Element<'_, Message> {
request.canvas = app.route_context();
} else if app.page == Page::Behavior {
request.canvas = app.state_context();
request.process = app.topology_context();
if let Some(target) = app.diagram_selection.as_ref().and_then(|s| s.primary()) {
request.selection_target = Some(Box::new(target.clone()));
request.placement = match target {
@@ -233,7 +259,7 @@ pub fn popup(app: &App) -> Option<Menu<Message>> {
});
}
let mut entries = vec![Entry::title(crate::views::short(p.named(id), 38))];
if request.canvas.is_some()
if (request.canvas.is_some() || request.process.is_some())
&& let Some(context) = app.multiselect_context()
{
use crate::app::multiselect::{Choice, Event as SelectionEvent};
@@ -311,11 +337,20 @@ pub fn popup(app: &App) -> Option<Menu<Message>> {
)),
true,
));
entries.push(item(
"Hide selected placements",
Message::DiagramSelection(SelectionEvent::Hide(snapshot.clone())),
crate::app::multiselect::placements(&snapshot).is_some(),
));
if request.process.is_some() {
entries.push(Entry::title(
"Drag contributions within one column to reorder",
));
entries.push(Entry::disabled(
"Hide unavailable · cards are model contributions",
));
} else {
entries.push(item(
"Hide selected placements",
Message::DiagramSelection(SelectionEvent::Hide(snapshot.clone())),
crate::app::multiselect::placements(&snapshot).is_some(),
));
}
entries.push(Entry::disabled(
"Delete selected from model (not yet available)",
));
@@ -327,6 +362,35 @@ pub fn popup(app: &App) -> Option<Menu<Message>> {
});
}
entries.push(Entry::separator());
if let Some(context) = request.process {
let endpoint = match request.selection_target.as_deref() {
Some(Target::ProcessOccurrence(endpoint))
if !endpoint.references.is_empty() =>
{
Some(endpoint.clone())
}
Some(Target::ProcessPreviewLink { source, .. }) => Some(source.clone()),
_ => None,
};
if let Some(endpoint) = endpoint {
entries.push(Entry::title("Reused contents · read-only in this process"));
entries.push(item(
"Open referenced definition",
Message::TopologyCanvas(
crate::behavior::topology::CanvasEvent::OpenQualified(
context, endpoint,
),
),
true,
));
return Some(Menu {
token: opened.token,
anchor: request.anchor,
entries,
on_close: Message::Menu(Event::Close),
});
}
}
}
}
if app.page == Page::Behavior