mod basic;
mod sugiyama;
use std::collections::HashMap;
use log::trace;
use orrery_core::{geometry, identifier::Id, semantic::LayoutEngine};
use super::layer::ContentStack;
use crate::{
error::RenderError,
layout::{
component,
layer::{LayeredLayout, LayoutContent},
positioning::LayoutBounds,
sequence,
},
structure,
};
#[derive(Debug, Clone)]
pub enum LayoutResult<'a> {
Component(ContentStack<component::Layout<'a>>),
Sequence(ContentStack<sequence::Layout<'a>>),
}
impl<'a> LayoutResult<'a> {
pub fn normalize_offset(&self) -> geometry::Point {
let bounds = self.layout_bounds();
geometry::Point::new(-bounds.min_x(), -bounds.min_y())
}
fn calculate_size(&self) -> geometry::Size {
match self {
LayoutResult::Component(layout) => layout.layout_size(),
LayoutResult::Sequence(layout) => layout.layout_size(),
}
}
fn layout_bounds(&self) -> geometry::Bounds {
match self {
LayoutResult::Component(layout) => layout
.iter()
.last()
.map(|content| content.content().layout_bounds())
.unwrap_or_default(),
LayoutResult::Sequence(layout) => layout
.iter()
.last()
.map(|content| content.content().layout_bounds())
.unwrap_or_default(),
}
}
}
pub type EmbeddedLayouts<'a> = HashMap<Id, LayoutResult<'a>>;
pub trait ComponentEngine {
fn calculate<'a>(
&self,
graph: &'a structure::ComponentGraph<'a, '_>,
embedded_layouts: &EmbeddedLayouts<'a>,
) -> Result<ContentStack<component::Layout<'a>>, RenderError>;
}
pub trait SequenceEngine {
fn calculate<'a>(
&self,
graph: &'a structure::SequenceGraph<'a>,
embedded_layouts: &EmbeddedLayouts<'a>,
) -> Result<ContentStack<sequence::Layout<'a>>, RenderError>;
}
#[derive(Default)]
pub struct EngineBuilder {
component_engines: HashMap<LayoutEngine, Box<dyn ComponentEngine>>,
sequence_engines: HashMap<LayoutEngine, Box<dyn SequenceEngine>>,
padding: geometry::Insets,
min_spacing: f32,
horizontal_spacing: f32,
vertical_spacing: f32,
event_padding: f32,
}
impl EngineBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn with_padding(mut self, padding: geometry::Insets) -> Self {
self.padding = padding;
self
}
pub fn with_min_spacing(mut self, spacing: f32) -> Self {
self.min_spacing = spacing;
self
}
pub fn with_horizontal_spacing(mut self, spacing: f32) -> Self {
self.horizontal_spacing = spacing;
self
}
pub fn with_vertical_spacing(mut self, spacing: f32) -> Self {
self.vertical_spacing = spacing;
self
}
pub fn with_event_padding(mut self, padding: f32) -> Self {
self.event_padding = padding;
self
}
pub fn component_engine(&mut self, engine_type: LayoutEngine) -> &dyn ComponentEngine {
let engine = self
.component_engines
.entry(engine_type)
.or_insert_with(|| {
let engine: Box<dyn ComponentEngine> = match engine_type {
LayoutEngine::Basic => {
let mut e = basic::Component::new();
e.set_padding(self.padding);
e.set_min_spacing(self.min_spacing);
Box::new(e)
}
LayoutEngine::Sugiyama => {
let mut e = sugiyama::Component::new();
e.set_horizontal_spacing(self.horizontal_spacing);
e.set_vertical_spacing(self.vertical_spacing);
e.set_container_padding(self.padding);
Box::new(e)
}
};
engine
});
&**engine
}
pub fn sequence_engine(&mut self, engine_type: LayoutEngine) -> &dyn SequenceEngine {
let engine = self.sequence_engines.entry(engine_type).or_insert_with(|| {
let mut engine = basic::Sequence::new();
engine.set_text_padding(self.padding);
engine.set_event_padding(self.event_padding);
engine.set_min_spacing(self.min_spacing);
Box::new(engine)
});
&**engine
}
pub fn build<'a>(
mut self,
collection: &'a structure::DiagramHierarchy<'a, '_>,
) -> Result<LayeredLayout<'a>, RenderError> {
let mut layered_layout = LayeredLayout::new();
let mut layout_info: HashMap<Id, LayoutResult<'a>> = HashMap::new();
let mut container_element_to_layer: HashMap<Id, usize> = HashMap::new();
let mut root_layout: Option<(usize, LayoutResult<'a>)> = None;
let mut embedded_diagrams = Vec::new();
for (container_id, graphed_diagram) in collection.iter_post_order() {
let diagram = graphed_diagram.ast_diagram();
let layout_result = match graphed_diagram.graph_kind() {
structure::GraphKind::ComponentGraph(graph) => {
let engine = self.component_engine(diagram.layout_engine());
let layout = engine.calculate(graph, &layout_info)?;
LayoutResult::Component(layout)
}
structure::GraphKind::SequenceGraph(graph) => {
let engine = self.sequence_engine(diagram.layout_engine());
let layout = engine.calculate(graph, &layout_info)?;
LayoutResult::Sequence(layout)
}
};
let layer_content = match &layout_result {
LayoutResult::Component(layout) => LayoutContent::Component(layout.clone()),
LayoutResult::Sequence(layout) => LayoutContent::Sequence(layout.clone()),
};
let layer_idx = layered_layout.add_layer(layer_content);
if let Some(id) = container_id {
container_element_to_layer.insert(id, layer_idx);
layout_info.insert(id, layout_result);
} else {
root_layout = Some((layer_idx, layout_result));
}
}
for (layer_idx, layout_result) in root_layout
.iter()
.map(|(idx, result)| (*idx, result))
.chain(
layout_info
.iter()
.map(|(&id, result)| (container_element_to_layer[&id], result)),
)
{
match layout_result {
LayoutResult::Component(layout) => {
for positioned_content in layout.iter() {
for component in positioned_content.content().components() {
if let Some(&embedded_idx) =
container_element_to_layer.get(&component.node_id())
{
embedded_diagrams.push((
layer_idx,
component.drawable(),
embedded_idx,
));
}
}
}
}
LayoutResult::Sequence(layout) => {
for positioned_content in layout.iter() {
for participant in positioned_content.content().participants().values() {
if let Some(&embedded_idx) =
container_element_to_layer.get(&participant.component().node_id())
{
embedded_diagrams.push((
layer_idx,
participant.component().drawable(),
embedded_idx,
));
}
}
}
}
}
}
for (container_idx, positioned_shape, embedded_idx) in embedded_diagrams.into_iter() {
layered_layout.adjust_relative_position(
container_idx,
positioned_shape,
embedded_idx,
)?;
}
trace!(layered_layout:?; "Built layered layout");
Ok(layered_layout)
}
}