Add persisted state-machine diagram contexts and shared geometry
This commit is contained in:
@@ -4,6 +4,10 @@ use serde::{Deserialize, Serialize};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
mod port_layout;
|
||||
pub use port_layout::{port_anchor_offset, port_anchor_position};
|
||||
#[path = "projection/state.rs"]
|
||||
mod state;
|
||||
pub(crate) use state::node_size as state_node_size;
|
||||
pub use state::{state_header_height, state_machine_contains, state_symbol_size};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ProjectedNode {
|
||||
@@ -79,9 +83,13 @@ pub(crate) fn parents(p: &Project, d: &Diagram) -> BTreeMap<Id, Option<Id>> {
|
||||
.nodes
|
||||
.values()
|
||||
.filter(|n| {
|
||||
p.elements
|
||||
.get(&n.element)
|
||||
.is_some_and(|e| e.kind() != Kind::Port && d.projection.includes(e.kind()))
|
||||
p.elements.get(&n.element).is_some_and(|e| {
|
||||
e.kind() != Kind::Port
|
||||
&& d.projection.includes(e.kind())
|
||||
&& d.projection
|
||||
.state_machine()
|
||||
.is_none_or(|machine| state_machine_contains(p, machine, e.id))
|
||||
})
|
||||
})
|
||||
.map(|n| (n.element, n.id))
|
||||
.collect();
|
||||
@@ -191,6 +199,11 @@ pub fn translated_routes(
|
||||
let (source, target) = edge_endpoint_placements(p, d, route.edge)?;
|
||||
let source_delta = deltas.get(&source)?;
|
||||
let target_delta = deltas.get(&target)?;
|
||||
if d.projection.state_machine().is_some()
|
||||
&& !state::boundary_moves_with(p, d, route.edge, source_delta, deltas)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
if source_delta != target_delta
|
||||
|| (source_delta.x == 0. && source_delta.y == 0.)
|
||||
|| route.waypoints.is_empty()
|
||||
@@ -221,7 +234,12 @@ pub fn project_diagram(p: &Project, d: &Diagram) -> ProjectedView {
|
||||
let Some(e) = p.elements.get(&n.element) else {
|
||||
continue;
|
||||
};
|
||||
if e.kind() == Kind::Port || !d.projection.includes(e.kind()) {
|
||||
if e.kind() == Kind::Port
|
||||
|| !d.projection.includes(e.kind())
|
||||
|| d.projection
|
||||
.state_machine()
|
||||
.is_some_and(|machine| !state_machine_contains(p, machine, e.id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if d.projection != ProjectionKind::Traceability && e.layer() != d.layer {
|
||||
@@ -255,6 +273,11 @@ pub fn project_diagram(p: &Project, d: &Diagram) -> ProjectedView {
|
||||
n.size
|
||||
},
|
||||
);
|
||||
let size = if d.projection.state_machine().is_some() {
|
||||
state::node_size(p, e, size, n.collapsed)
|
||||
} else {
|
||||
size
|
||||
};
|
||||
nodes.push(ProjectedNode {
|
||||
id: n.id,
|
||||
element: n.element,
|
||||
@@ -273,7 +296,10 @@ pub fn project_diagram(p: &Project, d: &Diagram) -> ProjectedView {
|
||||
}
|
||||
}
|
||||
nodes.sort_by_key(|n| (n.depth, n.id));
|
||||
enclose(p, &mut nodes);
|
||||
enclose_with_headers(p, &mut nodes, d.projection.state_machine().is_some());
|
||||
if d.projection.state_machine().is_some() {
|
||||
state::snap_boundaries(p, &mut nodes);
|
||||
}
|
||||
let visible: BTreeSet<_> = nodes.iter().map(|n| n.id).collect();
|
||||
let placed: BTreeMap<_, _> = d.nodes.values().map(|n| (n.element, n.id)).collect();
|
||||
let anchor = |element: Id| {
|
||||
@@ -313,6 +339,11 @@ pub fn project_diagram(p: &Project, d: &Diagram) -> ProjectedView {
|
||||
continue;
|
||||
}
|
||||
let included = match d.projection {
|
||||
ProjectionKind::StateMachine { machine } => matches!(&r.body,
|
||||
RelationBody::StateTransition { transition_kind, .. }
|
||||
if *transition_kind != TransitionKind::Internal
|
||||
&& state_machine_contains(p, machine, r.source())
|
||||
&& state_machine_contains(p, machine, r.target())),
|
||||
ProjectionKind::Allocation => matches!(
|
||||
r.kind(),
|
||||
RelationKind::Interaction
|
||||
@@ -338,7 +369,12 @@ pub fn project_diagram(p: &Project, d: &Diagram) -> ProjectedView {
|
||||
continue;
|
||||
}
|
||||
if let (Some(source), Some(target)) = (anchor(r.source()), anchor(r.target()))
|
||||
&& source != target
|
||||
&& (source != target
|
||||
|| (d.projection.state_machine().is_some()
|
||||
&& r.source() == r.target()
|
||||
&& nodes
|
||||
.iter()
|
||||
.any(|n| n.id == source && n.element == r.source())))
|
||||
{
|
||||
let (source_port, target_port) = match &r.body {
|
||||
RelationBody::FunctionalExchange {
|
||||
@@ -384,7 +420,13 @@ pub fn project_diagram(p: &Project, d: &Diagram) -> ProjectedView {
|
||||
}
|
||||
}
|
||||
}
|
||||
let ports = port_layout::layout(p, d, &mut nodes, &edges);
|
||||
// Dedicated state views contain state symbols, not semantic component ports.
|
||||
// Port layout would re-enclose already snapped boundary vertices.
|
||||
let ports = if d.projection.state_machine().is_some() {
|
||||
Vec::new()
|
||||
} else {
|
||||
port_layout::layout(p, d, &mut nodes, &edges)
|
||||
};
|
||||
ProjectedView {
|
||||
nodes,
|
||||
edges,
|
||||
@@ -393,13 +435,28 @@ pub fn project_diagram(p: &Project, d: &Diagram) -> ProjectedView {
|
||||
}
|
||||
|
||||
fn enclose(p: &Project, nodes: &mut [ProjectedNode]) {
|
||||
enclose_with_headers(p, nodes, false);
|
||||
}
|
||||
fn enclose_with_headers(p: &Project, nodes: &mut [ProjectedNode], state_view: bool) {
|
||||
for index in (0..nodes.len()).rev() {
|
||||
let child = nodes[index].clone();
|
||||
if state_view
|
||||
&& p.elements
|
||||
.get(&child.element)
|
||||
.is_some_and(|e| matches!(e.kind(), Kind::EntryPoint | Kind::ExitPoint))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if let Some(parent) = child
|
||||
.visual_parent
|
||||
.and_then(|id| nodes.iter().position(|n| n.id == id))
|
||||
{
|
||||
let footer = port_footer(p, nodes[parent].element);
|
||||
let header = if state_view {
|
||||
state_header_height(p, nodes[parent].element)
|
||||
} else {
|
||||
64.
|
||||
};
|
||||
let p = &mut nodes[parent];
|
||||
let right =
|
||||
(p.position.x + p.size.width).max(child.position.x + child.size.width + 24.);
|
||||
@@ -407,7 +464,7 @@ fn enclose(p: &Project, nodes: &mut [ProjectedNode]) {
|
||||
(p.position.y + p.size.height).max(child.position.y + child.size.height + footer);
|
||||
p.position.x = p.position.x.min(child.position.x - 24.);
|
||||
// Reserve two readable header lines above the first nested card.
|
||||
p.position.y = p.position.y.min(child.position.y - 64.);
|
||||
p.position.y = p.position.y.min(child.position.y - header);
|
||||
p.size.width = right - p.position.x;
|
||||
p.size.height = bottom - p.position.y;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user