Preserve nested architecture during authoring and distinguish allocation summaries
This commit is contained in:
@@ -11,6 +11,8 @@ pub struct ProjectedNode {
|
||||
pub visual_parent: Option<Id>,
|
||||
pub depth: usize,
|
||||
pub collapsed: bool,
|
||||
/// The work is grouped by its allocated leaves, not by a direct allocation.
|
||||
pub allocation_summary: bool,
|
||||
}
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ProjectedEdge {
|
||||
@@ -42,6 +44,17 @@ pub(crate) fn minimum_node_size(p: &Project, element: Id, mut size: Size) -> Siz
|
||||
}
|
||||
size
|
||||
}
|
||||
pub(crate) fn port_footer(p: &Project, element: Id) -> f32 {
|
||||
let count = p
|
||||
.children(element)
|
||||
.filter(|e| e.kind() == Kind::Port)
|
||||
.count();
|
||||
if count == 0 {
|
||||
24.
|
||||
} else {
|
||||
28. * count as f32 + 24.
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parents(p: &Project, d: &Diagram) -> BTreeMap<Id, Option<Id>> {
|
||||
let placed: BTreeMap<_, _> = d
|
||||
@@ -62,12 +75,22 @@ pub(crate) fn parents(p: &Project, d: &Diagram) -> BTreeMap<Id, Option<Id>> {
|
||||
if d.projection == ProjectionKind::Allocation
|
||||
&& let Some(performer) = crate::validation::allocation_summary(p, n.element)
|
||||
{
|
||||
let keep_parent = semantic.is_some_and(|parent| {
|
||||
crate::validation::allocation_summary(p, parent) == Some(performer)
|
||||
});
|
||||
if !keep_parent {
|
||||
semantic = Some(performer);
|
||||
let mut candidate = semantic;
|
||||
let mut visible_work_parent = None;
|
||||
let mut seen = BTreeSet::new();
|
||||
while let Some(id) = candidate {
|
||||
if !seen.insert(id)
|
||||
|| crate::validation::allocation_summary(p, id) != Some(performer)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if placed.contains_key(&id) {
|
||||
visible_work_parent = Some(id);
|
||||
break;
|
||||
}
|
||||
candidate = p.elements.get(&id).and_then(|e| e.parent);
|
||||
}
|
||||
semantic = visible_work_parent.or(Some(performer));
|
||||
}
|
||||
if d.projection == ProjectionKind::Traceability {
|
||||
return (n.id, None);
|
||||
@@ -117,6 +140,10 @@ pub fn project_view(p: &Project, diagram: Id) -> ProjectedView {
|
||||
let Some(d) = p.diagrams.get(&diagram) else {
|
||||
return ProjectedView::default();
|
||||
};
|
||||
project_diagram(p, d)
|
||||
}
|
||||
/// Project alternate placement geometry without copying or mutating semantics.
|
||||
pub fn project_diagram(p: &Project, d: &Diagram) -> ProjectedView {
|
||||
let parents = parents(p, d);
|
||||
let mut nodes = Vec::new();
|
||||
for n in d.nodes.values() {
|
||||
@@ -145,7 +172,18 @@ pub fn project_view(p: &Project, diagram: Id) -> ProjectedView {
|
||||
current = parents.get(&id).copied().flatten();
|
||||
}
|
||||
if !hidden {
|
||||
let size = minimum_node_size(p, e.id, n.size);
|
||||
let size = minimum_node_size(
|
||||
p,
|
||||
e.id,
|
||||
if n.collapsed {
|
||||
Size {
|
||||
width: n.size.width,
|
||||
height: 94.,
|
||||
}
|
||||
} else {
|
||||
n.size
|
||||
},
|
||||
);
|
||||
nodes.push(ProjectedNode {
|
||||
id: n.id,
|
||||
element: n.element,
|
||||
@@ -154,6 +192,12 @@ pub fn project_view(p: &Project, diagram: Id) -> ProjectedView {
|
||||
visual_parent: parents.get(&n.id).copied().flatten(),
|
||||
depth,
|
||||
collapsed: n.collapsed,
|
||||
allocation_summary: d.projection == ProjectionKind::Allocation
|
||||
&& crate::validation::allocation_summary(p, e.id).is_some()
|
||||
&& !p
|
||||
.relations
|
||||
.values()
|
||||
.any(|r| r.kind() == RelationKind::Allocation && r.target() == e.id),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -164,11 +208,12 @@ pub fn project_view(p: &Project, diagram: Id) -> ProjectedView {
|
||||
.visual_parent
|
||||
.and_then(|id| nodes.iter().position(|n| n.id == id))
|
||||
{
|
||||
let footer = port_footer(p, nodes[parent].element);
|
||||
let p = &mut nodes[parent];
|
||||
let right =
|
||||
(p.position.x + p.size.width).max(child.position.x + child.size.width + 24.);
|
||||
let bottom =
|
||||
(p.position.y + p.size.height).max(child.position.y + child.size.height + 24.);
|
||||
(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.);
|
||||
@@ -209,14 +254,23 @@ pub fn project_view(p: &Project, diagram: Id) -> ProjectedView {
|
||||
let side: Vec<_> = children.iter().filter(|e| matches!(e.shared(), Some(SharedBody::Port {direction: d, ..}) if *d == direction)).collect();
|
||||
for (index, port) in side.iter().enumerate() {
|
||||
let fraction = (index + 1) as f32 / (side.len() + 1) as f32;
|
||||
let y = if p.elements[&node.element].kind().is_performer()
|
||||
|| nodes
|
||||
.iter()
|
||||
.any(|child| child.visual_parent == Some(node.id))
|
||||
{
|
||||
node.position.y + node.size.height - 24. - index as f32 * 28.
|
||||
} else {
|
||||
node.position.y + 64. + (node.size.height - 76.).max(0.) * fraction
|
||||
};
|
||||
let position = match direction {
|
||||
PortDirection::In => Position {
|
||||
x: node.position.x,
|
||||
y: node.position.y + 64. + (node.size.height - 76.).max(0.) * fraction,
|
||||
y,
|
||||
},
|
||||
PortDirection::Out => Position {
|
||||
x: node.position.x + node.size.width,
|
||||
y: node.position.y + 64. + (node.size.height - 76.).max(0.) * fraction,
|
||||
y,
|
||||
},
|
||||
PortDirection::InOut => Position {
|
||||
x: node.position.x + node.size.width * fraction,
|
||||
|
||||
Reference in New Issue
Block a user