use ahash::{HashMap, HashSet};
use re_sdk_types::TransformFrameIdHash;
use super::{LayoutDirection, Model};
pub(super) struct Layout<'a> {
model: &'a Model,
pub(super) direction: LayoutDirection,
pub(super) positions: HashMap<TransformFrameIdHash, egui::Pos2>,
visited: HashSet<TransformFrameIdHash>,
next_cross: f32,
node_offset: egui::Vec2,
pub(super) margin: f32,
}
impl<'a> Layout<'a> {
const START_CROSS_OFFSET: f32 = 30.0;
const MARGIN: f32 = 30.0;
pub(super) fn compute(
model: &'a Model,
direction: LayoutDirection,
node_size: egui::Vec2,
) -> Self {
let mut layout = Self::new(model, direction, node_size);
let mut roots = model
.snapshot
.frames
.iter()
.filter(|node| !model.edge_indices_by_child.contains_key(&node.id))
.map(|node| node.id)
.collect::<Vec<_>>();
roots.sort_by_key(|id| model.sort_key(*id));
for root in roots {
layout.layout_tree(root, 0);
layout.finish_tree();
}
for node in &model.snapshot.frames {
if !layout.visited.contains(&node.id) {
layout.layout_tree(node.id, 0);
layout.finish_tree();
}
}
layout
}
pub(super) fn content_rect(&self, node_size: egui::Vec2) -> egui::Rect {
let max_x = self.positions.values().map(|pos| pos.x).fold(0.0, f32::max)
+ node_size.x
+ self.margin;
let max_y = self.positions.values().map(|pos| pos.y).fold(0.0, f32::max)
+ node_size.y
+ self.margin;
egui::Rect::from_min_size(
egui::Pos2::ZERO,
egui::vec2(max_x, max_y).max(egui::vec2(1.0, 1.0)),
)
.expand(self.margin)
}
pub(super) fn fork_depth_coordinate(
&self,
parent_pos: egui::Pos2,
child_pos: egui::Pos2,
node_size: egui::Vec2,
) -> f32 {
match self.direction {
LayoutDirection::Horizontal => (parent_pos.x + node_size.x + child_pos.x) / 2.0,
LayoutDirection::Vertical => (parent_pos.y + node_size.y + child_pos.y) / 2.0,
}
}
fn new(model: &'a Model, direction: LayoutDirection, node_size: egui::Vec2) -> Self {
Self {
model,
direction,
positions: Default::default(),
visited: Default::default(),
next_cross: Self::START_CROSS_OFFSET,
node_offset: direction.node_offset(node_size),
margin: Self::MARGIN,
}
}
fn layout_tree(&mut self, frame: TransformFrameIdHash, depth: usize) -> f32 {
if !self.visited.insert(frame) {
return self
.positions
.get(&frame)
.map_or(self.next_cross, |pos| self.cross_coordinate(*pos));
}
let children = self
.model
.edge_indices_by_parent
.get(&frame)
.cloned()
.unwrap_or_default();
let child_cross_coordinates = children
.into_iter()
.map(|edge_index| {
self.layout_tree(self.model.snapshot.edges[edge_index].child, depth + 1)
})
.collect::<Vec<_>>();
let cross = if child_cross_coordinates.is_empty() {
let cross = self.next_cross;
self.next_cross += self.cross_spacing();
cross
} else {
child_cross_coordinates
.first()
.copied()
.unwrap_or(self.next_cross)
.midpoint(
child_cross_coordinates
.last()
.copied()
.unwrap_or(self.next_cross),
)
};
self.positions
.insert(frame, self.node_position(depth, cross));
cross
}
fn finish_tree(&mut self) {
self.next_cross += self.cross_spacing();
}
fn node_position(&self, depth: usize, cross: f32) -> egui::Pos2 {
let depth_coordinate = self.margin + depth as f32 * self.depth_spacing();
match self.direction {
LayoutDirection::Horizontal => egui::pos2(depth_coordinate, cross),
LayoutDirection::Vertical => egui::pos2(cross, depth_coordinate),
}
}
fn cross_coordinate(&self, pos: egui::Pos2) -> f32 {
match self.direction {
LayoutDirection::Horizontal => pos.y,
LayoutDirection::Vertical => pos.x,
}
}
fn cross_spacing(&self) -> f32 {
match self.direction {
LayoutDirection::Horizontal => self.node_offset.y,
LayoutDirection::Vertical => self.node_offset.x,
}
}
fn depth_spacing(&self) -> f32 {
match self.direction {
LayoutDirection::Horizontal => self.node_offset.x,
LayoutDirection::Vertical => self.node_offset.y,
}
}
}
impl LayoutDirection {
fn node_offset(self, node_size: egui::Vec2) -> egui::Vec2 {
match self {
Self::Horizontal => node_size + egui::vec2(65.0, 20.0),
Self::Vertical => node_size + egui::vec2(50.0, 60.0),
}
}
}