use disposition_input_ir_model::IrDiagramAndIssues;
use disposition_input_model::{
edge::{EdgeGroup as InputEdgeGroup, EdgeKind},
entity::EntityTypes,
process::{ProcessDiagram, Processes},
tag::TagNames,
theme::{ThemeDefault, ThemeTypesStyles},
thing::{
ThingCopyText, ThingDependencies, ThingHierarchy as InputThingHierarchy, ThingId,
ThingInteractions, ThingLayouts, ThingNames,
},
InputDiagram,
};
use disposition_ir_model::{
edge::{Edge, EdgeGroup, EdgeGroups},
entity::EntityType,
enum_iterator,
layout::{FlexDirection, FlexLayout, LeafLayout, NodeLayout, NodeLayouts},
node::{
NodeCopyText, NodeFaceEdges, NodeHierarchy, NodeId, NodeInbuilt, NodeNames, NodeOrdering,
NodeShapes,
},
process::{ProcessStepEdges, ProcessStepEntities, ProcessStepRank, ProcessStepRanks},
IrDiagram,
};
use disposition_model_common::{edge::EdgeGroupId, theme::Css, Id, Map, RankDir, Set};
use crate::{
edge_face_assigner::EdgeFaceAssigner, node_ranks_calculator::NodeRanksCalculator,
process_step_graph_calculator::ProcessStepGraphCalculator,
};
use self::{
css_theme_vars::CssThemeVars, node_nesting_infos_builder::NodeNestingInfosBuilder,
tailwind_classes_builder::TailwindClassesBuilder, theme_attr_resolver::ThemeAttrResolver,
};
mod css_theme_vars;
mod node_nesting_infos_builder;
mod tailwind_class_state;
mod tailwind_classes_builder;
pub(crate) mod tailwind_color_shade;
pub(crate) mod tailwind_colors;
mod theme_attr_resolver;
#[derive(Clone, Copy, Debug)]
pub struct InputToIrDiagramMapper;
#[derive(Clone, Copy)]
struct ThemeResolveCtx<'a, 'id> {
entity_types: &'a EntityTypes<'id>,
theme_default: &'a ThemeDefault<'id>,
theme_types_styles: &'a ThemeTypesStyles<'id>,
}
impl InputToIrDiagramMapper {
pub fn map<'f, 'id>(input_diagram: &'f InputDiagram<'id>) -> IrDiagramAndIssues<'id>
where
'id: 'f,
{
let issues = Vec::new();
let InputDiagram {
things,
thing_names,
thing_copy_text,
thing_layouts,
thing_dependencies,
thing_interactions,
thing_descs,
processes,
tags,
tag_things,
edge_descs,
edge_labels,
entity_tooltips,
entity_types,
theme_default,
theme_types_styles,
theme_thing_dependencies_styles: _,
theme_tag_things_focus,
render_options,
css,
} = input_diagram;
let nodes = Self::build_node_names(things, thing_names, tags, processes);
let node_copy_text = Self::build_node_copy_text(thing_copy_text);
let node_hierarchy = Self::build_node_hierarchy(tags, processes, things);
let node_ordering = Self::build_node_ordering(things, tags, processes);
let edge_groups = Self::build_edge_groups(thing_dependencies, thing_interactions);
let thing_descs = thing_descs.clone();
let edge_labels = edge_labels.clone();
let edge_descs = edge_descs.clone();
let entity_tooltips = entity_tooltips.clone();
let ir_entity_types = Self::build_entity_types(
things,
tags,
processes,
entity_types,
thing_dependencies,
thing_interactions,
);
let flex_direction_default = match render_options.rank_dir {
RankDir::LeftToRight => FlexDirection::Column,
RankDir::RightToLeft => FlexDirection::ColumnReverse,
RankDir::TopToBottom => FlexDirection::Row,
RankDir::BottomToTop => FlexDirection::RowReverse,
};
let theme_resolve_ctx = ThemeResolveCtx {
entity_types: &ir_entity_types,
theme_default,
theme_types_styles,
};
let node_layouts = Self::build_node_layouts(
&node_hierarchy,
flex_direction_default,
theme_resolve_ctx,
tags,
processes,
thing_layouts,
);
let node_shapes =
Self::build_node_shapes(&nodes, &ir_entity_types, theme_default, theme_types_styles);
let process_render_expanded = render_options
.process_render_collapse
.process_render_expanded(processes.len());
let tailwind_classes_build_result = TailwindClassesBuilder::build(
&nodes,
&edge_groups,
&ir_entity_types,
theme_default,
theme_types_styles,
theme_tag_things_focus,
tags,
tag_things,
processes,
process_render_expanded,
);
let mut tailwind_classes = tailwind_classes_build_result.tailwind_classes;
let mut css_theme_vars = tailwind_classes_build_result.css_theme_vars;
let process_step_entities = Self::build_process_step_entities(processes);
let process_step_edges = Self::build_process_step_edges(processes);
let process_step_ranks = Self::build_process_step_ranks(processes, &process_step_edges);
let process_step_graphs = ProcessStepGraphCalculator::calculate(
processes,
&process_step_ranks,
&process_step_edges,
);
if !process_step_graphs.is_empty() {
let connector_classes = TailwindClassesBuilder::build_process_step_connector_classes(
theme_default,
theme_types_styles,
&mut css_theme_vars,
);
process_step_graphs
.values()
.flat_map(|process_step_graph| &process_step_graph.edges)
.for_each(|process_step_graph_edge| {
tailwind_classes.insert(
process_step_graph_edge.edge_id().into_inner(),
connector_classes.clone(),
);
});
}
let node_nesting_infos = NodeNestingInfosBuilder::build(&node_hierarchy);
let node_ranks_nested =
NodeRanksCalculator::calculate(&edge_groups, &ir_entity_types, &node_nesting_infos);
let edge_face_assignments = EdgeFaceAssigner::compute(
&edge_groups,
&ir_entity_types,
&node_nesting_infos,
&node_ranks_nested,
render_options.rank_dir,
);
let node_face_edges = NodeFaceEdges::from_assignments(&edge_face_assignments, &edge_groups);
let diagram = IrDiagram {
nodes,
node_copy_text,
node_hierarchy,
node_ordering,
edge_groups,
thing_descs,
edge_descs,
edge_labels,
entity_tooltips,
entity_types: ir_entity_types,
tailwind_classes,
node_layouts,
node_ranks_nested,
node_nesting_infos,
edge_face_assignments,
node_face_edges,
node_shapes,
process_step_entities,
process_step_edges,
process_step_ranks,
process_step_graphs,
render_options: *render_options,
css: Self::css_with_theme_vars(css, &css_theme_vars),
};
IrDiagramAndIssues { diagram, issues }
}
fn css_with_theme_vars(css: &Css, css_theme_vars: &CssThemeVars) -> Css {
if css_theme_vars.is_empty() {
return css.clone();
}
let vars_css = css_theme_vars.to_css();
if css.is_empty() {
Css::from_string(vars_css)
} else {
Css::from_string(format!("{vars_css}\n{}", css.as_str()))
}
}
fn id_from_string(s: String) -> Id<'static> {
Id::try_from(s).expect("valid ID string")
}
fn build_node_names<'id>(
things: &InputThingHierarchy<'id>,
thing_names: &ThingNames<'id>,
tags: &TagNames<'id>,
processes: &Processes<'id>,
) -> NodeNames<'id> {
let thing_nodes = things.thing_ids_recursive().into_iter().map(|thing_id| {
let node_id = NodeId::from(thing_id.as_ref().clone());
let name = thing_names
.get(thing_id.as_ref())
.cloned()
.unwrap_or_else(|| thing_id.as_str().to_string());
(node_id, name)
});
let tag_nodes = tags.iter().map(|(tag_id, name)| {
let node_id = NodeId::from(tag_id.as_ref().clone());
(node_id, name.clone())
});
let process_and_step_nodes = processes.iter().flat_map(|(process_id, process_diagram)| {
let process_node_id = NodeId::from(process_id.as_ref().clone());
let process_name = process_diagram
.name
.clone()
.unwrap_or_else(|| process_id.as_str().to_string());
let step_nodes = process_diagram.steps.iter().map(|(step_id, step_name)| {
let step_node_id = NodeId::from(step_id.as_ref().clone());
(step_node_id, step_name.clone())
});
std::iter::once((process_node_id, process_name)).chain(step_nodes)
});
thing_nodes
.chain(tag_nodes)
.chain(process_and_step_nodes)
.collect()
}
fn build_node_copy_text<'id>(thing_copy_text: &ThingCopyText<'id>) -> NodeCopyText<'id> {
thing_copy_text
.iter()
.map(|(thing_id, text)| {
let node_id = NodeId::from(thing_id.as_ref().clone());
(node_id, text.clone())
})
.collect()
}
fn build_node_hierarchy<'id>(
tags: &TagNames<'id>,
processes: &Processes<'id>,
thing_hierarchy: &InputThingHierarchy<'id>,
) -> NodeHierarchy<'id> {
let tag_entries = tags.keys().map(|tag_id| {
let node_id = NodeId::from(tag_id.as_ref().clone());
(node_id, NodeHierarchy::new())
});
let process_entries = processes.iter().map(|(process_id, process_diagram)| {
let process_node_id = NodeId::from(process_id.as_ref().clone());
let process_children: NodeHierarchy = process_diagram
.steps
.keys()
.map(|step_id| {
let step_node_id = NodeId::from(step_id.as_ref().clone());
(step_node_id, NodeHierarchy::new())
})
.collect();
(process_node_id, process_children)
});
let thing_hierarchy = Self::convert_thing_hierarchy_to_node_hierarchy(thing_hierarchy);
tag_entries
.chain(process_entries)
.chain(thing_hierarchy)
.collect()
}
fn convert_thing_hierarchy_to_node_hierarchy<'id>(
thing_hierarchy: &InputThingHierarchy<'id>,
) -> NodeHierarchy<'id> {
thing_hierarchy
.iter()
.map(|(thing_id, children)| {
let node_id = NodeId::from(thing_id.as_ref().clone());
let child_hierarchy = Self::convert_thing_hierarchy_to_node_hierarchy(children);
(node_id, child_hierarchy)
})
.collect()
}
fn build_node_ordering<'id>(
things: &InputThingHierarchy<'id>,
tags: &TagNames<'id>,
processes: &Processes<'id>,
) -> NodeOrdering<'id> {
let mut tab_index: u32 = 1;
let mut tab_indices = Map::<&Id<'id>, u32>::new();
Self::collect_thing_tab_indices_recursive(things, &mut tab_index, &mut tab_indices);
let mut process_step_count = 0;
processes.iter().for_each(|(process_id, process_diagram)| {
tab_indices.insert(process_id.as_ref(), tab_index);
tab_index += 1;
process_diagram.steps.keys().for_each(|step_id| {
tab_indices.insert(step_id.as_ref(), tab_index);
tab_index += 1;
});
process_step_count += process_diagram.steps.len();
});
tags.keys().for_each(|tag_id| {
tab_indices.insert(tag_id.as_ref(), tab_index);
tab_index += 1;
});
let mut node_ordering = NodeOrdering::with_capacity(
tags.len() + processes.len() + process_step_count + things.total_descendants(),
);
tags.keys().for_each(|tag_id| {
let tab_idx = tab_indices.get(tag_id.as_ref()).copied().unwrap_or(0);
let tag_node_id = NodeId::from(tag_id.as_ref().clone());
node_ordering.insert(tag_node_id, tab_idx);
});
processes.keys().for_each(|process_id| {
let process_node_id = NodeId::from(process_id.as_ref().clone());
let tab_idx = tab_indices
.get(process_node_id.as_ref())
.copied()
.unwrap_or(0);
node_ordering.insert(process_node_id, tab_idx);
});
processes.values().for_each(|process_diagram| {
process_diagram.steps.keys().for_each(|step_id| {
let process_step_node_id = NodeId::from(step_id.as_ref().clone());
let tab_idx = tab_indices
.get(process_step_node_id.as_ref())
.copied()
.unwrap_or(0);
node_ordering.insert(process_step_node_id, tab_idx);
});
});
Self::add_things_to_ordering_recursive(things, &tab_indices, &mut node_ordering);
node_ordering
}
fn collect_thing_tab_indices_recursive<'f, 'id>(
thing_hierarchy: &'f InputThingHierarchy<'id>,
tab_index: &mut u32,
tab_indices: &mut Map<&'f Id<'id>, u32>,
) {
thing_hierarchy.iter().for_each(|(thing_id, children)| {
tab_indices.insert(thing_id.as_ref(), *tab_index);
*tab_index += 1;
Self::collect_thing_tab_indices_recursive(children, tab_index, tab_indices);
});
}
fn add_things_to_ordering_recursive<'f, 'id>(
thing_hierarchy: &'f InputThingHierarchy<'id>,
thing_tab_indices: &Map<&'f Id<'id>, u32>,
node_ordering: &mut NodeOrdering<'id>,
) {
thing_hierarchy.iter().for_each(|(thing_id, children)| {
let thing_node_id = NodeId::from(thing_id.as_ref().clone());
let tab_idx = thing_tab_indices
.get(thing_node_id.as_ref())
.copied()
.unwrap_or(0);
node_ordering.insert(thing_node_id, tab_idx);
Self::add_things_to_ordering_recursive(children, thing_tab_indices, node_ordering);
});
}
fn build_edge_groups<'id>(
thing_dependencies: &ThingDependencies<'id>,
thing_interactions: &ThingInteractions<'id>,
) -> EdgeGroups<'id> {
let dependency_entries =
thing_dependencies
.iter()
.map(|(edge_group_id, input_edge_group)| {
(
edge_group_id.clone(),
Self::input_edge_group_to_edges(input_edge_group),
)
});
let interaction_entries = thing_interactions
.iter()
.filter(|(edge_group_id, _)| !thing_dependencies.contains_key(edge_group_id))
.map(|(edge_group_id, input_edge_group)| {
(
edge_group_id.clone(),
Self::input_edge_group_to_edges(input_edge_group),
)
});
dependency_entries.chain(interaction_entries).collect()
}
fn input_edge_group_to_edges<'id>(input_edge_group: &InputEdgeGroup<'id>) -> EdgeGroup<'id> {
let things = &input_edge_group.things;
let edges: Vec<Edge> = Self::edge_kind_to_edges(input_edge_group.kind, things);
EdgeGroup::from(edges)
}
fn edge_kind_to_edges<'id>(edge_kind: EdgeKind, things: &[ThingId<'id>]) -> Vec<Edge<'id>> {
match edge_kind {
EdgeKind::Cyclic => {
things
.iter()
.enumerate()
.map(|(index, thing)| {
let from_id = NodeId::from(thing.as_ref().clone());
let to_idx = (index + 1) % things.len();
let to_id = NodeId::from(things[to_idx].as_ref().clone());
Edge::new(from_id, to_id)
})
.collect()
}
EdgeKind::Sequence => {
things
.windows(2)
.map(|pair| {
let from_id = NodeId::from(pair[0].as_ref().clone());
let to_id = NodeId::from(pair[1].as_ref().clone());
Edge::new(from_id, to_id)
})
.collect()
}
EdgeKind::Symmetric => {
if things.len() == 1 {
let node_id = NodeId::from(things[0].as_ref().clone());
vec![
Edge::new(node_id.clone(), node_id.clone()),
Edge::new(node_id.clone(), node_id),
]
} else {
let forward: Vec<Edge> = things
.windows(2)
.map(|pair| {
let from_id = NodeId::from(pair[0].as_ref().clone());
let to_id = NodeId::from(pair[1].as_ref().clone());
Edge::new(from_id, to_id)
})
.collect();
let reverse: Vec<Edge> = things
.windows(2)
.rev()
.map(|pair| {
let from_id = NodeId::from(pair[1].as_ref().clone());
let to_id = NodeId::from(pair[0].as_ref().clone());
Edge::new(from_id, to_id)
})
.collect();
forward.into_iter().chain(reverse).collect()
}
}
}
}
fn build_entity_types<'id>(
things: &InputThingHierarchy<'id>,
tags: &TagNames<'id>,
processes: &Processes<'id>,
input_entity_types: &EntityTypes<'id>,
thing_dependencies: &ThingDependencies<'id>,
thing_interactions: &ThingInteractions<'id>,
) -> EntityTypes<'id> {
let build_types = |id: &Id<'id>, default_type: EntityType| {
let mut types = Set::new();
types.insert(default_type);
if let Some(custom_types) = input_entity_types.get(id) {
types.extend(custom_types.iter().cloned());
}
types
};
let thing_entries = things.thing_ids_recursive().into_iter().map(|thing_id| {
let id: Id = thing_id.as_ref().clone();
let types = build_types(&id, EntityType::ThingDefault);
(id, types)
});
let tag_entries = tags.keys().map(|tag_id| {
let id: Id = tag_id.as_ref().clone();
let types = build_types(&id, EntityType::TagDefault);
(id, types)
});
let process_entries = processes.iter().flat_map(|(process_id, process_diagram)| {
let process_id_inner: Id = process_id.as_ref().clone();
let process_types = build_types(&process_id_inner, EntityType::ProcessDefault);
let step_entries = process_diagram.steps.keys().map(|step_id| {
let id: Id = step_id.as_ref().clone();
let types = build_types(&id, EntityType::ProcessStepDefault);
(id, types)
});
std::iter::once((process_id_inner, process_types)).chain(step_entries)
});
let node_inbuilt_types = enum_iterator::all::<NodeInbuilt>().map(|node_inbuilt| {
let mut entity_types = Set::with_capacity(1);
entity_types.insert(node_inbuilt.entity_type());
(node_inbuilt.id(), entity_types)
});
let mut entity_types: Map<Id<'id>, Set<EntityType>> = node_inbuilt_types
.chain(thing_entries)
.chain(tag_entries)
.chain(process_entries)
.collect();
Self::build_entity_types_dependencies(
&mut entity_types,
thing_dependencies,
input_entity_types,
);
Self::build_entity_types_interactions(
&mut entity_types,
thing_interactions,
input_entity_types,
);
EntityTypes::from(entity_types)
}
fn build_entity_types_dependencies<'id>(
entity_types: &mut Map<Id<'id>, Set<EntityType>>,
thing_deps: &ThingDependencies<'id>,
input_entity_types: &EntityTypes<'id>,
) {
let edge_group_entries = thing_deps
.iter()
.flat_map(|(edge_group_id, input_edge_group)| {
let edge_kind = input_edge_group.kind;
let things = &input_edge_group.things;
let edge_group_entity_types = Self::build_entity_types_for_edge_groups(
input_entity_types,
edge_group_id,
edge_kind,
Self::edge_group_default_type_dependency,
);
let edge_entity_types = Self::build_entity_types_for_edges(
input_entity_types,
edge_group_id,
edge_kind,
things,
Self::edge_default_type_dependency,
);
std::iter::once(edge_group_entity_types).chain(edge_entity_types)
});
entity_types.extend(edge_group_entries);
}
fn build_entity_types_interactions<'id>(
entity_types: &mut Map<Id<'id>, Set<EntityType>>,
thing_interactions: &ThingInteractions<'id>,
input_entity_types: &EntityTypes<'id>,
) {
let edge_group_entries =
thing_interactions
.iter()
.flat_map(|(edge_group_id, input_edge_group)| {
let edge_kind = input_edge_group.kind;
let things = &input_edge_group.things;
let edge_group_entity_types = Self::build_entity_types_for_edge_groups(
input_entity_types,
edge_group_id,
edge_kind,
Self::edge_group_default_type_interaction,
);
let edge_entity_types = Self::build_entity_types_for_edges(
input_entity_types,
edge_group_id,
edge_kind,
things,
Self::edge_default_type_interaction,
);
std::iter::once(edge_group_entity_types).chain(edge_entity_types)
});
entity_types.extend(edge_group_entries);
}
fn build_entity_types_for_edge_groups<'id>(
input_entity_types: &EntityTypes<'id>,
edge_group_id: &EdgeGroupId<'id>,
edge_kind: EdgeKind,
edge_group_default_type_fn: fn(EdgeKind) -> EntityType,
) -> (Id<'id>, Set<EntityType>) {
let edge_group_id: Id<'id> = edge_group_id.as_ref().clone();
let edge_group_default_type = edge_group_default_type_fn(edge_kind);
let mut types = Set::new();
types.insert(edge_group_default_type);
if let Some(custom_types) = input_entity_types.get(&edge_group_id) {
types.extend(custom_types.iter().cloned());
}
(edge_group_id, types)
}
fn edge_group_default_type_dependency(edge_kind: EdgeKind) -> EntityType {
match edge_kind {
EdgeKind::Cyclic => EntityType::DependencyEdgeCyclicDefault,
EdgeKind::Sequence => EntityType::DependencyEdgeSequenceDefault,
EdgeKind::Symmetric => EntityType::DependencyEdgeSymmetricDefault,
}
}
fn edge_default_type_dependency(
edge_kind: EdgeKind,
forward_count: usize,
i: usize,
) -> EntityType {
match edge_kind {
EdgeKind::Cyclic => EntityType::DependencyEdgeCyclicForwardDefault,
EdgeKind::Sequence => EntityType::DependencyEdgeSequenceForwardDefault,
EdgeKind::Symmetric => {
if i < forward_count {
EntityType::DependencyEdgeSymmetricForwardDefault
} else {
EntityType::DependencyEdgeSymmetricReverseDefault
}
}
}
}
fn edge_group_default_type_interaction(edge_kind: EdgeKind) -> EntityType {
match edge_kind {
EdgeKind::Cyclic => EntityType::InteractionEdgeCyclicDefault,
EdgeKind::Sequence => EntityType::InteractionEdgeSequenceDefault,
EdgeKind::Symmetric => EntityType::InteractionEdgeSymmetricDefault,
}
}
fn edge_default_type_interaction(
edge_kind: EdgeKind,
forward_count: usize,
i: usize,
) -> EntityType {
match edge_kind {
EdgeKind::Cyclic => EntityType::InteractionEdgeCyclicForwardDefault,
EdgeKind::Sequence => EntityType::InteractionEdgeSequenceForwardDefault,
EdgeKind::Symmetric => {
if i < forward_count {
EntityType::InteractionEdgeSymmetricForwardDefault
} else {
EntityType::InteractionEdgeSymmetricReverseDefault
}
}
}
}
fn build_entity_types_for_edges<'id>(
input_entity_types: &EntityTypes<'id>,
edge_group_id: &EdgeGroupId<'id>,
edge_kind: EdgeKind,
things: &[ThingId<'id>],
edge_default_type_fn: fn(EdgeKind, usize, usize) -> EntityType,
) -> impl Iterator<Item = (Id<'id>, Set<EntityType>)> {
let (edge_count, forward_count) = match edge_kind {
EdgeKind::Cyclic => (things.len(), things.len()),
EdgeKind::Sequence => {
let count = things.len().saturating_sub(1);
(count, count)
}
EdgeKind::Symmetric => {
let forward = things.len().max(1).saturating_sub(1).max(1);
let total = if things.len() <= 1 { 2 } else { forward * 2 };
(total, forward)
}
};
(0..edge_count).map(move |i| {
let edge_id_str = format!("{edge_group_id}__{i}");
let edge_id = Self::id_from_string(edge_id_str);
let edge_default_type = edge_default_type_fn(edge_kind, forward_count, i);
let mut types = Set::new();
types.insert(edge_default_type);
if let Some(custom_types) = input_entity_types.get(&edge_id) {
types.extend(custom_types.iter().cloned());
}
(edge_id, types)
})
}
#[allow(clippy::too_many_arguments)]
fn build_node_layouts<'id>(
node_hierarchy: &NodeHierarchy<'id>,
flex_direction_default: FlexDirection,
theme_ctx: ThemeResolveCtx<'_, 'id>,
tags: &TagNames<'id>,
processes: &Processes<'id>,
thing_layouts: &ThingLayouts<'id>,
) -> NodeLayouts<'id> {
let mut node_layouts = NodeLayouts::new();
let is_tag = |node_id: &NodeId<'id>| tags.contains_key(node_id);
let is_process = |node_id: &NodeId<'id>| processes.contains_key(node_id);
Self::inbuilt_container_layout_insert(
&mut node_layouts,
NodeInbuilt::Root,
FlexDirection::ColumnReverse,
thing_layouts,
theme_ctx,
);
Self::inbuilt_container_layout_insert(
&mut node_layouts,
NodeInbuilt::ThingsAndProcessesContainer,
FlexDirection::RowReverse,
thing_layouts,
theme_ctx,
);
Self::inbuilt_container_layout_insert(
&mut node_layouts,
NodeInbuilt::ProcessesContainer,
FlexDirection::Column,
thing_layouts,
theme_ctx,
);
node_layouts.extend(Self::process_node_layouts_build(processes, theme_ctx));
Self::inbuilt_container_layout_insert(
&mut node_layouts,
NodeInbuilt::TagsContainer,
FlexDirection::Row,
thing_layouts,
theme_ctx,
);
node_layouts.extend(Self::tag_node_layouts_build(tags, theme_ctx));
Self::inbuilt_container_layout_insert(
&mut node_layouts,
NodeInbuilt::ThingsContainer,
flex_direction_default,
thing_layouts,
theme_ctx,
);
Self::build_thing_layouts(
node_hierarchy,
flex_direction_default,
theme_ctx,
&mut node_layouts,
&is_tag,
&is_process,
thing_layouts,
);
node_layouts
}
fn inbuilt_container_layout_insert<'id>(
node_layouts: &mut NodeLayouts<'id>,
inbuilt: NodeInbuilt,
direction_default: FlexDirection,
thing_layouts: &ThingLayouts<'id>,
theme_ctx: ThemeResolveCtx<'_, 'id>,
) {
let container_id = inbuilt.id();
let direction = thing_layouts
.get(&container_id)
.copied()
.unwrap_or(direction_default);
let layout = Self::build_container_layout(&container_id, direction, false, theme_ctx);
node_layouts.insert(NodeId::from(container_id), layout);
}
fn process_node_layouts_build<'id>(
processes: &Processes<'id>,
theme_ctx: ThemeResolveCtx<'_, 'id>,
) -> Vec<(NodeId<'id>, NodeLayout)> {
processes
.iter()
.flat_map(|(process_id, process_diagram)| {
let process_node_id = NodeId::from(process_id.as_ref().clone());
let process_layout = if !process_diagram.steps.is_empty() {
Self::build_node_flex_layout(
process_node_id.clone().into_inner(),
FlexDirection::Column,
false,
theme_ctx,
)
} else {
Self::build_node_leaf_layout(process_node_id.clone().into_inner(), theme_ctx)
};
let step_layouts = process_diagram.steps.keys().map(move |step_id| {
let step_node_id = NodeId::from(step_id.as_ref().clone());
let step_node_layout =
Self::build_node_leaf_layout(step_id.clone().into_inner(), theme_ctx);
(step_node_id, step_node_layout)
});
std::iter::once((process_node_id, process_layout)).chain(step_layouts)
})
.collect()
}
fn tag_node_layouts_build<'id>(
tags: &TagNames<'id>,
theme_ctx: ThemeResolveCtx<'_, 'id>,
) -> Vec<(NodeId<'id>, NodeLayout)> {
tags.keys()
.map(|tag_id| {
let tag_node_id = NodeId::from(tag_id.as_ref().clone());
let tag_node_layout =
Self::build_node_leaf_layout(tag_id.clone().into_inner(), theme_ctx);
(tag_node_id, tag_node_layout)
})
.collect()
}
fn build_container_layout<'id>(
container_id: &Id<'id>,
direction: FlexDirection,
wrap: bool,
theme_ctx: ThemeResolveCtx<'_, 'id>,
) -> NodeLayout {
let ThemeResolveCtx {
entity_types,
theme_default,
theme_types_styles,
} = theme_ctx;
let (padding_top, padding_right, padding_bottom, padding_left) =
ThemeAttrResolver::resolve_padding(
Some(container_id),
entity_types,
theme_default,
theme_types_styles,
);
let (margin_top, margin_right, margin_bottom, margin_left) =
ThemeAttrResolver::resolve_margin(
Some(container_id),
entity_types,
theme_default,
theme_types_styles,
);
let gap = ThemeAttrResolver::resolve_gap(
Some(container_id),
entity_types,
theme_default,
theme_types_styles,
);
NodeLayout::Flex(FlexLayout {
direction,
wrap,
padding_top,
padding_right,
padding_bottom,
padding_left,
margin_top,
margin_right,
margin_bottom,
margin_left,
gap,
})
}
fn build_node_flex_layout<'id>(
id: Id<'id>,
direction: FlexDirection,
wrap: bool,
theme_ctx: ThemeResolveCtx<'_, 'id>,
) -> NodeLayout {
let ThemeResolveCtx {
entity_types,
theme_default,
theme_types_styles,
} = theme_ctx;
let (padding_top, padding_right, padding_bottom, padding_left) =
ThemeAttrResolver::resolve_padding(
Some(&id),
entity_types,
theme_default,
theme_types_styles,
);
let (margin_top, margin_right, margin_bottom, margin_left) =
ThemeAttrResolver::resolve_margin(
Some(&id),
entity_types,
theme_default,
theme_types_styles,
);
let gap = ThemeAttrResolver::resolve_gap(
Some(&id),
entity_types,
theme_default,
theme_types_styles,
);
NodeLayout::Flex(FlexLayout {
direction,
wrap,
padding_top,
padding_right,
padding_bottom,
padding_left,
margin_top,
margin_right,
margin_bottom,
margin_left,
gap,
})
}
fn build_node_leaf_layout<'id>(id: Id<'id>, theme_ctx: ThemeResolveCtx<'_, 'id>) -> NodeLayout {
let ThemeResolveCtx {
entity_types,
theme_default,
theme_types_styles,
} = theme_ctx;
let (padding_top, padding_right, padding_bottom, padding_left) =
ThemeAttrResolver::resolve_padding(
Some(&id),
entity_types,
theme_default,
theme_types_styles,
);
let (margin_top, margin_right, margin_bottom, margin_left) =
ThemeAttrResolver::resolve_margin(
Some(&id),
entity_types,
theme_default,
theme_types_styles,
);
NodeLayout::Leaf(LeafLayout {
padding_top,
padding_right,
padding_bottom,
padding_left,
margin_top,
margin_right,
margin_bottom,
margin_left,
})
}
fn build_thing_layouts<'id, F, G>(
hierarchy: &NodeHierarchy<'id>,
flex_direction_default: FlexDirection,
theme_ctx: ThemeResolveCtx<'_, 'id>,
node_layouts: &mut NodeLayouts<'id>,
is_tag: &F,
is_process: &G,
thing_layouts: &ThingLayouts<'id>,
) where
F: Fn(&NodeId<'id>) -> bool,
G: Fn(&NodeId<'id>) -> bool,
{
let thing_layout_entries: Vec<_> = hierarchy
.iter()
.filter(|(node_id, _)| !is_tag(node_id) && !is_process(node_id))
.flat_map(|(node_id, children)| {
let layout = if children.is_empty() {
Self::build_node_leaf_layout(node_id.clone().into_inner(), theme_ctx)
} else {
let thing_id = node_id.as_ref().clone();
let direction = thing_layouts
.get(&thing_id)
.copied()
.unwrap_or(flex_direction_default);
Self::build_node_flex_layout(
node_id.clone().into_inner(),
direction,
false,
theme_ctx,
)
};
let children_to_process = if !children.is_empty() {
Some(children.clone())
} else {
None
};
std::iter::once((node_id.clone(), layout, children_to_process))
})
.collect();
thing_layout_entries
.into_iter()
.for_each(|(node_id, layout, children_opt)| {
node_layouts.insert(node_id, layout);
if let Some(children) = children_opt {
Self::build_thing_layouts(
&children,
flex_direction_default,
theme_ctx,
node_layouts,
is_tag,
is_process,
thing_layouts,
);
}
});
}
fn build_node_shapes<'id>(
nodes: &NodeNames<'id>,
entity_types: &EntityTypes<'id>,
theme_default: &ThemeDefault<'id>,
theme_types_styles: &ThemeTypesStyles<'id>,
) -> NodeShapes<'id> {
nodes
.iter()
.map(|(node_id, _name)| {
let id: Id<'id> = node_id.as_ref().clone();
let shape = ThemeAttrResolver::resolve_node_shape(
&id,
entity_types,
theme_default,
theme_types_styles,
);
(node_id.clone(), shape)
})
.collect()
}
fn build_process_step_entities<'id>(processes: &Processes<'id>) -> ProcessStepEntities<'id> {
processes
.iter()
.flat_map(|(_process_id, process_diagram)| {
process_diagram
.step_thing_interactions
.iter()
.map(|(step_id, edge_group_ids)| {
let node_id = NodeId::from(step_id.as_ref().clone());
let entity_ids: Vec<Id<'id>> = edge_group_ids
.iter()
.map(|edge_group_id| edge_group_id.as_ref().clone())
.collect();
(node_id, entity_ids)
})
})
.collect()
}
fn build_process_step_edges<'id>(processes: &Processes<'id>) -> ProcessStepEdges<'id> {
processes
.values()
.flat_map(Self::process_step_edges_for_process)
.collect()
}
fn process_step_edges_for_process<'id>(
process_diagram: &ProcessDiagram<'id>,
) -> Vec<Edge<'id>> {
if process_diagram.process_step_dependencies.is_empty() {
let step_node_ids: Vec<NodeId<'id>> = process_diagram
.steps
.keys()
.map(|step_id| NodeId::from(step_id.as_ref().clone()))
.collect();
step_node_ids
.windows(2)
.map(|pair| Edge::new(pair[0].clone(), pair[1].clone()))
.collect()
} else {
process_diagram
.process_step_dependencies
.iter()
.flat_map(|(step_id, dependency_ids)| {
let to_id = NodeId::from(step_id.as_ref().clone());
dependency_ids.iter().map(move |dependency_id| {
let from_id = NodeId::from(dependency_id.as_ref().clone());
Edge::new(from_id, to_id.clone())
})
})
.collect()
}
}
fn build_process_step_ranks<'id>(
processes: &Processes<'id>,
process_step_edges: &ProcessStepEdges<'id>,
) -> ProcessStepRanks<'id> {
let step_node_ids: Vec<NodeId<'id>> = processes
.values()
.flat_map(|process_diagram| process_diagram.steps.keys())
.map(|step_id| NodeId::from(step_id.as_ref().clone()))
.collect();
let node_count = step_node_ids.len();
let node_to_index: Map<NodeId<'id>, usize> = step_node_ids
.iter()
.enumerate()
.map(|(index, node_id)| (node_id.clone(), index))
.collect();
let (adjacency, mut in_degree) = process_step_edges.iter().fold(
(vec![Vec::new(); node_count], vec![0usize; node_count]),
|(mut adjacency, mut in_degree), edge| {
if let (Some(&from_idx), Some(&to_idx)) =
(node_to_index.get(&edge.from), node_to_index.get(&edge.to))
{
adjacency[from_idx].push(to_idx);
in_degree[to_idx] += 1;
}
(adjacency, in_degree)
},
);
let mut ranks: Vec<u32> = vec![0; node_count];
let mut queue: std::collections::VecDeque<usize> = in_degree
.iter()
.enumerate()
.filter(|(_index, in_degree_item)| **in_degree_item == 0)
.map(|(index, _in_degree_item)| index)
.collect();
while let Some(from_idx) = queue.pop_front() {
for &to_idx in &adjacency[from_idx] {
let candidate_rank = ranks[from_idx] + 1;
if candidate_rank > ranks[to_idx] {
ranks[to_idx] = candidate_rank;
}
in_degree[to_idx] -= 1;
if in_degree[to_idx] == 0 {
queue.push_back(to_idx);
}
}
}
step_node_ids
.into_iter()
.enumerate()
.map(|(index, node_id)| (node_id, ProcessStepRank::new(ranks[index])))
.collect()
}
}