oxframe_components/
flow.rs1use std::collections::BTreeMap;
4
5use oxframe_2d::{
6 Accent, BaseProps, Group, Len, Lg, Line, Md, Muted, NodeBuilder, NodeId, Rect, Surface, Txt, Xl,
7};
8use oxframe_core::Tween;
9
10use crate::host::{self, Host};
11
12pub struct FlowDiagram {
16 host: Host,
17}
18
19impl FlowDiagram {
20 #[allow(clippy::new_ret_no_self)]
22 pub fn new() -> FlowDiagramBuilder {
23 FlowDiagramBuilder {
24 base: BaseProps::default(),
25 edges: Vec::new(),
26 down: false,
27 }
28 }
29
30 pub fn enter(&self) -> Tween<f64> {
32 self.host.enter()
33 }
34 pub fn next(&self) -> Tween<f64> {
36 self.host.next()
37 }
38 pub fn exit(&self) -> Tween<f64> {
40 self.host.exit()
41 }
42 pub fn len(&self) -> usize {
44 self.host.len()
45 }
46 pub fn is_empty(&self) -> bool {
48 self.host.is_empty()
49 }
50}
51
52#[must_use = "a builder does nothing until it is passed to `add`"]
54pub struct FlowDiagramBuilder {
55 base: BaseProps,
56 edges: Vec<(String, String)>,
57 down: bool,
58}
59
60impl FlowDiagramBuilder {
61 pub fn edge(mut self, from: impl Into<String>, to: impl Into<String>) -> Self {
63 self.edges.push((from.into(), to.into()));
64 self
65 }
66 pub fn down(mut self) -> Self {
68 self.down = true;
69 self
70 }
71}
72
73impl NodeBuilder for FlowDiagramBuilder {
74 type Handle = FlowDiagram;
75 fn base(&mut self) -> &mut BaseProps {
76 &mut self.base
77 }
78 fn build(self, _: &oxframe_2d::SceneGraph, parent: NodeId) -> FlowDiagram {
79 let root: Group = host::root(parent, self.base);
80 let mut index = BTreeMap::<String, usize>::new();
81 let mut names = Vec::new();
82 let mut links = Vec::new();
83 for (from, to) in self.edges {
84 let a = *index.entry(from.clone()).or_insert_with(|| {
85 names.push(from);
86 names.len() - 1
87 });
88 let b = *index.entry(to.clone()).or_insert_with(|| {
89 names.push(to);
90 names.len() - 1
91 });
92 links.push((a, b));
93 }
94 let layers = layers_of(names.len(), &links);
95 let (card_w, card_h) = (200.0, 84.0);
96 let gap = Md.px();
97 let layer_gap = Xl.px();
98 let max_layer = layers.iter().copied().max().unwrap_or(0);
99 let mut counts = vec![0usize; max_layer + 1];
100 for layer in &layers {
101 counts[*layer] += 1;
102 }
103 let along = (max_layer as f64) * (card_w + layer_gap);
104 let mut slot = vec![0usize; max_layer + 1];
105 let mut pos = vec![(0.0, 0.0); names.len()];
106 for (i, layer) in layers.iter().copied().enumerate() {
107 let n = counts[layer].max(1) as f64;
108 let across = (n - 1.0) * (card_h + gap);
109 let a = -(along / 2.0) + layer as f64 * (card_w + layer_gap);
110 let b = -(across / 2.0) + slot[layer] as f64 * (card_h + gap);
111 slot[layer] += 1;
112 pos[i] = if self.down { (b, a) } else { (a, b) };
113 }
114 let mut reveals = Vec::new();
115 for (i, name) in names.iter().enumerate() {
116 let (x, y) = pos[i];
117 root.add(
118 Rect::new()
119 .width(card_w)
120 .height(card_h)
121 .radius(Lg)
122 .fill(Surface)
123 .x(x)
124 .y(y)
125 .name(name.clone()),
126 );
127 root.add(
128 Txt::new()
129 .text(name.clone())
130 .body()
131 .x(x)
132 .y(y)
133 .name(format!("{name}-label")),
134 );
135 let bar = root.add(
136 Rect::new()
137 .width(8.0)
138 .height(card_h - 28.0)
139 .fill(Accent)
140 .x(x - card_w / 2.0 + 16.0)
141 .y(y)
142 .opacity(0.0)
143 .name(format!("{name}-bar")),
144 );
145 reveals.push(bar.node());
146 }
147 for (a, b) in links {
148 let (x1, y1) = pos[a];
149 let (x2, y2) = pos[b];
150 let (start, end) = if self.down {
151 ((x1, y1 + card_h / 2.0), (x2, y2 - card_h / 2.0))
152 } else {
153 ((x1 + card_w / 2.0, y1), (x2 - card_w / 2.0, y2))
154 };
155 root.add(
156 Line::new()
157 .points([start, end])
158 .end_arrow(true)
159 .stroke(Muted)
160 .line_width(4.0)
161 .name("link"),
162 );
163 }
164 FlowDiagram {
165 host: Host::new(root.node(), reveals),
166 }
167 }
168}
169
170fn layers_of(n: usize, links: &[(usize, usize)]) -> Vec<usize> {
171 let mut layer = vec![0; n];
172 for _ in 0..n.max(1) {
173 let mut changed = false;
174 for &(from, to) in links {
175 let next = layer[from] + 1;
176 if next > layer[to] && next < n {
177 layer[to] = next;
178 changed = true;
179 }
180 }
181 if !changed {
182 break;
183 }
184 }
185 layer
186}