use std::{collections::HashMap, rc::Rc};
use orrery_core::{
draw::{Arrow, ArrowWithText, Drawable, PositionedArrowWithText, Shape, ShapeWithText, Text},
geometry::{Insets, Size},
identifier::Id,
semantic::Block,
};
use crate::{
error::RenderError,
layout::{
component::{self, Component, Layout},
engines::{ComponentEngine, EmbeddedLayouts, graphviz::dot_bridge::DotBridge},
layer::{ContentStack, PositionedContent},
},
structure::{ComponentGraph, ContainmentScope},
};
pub struct Engine {
container_padding: Insets,
}
impl Engine {
pub fn new() -> Self {
Self {
container_padding: Insets::uniform(20.0),
}
}
pub fn set_container_padding(&mut self, padding: Insets) -> &mut Self {
self.container_padding = padding;
self
}
fn calculate_layout<'a>(
&self,
graph: &'a ComponentGraph<'a, '_>,
embedded_layouts: &EmbeddedLayouts<'a>,
) -> Result<ContentStack<Layout<'a>>, RenderError> {
let mut content_stack = ContentStack::<Layout<'a>>::new();
let mut positioned_content_sizes = HashMap::<Id, Size>::new();
for containment_scope in graph.containment_scopes() {
let positioned_content = self.layout_containment_scope(
graph,
containment_scope,
&positioned_content_sizes,
embedded_layouts,
)?;
if let Some(container) = containment_scope.container() {
let size = positioned_content.layout_size();
positioned_content_sizes.insert(container, size);
}
content_stack.push(positioned_content);
}
component::adjust_positioned_contents_offset(&mut content_stack, graph)?;
Ok(content_stack)
}
fn layout_containment_scope<'a>(
&self,
graph: &'a ComponentGraph<'a, '_>,
containment_scope: &ContainmentScope<'a, 'a>,
positioned_content_sizes: &HashMap<Id, Size>,
embedded_layouts: &EmbeddedLayouts<'a>,
) -> Result<PositionedContent<Layout<'a>>, RenderError> {
if containment_scope.nodes_count() == 0 {
return Ok(PositionedContent::new(Layout::new(vec![], vec![])));
}
let mut component_shapes = self.calculate_component_shapes(
graph,
containment_scope,
positioned_content_sizes,
embedded_layouts,
)?;
let component_sizes: HashMap<Id, Size> = component_shapes
.iter()
.map(|(idx, shape_with_text)| (*idx, shape_with_text.size()))
.collect();
let bridge = DotBridge::new(graph, containment_scope, &component_sizes)?;
let layout_result = bridge.run()?;
let components: Vec<Component> = graph
.scope_nodes(containment_scope)
.map(|node| {
let position = layout_result.position(node.id()).ok_or_else(|| {
RenderError::Layout(format!("position not found for `{node}`"))
})?;
let shape_with_text = component_shapes
.remove(&node.id())
.ok_or_else(|| RenderError::Layout(format!("shape not found for `{node}`")))?;
Ok(Component::new(node, shape_with_text, position))
})
.collect::<Result<_, RenderError>>()?;
let relations: Vec<PositionedArrowWithText> = layout_result
.into_edge_paths()
.into_iter()
.map(|(relation, path)| {
let arrow_def = Rc::clone(relation.arrow_definition());
let arrow = Arrow::new(arrow_def, relation.arrow_direction());
let arrow_with_text = ArrowWithText::new(arrow, relation.text());
PositionedArrowWithText::new(arrow_with_text, path)
})
.collect();
Ok(PositionedContent::new(Layout::new(components, relations)))
}
fn calculate_component_shapes<'a>(
&self,
graph: &'a ComponentGraph<'a, '_>,
containment_scope: &ContainmentScope,
positioned_content_sizes: &HashMap<Id, Size>,
embedded_layouts: &EmbeddedLayouts<'a>,
) -> Result<HashMap<Id, ShapeWithText<'a>>, RenderError> {
let mut component_shapes: HashMap<Id, ShapeWithText<'a>> = HashMap::new();
for node in graph.scope_nodes(containment_scope) {
let mut shape = Shape::new(Rc::clone(node.shape_definition()));
shape.set_padding(self.container_padding);
let text = Text::new(node.shape_definition().text(), node.display_text());
let mut shape_with_text = ShapeWithText::new(shape, Some(text));
match node.block() {
Block::Diagram(_) => {
let layout = embedded_layouts.get(&node.id()).ok_or_else(|| {
RenderError::Layout(format!("embedded layout not found for `{node}`"))
})?;
let content_size = layout.calculate_size();
shape_with_text
.set_inner_content_size(content_size)
.map_err(|err| {
RenderError::Layout(format!(
"cannot set content size for diagram block `{node}`: {err}"
))
})?;
}
Block::Scope(_) => {
let content_size =
*positioned_content_sizes.get(&node.id()).ok_or_else(|| {
RenderError::Layout(format!("scope size not found for `{node}`"))
})?;
shape_with_text
.set_inner_content_size(content_size)
.map_err(|err| {
RenderError::Layout(format!(
"cannot set content size for scope block `{node}`: {err}"
))
})?;
}
Block::None => {
}
};
component_shapes.insert(node.id(), shape_with_text);
}
Ok(component_shapes)
}
}
impl ComponentEngine for Engine {
fn calculate<'a>(
&self,
graph: &'a ComponentGraph<'a, '_>,
embedded_layouts: &EmbeddedLayouts<'a>,
) -> Result<ContentStack<Layout<'a>>, RenderError> {
self.calculate_layout(graph, embedded_layouts)
}
}