use std::{collections::HashMap, rc::Rc};
use log::{debug, error};
use orrery_core::{
draw,
geometry::{Bounds, Point},
identifier::Id,
semantic,
};
use crate::{
error::RenderError,
layout::{layer, positioning::LayoutBounds},
structure,
};
#[derive(Debug, Clone)]
pub struct Component<'a> {
node_id: Id, drawable: Rc<draw::PositionedDrawable<draw::ShapeWithText<'a>>>, }
impl<'a> Component<'a> {
pub fn new(
node: &semantic::Node,
shape_with_text: draw::ShapeWithText<'a>,
position: Point,
) -> Component<'a> {
let drawable =
Rc::new(draw::PositionedDrawable::new(shape_with_text).with_position(position));
Component {
node_id: node.id(),
drawable,
}
}
pub fn drawable(&self) -> &draw::PositionedDrawable<draw::ShapeWithText<'_>> {
&self.drawable
}
pub fn position(&self) -> Point {
self.drawable.position()
}
pub fn bounds(&self) -> Bounds {
self.drawable.bounds()
}
pub fn node_id(&self) -> Id {
self.node_id
}
}
#[derive(Debug, Clone)]
pub struct LayoutRelation<'a> {
source_index: usize,
target_index: usize,
arrow_with_text: draw::ArrowWithText<'a>,
}
impl<'a> LayoutRelation<'a> {
pub fn from_ast(
relation: &'a semantic::Relation,
source_index: usize,
target_index: usize,
) -> Self {
let arrow_def = Rc::clone(relation.arrow_definition());
let arrow = draw::Arrow::new(arrow_def, relation.arrow_direction());
let arrow_with_text = draw::ArrowWithText::new(arrow, relation.text());
Self {
source_index,
target_index,
arrow_with_text,
}
}
pub fn arrow_with_text(&self) -> &draw::ArrowWithText<'_> {
&self.arrow_with_text
}
}
#[derive(Debug, Clone)]
pub struct Layout<'a> {
components: Vec<Component<'a>>,
relations: Vec<LayoutRelation<'a>>,
bounds: Bounds,
}
impl<'a> Layout<'a> {
pub fn new(components: Vec<Component<'a>>, relations: Vec<LayoutRelation<'a>>) -> Self {
let bounds = if components.is_empty() {
Bounds::default()
} else {
components
.iter()
.skip(1)
.fold(components[0].bounds(), |acc, comp| {
acc.merge(&comp.bounds())
})
};
Self {
components,
relations,
bounds,
}
}
pub fn components(&self) -> &[Component<'a>] {
&self.components
}
pub fn relations(&self) -> &[LayoutRelation<'a>] {
&self.relations
}
pub fn source(&self, lr: &LayoutRelation) -> &Component<'_> {
&self.components[lr.source_index]
}
pub fn target(&self, lr: &LayoutRelation) -> &Component<'_> {
&self.components[lr.target_index]
}
}
impl<'a> LayoutBounds for Layout<'a> {
fn layout_bounds(&self) -> Bounds {
self.bounds
}
}
pub fn adjust_positioned_contents_offset<'a>(
content_stack: &mut layer::ContentStack<Layout>,
graph: &'a structure::ComponentGraph<'a, '_>,
) -> Result<(), RenderError> {
let container_indices: HashMap<_, _> = graph
.containment_scopes()
.enumerate()
.filter_map(|(idx, scope)| scope.container().map(|container| (container, idx)))
.collect();
for (source_idx, source_scope) in graph.containment_scopes().enumerate().rev() {
for (node_id, destination_idx) in source_scope.node_ids().filter_map(|node_id| {
container_indices
.get(&node_id)
.map(|&destination_idx| (node_id, destination_idx))
}) {
if source_idx == destination_idx {
error!(index = source_idx; "Source and destination indices are the same");
continue;
}
let source = content_stack.get_unchecked(source_idx);
let node = graph.node_by_id(node_id).ok_or_else(|| {
RenderError::Layout(format!(
"Node with id {node_id} not found in graph during layout adjustment"
))
})?;
let source_component = source
.content()
.components()
.iter()
.find(|component| component.node_id == node.id())
.ok_or_else(|| {
RenderError::Layout(format!(
"Component with id {node} not found in source layer {source_idx}"
))
})?;
let target_offset = source
.offset()
.add_point(source_component.bounds().min_point())
.add_point(
source_component
.drawable
.inner()
.shape_to_inner_content_min_point(),
); debug!(
node_id:% = node,
source_offset:? = source.offset();
"Adjusting positioned content offset [source]",
);
let target = content_stack.get_mut_unchecked(destination_idx);
debug!(
node_id:% = node,
original_offset:? = target.offset(),
new_offset:? = target_offset;
"Adjusting positioned content offset [target]",
);
target.set_offset(target_offset);
}
}
Ok(())
}