Support per-view connection boundary attachments in ordinary and state diagrams
This commit is contained in:
@@ -20,6 +20,7 @@ pub enum Tool {
|
||||
Region,
|
||||
Transition,
|
||||
Reconnect { relation: Id, endpoint: Endpoint },
|
||||
AdjustAttachment { relation: Id, end: EdgeEnd },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -66,6 +67,13 @@ pub enum Event {
|
||||
endpoint: Endpoint,
|
||||
vertex: Id,
|
||||
},
|
||||
SetAttachment {
|
||||
context: Context,
|
||||
relation: Id,
|
||||
end: EdgeEnd,
|
||||
placement: Id,
|
||||
attachment: Option<EdgeAttachment>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Event {
|
||||
@@ -84,7 +92,8 @@ impl Event {
|
||||
| Self::Place { context: c, .. }
|
||||
| Self::Move { context: c, .. }
|
||||
| Self::Connect { context: c, .. }
|
||||
| Self::Reconnect { context: c, .. } => Some(*c),
|
||||
| Self::Reconnect { context: c, .. }
|
||||
| Self::SetAttachment { context: c, .. } => Some(*c),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -249,6 +258,36 @@ pub fn can_reconnect(
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
/// Only the actual visible transition owner can receive a visual attachment.
|
||||
/// Folded promotion, semantic ports and internal-transition rows are excluded.
|
||||
pub fn attachment_node(
|
||||
project: &Project,
|
||||
diagram: Id,
|
||||
relation: Id,
|
||||
end: EdgeEnd,
|
||||
) -> Option<projection::ProjectedNode> {
|
||||
machine(project, diagram).ok()?;
|
||||
let relation_record = project.relations.get(&relation)?;
|
||||
if relation_record.kind() != RelationKind::StateTransition
|
||||
|| oa_core::routing::semantic_port_endpoint(project, relation, end).is_some()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let view = projection::project_view(project, diagram);
|
||||
let edge = view.edges.iter().find(|edge| edge.id == relation)?;
|
||||
let stored = project.diagrams.get(&diagram)?;
|
||||
if projection::edge_endpoint_placements(project, stored, relation)?
|
||||
!= (edge.source, edge.target)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let owner = match end {
|
||||
EdgeEnd::Source => edge.source,
|
||||
EdgeEnd::Target => edge.target,
|
||||
};
|
||||
view.nodes.iter().find(|node| node.id == owner).cloned()
|
||||
}
|
||||
|
||||
fn checked(project: &Project, plan: Plan) -> Result<Plan, ModelError> {
|
||||
prepare_command(project, &plan.command)?;
|
||||
Ok(plan)
|
||||
@@ -492,6 +531,22 @@ pub fn plan(project: &Project, current: Context, event: &Event) -> Result<Plan,
|
||||
relation,
|
||||
None,
|
||||
),
|
||||
Event::SetAttachment {
|
||||
relation,
|
||||
end,
|
||||
placement,
|
||||
attachment,
|
||||
..
|
||||
} => {
|
||||
attachment_node(project, diagram, relation, end)
|
||||
.filter(|owner| owner.id == placement)
|
||||
.ok_or_else(|| {
|
||||
error("Choose the actual visible transition endpoint in this view.")
|
||||
})?;
|
||||
let mut route = crate::route_edit::stored_route(&project.diagrams[&diagram], relation);
|
||||
route.set_attachment(end, attachment);
|
||||
(Command::SetEdgeRoute { diagram, route }, relation, None)
|
||||
}
|
||||
_ => {
|
||||
return Err(error(
|
||||
"Handle this navigation or tool request without a model command.",
|
||||
@@ -580,6 +635,85 @@ mod tests {
|
||||
plan.diagram
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attachment_reset_preserves_other_end_pins_label_and_rejects_ineligible_owners() {
|
||||
let mut f = fixture();
|
||||
let diagram = create(&mut f);
|
||||
let c = context(diagram);
|
||||
let edge = f.project.relate(
|
||||
RelationKind::StateTransition,
|
||||
"Leave work",
|
||||
f.leaf,
|
||||
f.target,
|
||||
);
|
||||
let attachment = EdgeAttachment {
|
||||
side: PortAnchorSide::Top,
|
||||
offset: 0.3,
|
||||
};
|
||||
let mut route = crate::route_edit::stored_route(&f.project.diagrams[&diagram], edge);
|
||||
route.source_attachment = Some(attachment);
|
||||
route.target_attachment = Some(attachment);
|
||||
route.waypoints.push(Position { x: 700., y: 700. });
|
||||
// Retain the exact label value alongside the other route components.
|
||||
route.label = Some(EdgeLabel {
|
||||
fraction: 0.5,
|
||||
offset: Position { x: 10., y: 20. },
|
||||
});
|
||||
f.project
|
||||
.diagrams
|
||||
.get_mut(&diagram)
|
||||
.unwrap()
|
||||
.routes
|
||||
.push(route.clone());
|
||||
let owner = attachment_node(&f.project, diagram, edge, EdgeEnd::Source).unwrap();
|
||||
let event = Event::SetAttachment {
|
||||
context: c,
|
||||
relation: edge,
|
||||
end: EdgeEnd::Source,
|
||||
placement: owner.id,
|
||||
attachment: None,
|
||||
};
|
||||
let accepted = prepare_command(&f.project, &plan(&f.project, c, &event).unwrap().command)
|
||||
.unwrap()
|
||||
.project;
|
||||
route.source_attachment = None;
|
||||
assert_eq!(accepted.diagrams[&diagram].routes, vec![route]);
|
||||
assert_eq!(accepted.relations, f.project.relations);
|
||||
let wrong = Event::SetAttachment {
|
||||
context: c,
|
||||
relation: edge,
|
||||
end: EdgeEnd::Source,
|
||||
placement: node(&f.project, diagram, f.target),
|
||||
attachment: Some(attachment),
|
||||
};
|
||||
assert!(plan(&f.project, c, &wrong).is_err());
|
||||
let composite = node(&f.project, diagram, f.composite);
|
||||
f.project
|
||||
.diagrams
|
||||
.get_mut(&diagram)
|
||||
.unwrap()
|
||||
.nodes
|
||||
.get_mut(&composite)
|
||||
.unwrap()
|
||||
.collapsed = true;
|
||||
assert!(attachment_node(&f.project, diagram, edge, EdgeEnd::Source).is_none());
|
||||
assert!(plan(&f.project, c, &event).is_err());
|
||||
let internal = f.project.relate(
|
||||
RelationKind::StateTransition,
|
||||
"Internal",
|
||||
f.target,
|
||||
f.target,
|
||||
);
|
||||
if let RelationBody::StateTransition {
|
||||
transition_kind, ..
|
||||
} = &mut f.project.relations.get_mut(&internal).unwrap().body
|
||||
{
|
||||
*transition_kind = TransitionKind::Internal;
|
||||
}
|
||||
assert!(attachment_node(&f.project, diagram, internal, EdgeEnd::Source).is_none());
|
||||
assert!(attachment_node(&f.project, diagram, internal, EdgeEnd::Target).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn view_initializes_only_its_machine_in_one_source_and_model_history_entry() {
|
||||
let f = fixture();
|
||||
@@ -763,6 +897,8 @@ mod tests {
|
||||
.routes
|
||||
.push(EdgeRoute {
|
||||
edge,
|
||||
source_attachment: None,
|
||||
target_attachment: None,
|
||||
waypoints: vec![pin],
|
||||
label: None,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user