1use std::collections::HashMap;
4
5use egui::{Context, Id, Pos2, Vec2, pos2};
6
7use crate::ui::TreeizeViewer;
8use crate::ui::state::NodeState;
9use crate::{NodeId, Treeize};
10
11#[derive(Clone, Copy, Debug, PartialEq)]
13pub struct LayoutConfig {
14 pub horizontal_spacing: f32,
16
17 pub vertical_spacing: f32,
19
20 pub start_pos: Pos2,
22}
23
24impl Default for LayoutConfig {
25 fn default() -> Self {
26 LayoutConfig { horizontal_spacing: 200.0, vertical_spacing: 150.0, start_pos: Pos2::ZERO }
27 }
28}
29
30trait NodeDimensionProvider {
33 fn get_size(&self, node_id: NodeId) -> (f32, f32);
35
36 fn get_children(&self, node_id: NodeId) -> Vec<NodeId>;
38}
39
40struct TreeizeAdapter<'a, T> {
42 node_sizes: Option<&'a HashMap<NodeId, Vec2>>,
43 children_map: HashMap<NodeId, Vec<NodeId>>,
44 config: LayoutConfig,
45 _phantom: std::marker::PhantomData<T>,
46}
47
48impl<'a, T> TreeizeAdapter<'a, T> {
49 fn new(
50 treeize: &Treeize<T>,
51 node_sizes: Option<&'a HashMap<NodeId, Vec2>>,
52 has_output: &mut impl FnMut(NodeId) -> bool,
53 has_input: &mut impl FnMut(NodeId) -> bool,
54 config: LayoutConfig,
55 ) -> Self {
56 let mut children_map: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
58
59 for (out_pin, in_pin) in treeize.wires() {
60 let from_node = out_pin.node;
61 let to_node = in_pin.node;
62
63 if has_output(from_node) && has_input(to_node) {
64 children_map.entry(from_node).or_default().push(to_node);
65 }
66 }
67
68 TreeizeAdapter { node_sizes, children_map, config, _phantom: std::marker::PhantomData }
69 }
70}
71
72impl<T> NodeDimensionProvider for TreeizeAdapter<'_, T> {
73 fn get_size(&self, node_id: NodeId) -> (f32, f32) {
74 if let Some(sizes) = self.node_sizes
75 && let Some(size) = sizes.get(&node_id)
76 {
77 return (size.x, size.y);
78 }
79 (self.config.horizontal_spacing, self.config.vertical_spacing)
81 }
82
83 fn get_children(&self, node_id: NodeId) -> Vec<NodeId> {
84 self.children_map.get(&node_id).cloned().unwrap_or_default()
85 }
86}
87
88struct LayoutNode {
90 offset_x: f32,
92 contour: Vec<(f32, f32)>,
95 width: f32,
96 height: f32,
97}
98
99struct LayoutState {
101 nodes: HashMap<NodeId, LayoutNode>,
102}
103
104impl LayoutState {
105 fn new() -> Self {
106 Self { nodes: HashMap::new() }
107 }
108}
109
110fn first_walk<P: NodeDimensionProvider>(
112 node_id: NodeId,
113 provider: &P,
114 config: &LayoutConfig,
115 state: &mut LayoutState,
116) {
117 let (w, h) = provider.get_size(node_id);
118 let children = provider.get_children(node_id);
119
120 if children.is_empty() {
121 state.nodes.insert(
123 node_id,
124 LayoutNode { offset_x: 0.0, contour: vec![(-w / 2.0, w / 2.0)], width: w, height: h },
125 );
126 return;
127 }
128
129 for child_id in &children {
131 first_walk(*child_id, provider, config, state);
132 }
133
134 let mut merged_contour: Vec<(f32, f32)> = Vec::new();
136 let mut child_offsets: Vec<f32> = Vec::new();
137 let mut current_offset = 0.0;
138
139 for (i, child_id) in children.iter().enumerate() {
140 let child_node = state.nodes.get(child_id).unwrap();
141
142 if i == 0 {
143 merged_contour.clone_from(&child_node.contour);
145 child_offsets.push(0.0);
146 } else {
147 let mut shift = 0.0f32;
149
150 for ((_l1, r1), (l2, _r2)) in merged_contour.iter().zip(child_node.contour.iter()) {
152 let dist = (r1 + config.horizontal_spacing) - l2;
154 if dist > shift {
155 shift = dist;
156 }
157 }
158
159 child_offsets.push(shift);
160 current_offset = shift;
161
162 for (depth, (l, r)) in child_node.contour.iter().enumerate() {
164 let shifted_l = l + shift;
165 let shifted_r = r + shift;
166
167 if depth < merged_contour.len() {
168 let (exist_l, exist_r) = merged_contour[depth];
170 merged_contour[depth] = (exist_l.min(shifted_l), exist_r.max(shifted_r));
171 } else {
172 merged_contour.push((shifted_l, shifted_r));
174 }
175 }
176 }
177 }
178
179 let children_center = current_offset / 2.0;
183
184 let shift_children_left = -children_center;
188 let mut final_contour = Vec::new();
189 final_contour.push((-w / 2.0, w / 2.0)); for (l, r) in merged_contour {
193 final_contour.push((l + shift_children_left, r + shift_children_left));
194 }
195
196 for (i, child_id) in children.iter().enumerate() {
198 if let Some(node) = state.nodes.get_mut(child_id) {
199 node.offset_x = child_offsets[i] + shift_children_left;
201 }
202 }
203
204 state.nodes.insert(
206 node_id,
207 LayoutNode {
208 offset_x: 0.0, contour: final_contour,
210 width: w,
211 height: h,
212 },
213 );
214}
215
216fn second_walk<P: NodeDimensionProvider>(
218 node_id: NodeId,
219 absolute_x: f32,
220 absolute_y: f32,
221 state: &LayoutState,
222 positions: &mut HashMap<NodeId, Pos2>,
223 provider: &P,
224 config: &LayoutConfig,
225) {
226 let layout_node = state.nodes.get(&node_id).unwrap();
227
228 let center_x = absolute_x + layout_node.offset_x;
232 let top_left_x = center_x - layout_node.width / 2.0;
233
234 positions.insert(node_id, pos2(top_left_x, absolute_y));
235
236 let children = provider.get_children(node_id);
237 let next_y = absolute_y + layout_node.height + config.vertical_spacing;
238
239 for child_id in children {
240 second_walk(child_id, center_x, next_y, state, positions, provider, config);
241 }
242}
243
244#[allow(clippy::implicit_hasher)]
268pub fn layout_tree<T>(
269 treeize: &Treeize<T>,
270 config: LayoutConfig,
271 mut has_output: impl FnMut(NodeId) -> bool,
272 mut has_input: impl FnMut(NodeId) -> bool,
273 node_sizes: Option<&HashMap<NodeId, Vec2>>,
274) -> HashMap<NodeId, Pos2> {
275 let mut positions = HashMap::new();
276
277 let adapter = TreeizeAdapter::new(treeize, node_sizes, &mut has_output, &mut has_input, config);
279
280 let mut has_incoming: HashMap<NodeId, bool> = HashMap::new();
282 for (out_pin, in_pin) in treeize.wires() {
283 let to_node = in_pin.node;
284 if has_output(out_pin.node) && has_input(to_node) {
285 has_incoming.insert(to_node, true);
286 }
287 }
288
289 let root_nodes: Vec<NodeId> = treeize
290 .node_ids()
291 .map(|(node_id, _)| node_id)
292 .filter(|node_id| !has_incoming.get(node_id).copied().unwrap_or(false))
293 .collect();
294
295 if root_nodes.is_empty() {
296 for (node_id, _) in treeize.node_ids() {
298 positions.insert(node_id, config.start_pos);
299 }
300 return positions;
301 }
302
303 let mut layout_state = LayoutState::new();
305 let mut root_x_offset = 0.0;
306
307 for root_id in &root_nodes {
308 first_walk(*root_id, &adapter, &config, &mut layout_state);
310
311 let root_node = layout_state.nodes.get(root_id).unwrap();
313 let tree_width = root_node.contour.iter().map(|(l, r)| r - l).fold(0.0f32, f32::max);
314
315 let root_center_x = config.start_pos.x + root_x_offset + tree_width / 2.0;
318 second_walk(
319 *root_id,
320 root_center_x,
321 config.start_pos.y,
322 &layout_state,
323 &mut positions,
324 &adapter,
325 &config,
326 );
327
328 root_x_offset += tree_width + config.horizontal_spacing;
330 }
331
332 for (node_id, _) in treeize.node_ids() {
334 positions.entry(node_id).or_insert(config.start_pos);
335 }
336
337 positions
338}
339
340pub fn apply_layout<T, H>(treeize: &mut Treeize<T>, positions: &HashMap<NodeId, Pos2, H>)
347where
348 H: std::hash::BuildHasher,
349{
350 for (node_id, pos) in positions {
351 if let Some(node) = treeize.get_node_info_mut(*node_id) {
352 node.pos = *pos;
353 }
354 }
355}
356
357#[allow(clippy::implicit_hasher)]
406pub fn layout_and_apply<T>(
407 treeize: &mut Treeize<T>,
408 config: LayoutConfig,
409 has_output: impl FnMut(NodeId) -> bool,
410 has_input: impl FnMut(NodeId) -> bool,
411 node_sizes: Option<&HashMap<NodeId, Vec2>>,
412) {
413 let positions = layout_tree(treeize, config, has_output, has_input, node_sizes);
414 apply_layout(treeize, &positions);
415}
416
417pub fn layout_with_viewer<T, V>(
452 treeize: &mut Treeize<T>,
453 viewer: &mut V,
454 config: LayoutConfig,
455 ctx: &Context,
456 treeize_id: Id,
457) where
458 V: TreeizeViewer<T>,
459{
460 let mut node_sizes_map: HashMap<NodeId, Vec2> = HashMap::new();
462
463 for (node_id, _) in treeize.node_ids() {
464 let node_state_id = treeize_id.with(("treeize-node", node_id));
465 if let Some(node_data) = NodeState::pick_data(ctx, node_state_id) {
466 node_sizes_map.insert(node_id, node_data.size);
467 }
468 }
469
470 let node_sizes = if node_sizes_map.is_empty() { None } else { Some(&node_sizes_map) };
471
472 let node_info: Vec<(NodeId, bool, bool)> = treeize
474 .node_ids()
475 .map(|(node_id, data)| {
476 let has_out = viewer.has_output(data);
477 let has_in = viewer.has_input(data);
478 (node_id, has_out, has_in)
479 })
480 .collect();
481
482 let mut has_output_map: HashMap<NodeId, bool> = HashMap::new();
484 let mut has_input_map: HashMap<NodeId, bool> = HashMap::new();
485
486 for (node_id, has_out, has_in) in &node_info {
487 has_output_map.insert(*node_id, *has_out);
488 has_input_map.insert(*node_id, *has_in);
489 }
490
491 let has_output_fn =
493 |node_id: NodeId| -> bool { has_output_map.get(&node_id).copied().unwrap_or(false) };
494
495 let has_input_fn =
496 |node_id: NodeId| -> bool { has_input_map.get(&node_id).copied().unwrap_or(false) };
497
498 layout_and_apply(treeize, config, has_output_fn, has_input_fn, node_sizes);
499}