1use core::{
9 hash::{BuildHasher, Hash},
10 marker::PhantomData,
11 num::FpCategory,
12};
13
14use alloc::vec::Vec;
15use glam::Vec2;
16use scratchpads::Scratchpad;
17use tinyvec::ArrayVec;
18
19use crate::{
20 IndependentContents, LayoutItem, Layouter, Node, Weave,
21 dependent::{DependentNode, DependentWeave},
22 independent::{IndependentNode, IndependentWeave},
23 layout::positioner::Layout2D,
24};
25
26mod positioner;
27
28#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
32#[must_use]
33pub struct Spacing {
34 pub node: f32,
36 pub layer: f32,
38 pub corridor: f32,
40 pub edge: f32,
42}
43
44impl Default for Spacing {
45 fn default() -> Self {
46 Self {
47 node: 16.0,
48 layer: 16.0,
49 corridor: 0.0,
50 edge: 8.0,
51 }
52 }
53}
54
55impl Spacing {
56 #[must_use]
58 pub const fn validate(&self) -> bool {
59 validate_float(self.node)
60 && validate_float(self.layer)
61 && validate_float(self.corridor)
62 && validate_float(self.edge)
63 }
64}
65
66#[derive(Default, Debug, Clone)]
70#[must_use]
71pub struct DependentLayouter<K>
72where
73 K: Hash + Copy + Eq + Ord,
74{
75 pub spacing: Spacing,
77
78 layout: Layout2D<K>,
79}
80
81impl<K> DependentLayouter<K>
82where
83 K: Hash + Copy + Eq + Ord,
84{
85 pub fn new(spacing: Spacing) -> Self {
87 Self {
88 spacing,
89 layout: Layout2D::default(),
90 }
91 }
92}
93
94impl<K, T, M, S>
95 Layouter<DependentWeave<K, T, M, S>, K, DependentNode<K, T, S>, T, Vec2, ArrayVec<[Vec2; 6]>>
96 for DependentLayouter<K>
97where
98 K: Hash + Copy + Eq + Ord + 'static,
99 S: BuildHasher + Default + Clone + 'static,
100{
101 fn layout(&mut self, weave: &mut DependentWeave<K, T, M, S>, sizes: impl FnMut(&K) -> Vec2) {
102 self.layout.layout_dependent(weave, sizes, &self.spacing);
103 }
104 fn size(&self) -> Vec2 {
105 self.layout.size()
106 }
107 fn view(
108 &mut self,
109 min: Vec2,
110 max: Vec2,
111 callback: impl FnMut(LayoutItem<K, Vec2, ArrayVec<[Vec2; 6]>>),
112 ) {
113 self.layout.view(min, max, callback);
114 }
115}
116
117#[derive(Default, Debug, Clone)]
121#[must_use]
122pub struct IndependentLayouter<K>
123where
124 K: Hash + Copy + Eq + Ord,
125{
126 pub spacing: Spacing,
128
129 layout: Layout2D<K>,
130 topological: Vec<K>,
131}
132
133impl<K> IndependentLayouter<K>
134where
135 K: Hash + Copy + Eq + Ord,
136{
137 pub fn new(spacing: Spacing) -> Self {
139 Self {
140 spacing,
141 layout: Layout2D::default(),
142 topological: Vec::new(),
143 }
144 }
145}
146
147impl<K, T, M, S>
148 Layouter<
149 IndependentWeave<K, T, M, S>,
150 K,
151 IndependentNode<K, T, S>,
152 T,
153 Vec2,
154 ArrayVec<[Vec2; 6]>,
155 > for IndependentLayouter<K>
156where
157 K: Hash + Copy + Eq + Ord + 'static,
158 T: IndependentContents,
159 S: BuildHasher + Default + Clone + 'static,
160{
161 fn layout(&mut self, weave: &mut IndependentWeave<K, T, M, S>, sizes: impl FnMut(&K) -> Vec2) {
162 weave.get_ordered_identifiers(&mut self.topological);
163
164 self.layout
165 .layout_independent(weave, sizes, &self.spacing, &mut self.topological);
166 }
167 fn size(&self) -> Vec2 {
168 self.layout.size()
169 }
170 fn view(
171 &mut self,
172 min: Vec2,
173 max: Vec2,
174 callback: impl FnMut(LayoutItem<K, Vec2, ArrayVec<[Vec2; 6]>>),
175 ) {
176 self.layout.view(min, max, callback);
177 }
178}
179
180#[derive(Debug, Clone)]
184#[must_use]
185pub struct TopologicalLayouter<K, S>
186where
187 K: Hash + Copy + Eq + Ord,
188 S: BuildHasher + Default + Clone,
189{
190 pub spacing: Spacing,
192
193 layout: Layout2D<K>,
194 topological: Vec<K>,
195 scratchpad: Scratchpad,
196 _hasher: PhantomData<S>,
197}
198
199impl<K, S> Default for TopologicalLayouter<K, S>
200where
201 K: Hash + Copy + Eq + Ord,
202 S: BuildHasher + Default + Clone,
203{
204 fn default() -> Self {
205 Self::new(Spacing::default())
206 }
207}
208
209impl<K, S> TopologicalLayouter<K, S>
210where
211 K: Hash + Copy + Eq + Ord,
212 S: BuildHasher + Default + Clone,
213{
214 pub fn new(spacing: Spacing) -> Self {
216 Self {
217 spacing,
218 layout: Layout2D::default(),
219 topological: Vec::new(),
220 scratchpad: Scratchpad::new(),
221 _hasher: PhantomData,
222 }
223 }
224}
225
226impl<W, K, N, T, S> Layouter<W, K, N, T, Vec2, ArrayVec<[Vec2; 6]>> for TopologicalLayouter<K, S>
227where
228 W: Weave<K, N, T>,
229 K: Hash + Copy + Eq + Ord + 'static,
230 N: Node<K, T>,
231 S: BuildHasher + Default + Clone + 'static,
232 for<'a> &'a N::From: IntoIterator<Item = &'a K>,
233{
234 fn layout(&mut self, weave: &mut W, sizes: impl FnMut(&K) -> Vec2) {
235 weave.get_ordered_identifiers(&mut self.topological);
236
237 assert_eq!(
238 weave.len(),
239 self.topological.len(),
240 "Malformed topological order"
241 );
242
243 self.layout.layout_topological::<W, N, T, S, _>(
244 weave,
245 sizes,
246 &self.spacing,
247 &mut self.scratchpad,
248 &mut self.topological,
249 );
250 }
251 fn size(&self) -> Vec2 {
252 self.layout.size()
253 }
254 fn view(
255 &mut self,
256 min: Vec2,
257 max: Vec2,
258 callback: impl FnMut(LayoutItem<K, Vec2, ArrayVec<[Vec2; 6]>>),
259 ) {
260 self.layout.view(min, max, callback);
261 }
262}
263
264#[allow(
268 clippy::float_arithmetic,
269 clippy::arithmetic_side_effects,
270 reason = "Coordinate calculation"
271)]
272#[must_use]
273pub fn smooth(points: ArrayVec<[Vec2; 6]>) -> ArrayVec<[[Vec2; 4]; 5]> {
274 let mut segments = ArrayVec::new();
275
276 for [start, end] in points.array_windows::<2>().copied() {
277 let y_diff = end.y - start.y;
278
279 segments.push(if y_diff == 0.0 {
280 let x_diff = end.x - start.x;
281
282 [
283 start,
284 start + Vec2::new(x_diff * (1.0 / 3.0), 0.0),
285 start + Vec2::new(x_diff * (2.0 / 3.0), 0.0),
286 end,
287 ]
288 } else {
289 let arm = Vec2::new(0.0, y_diff * 0.5);
290
291 [start, start + arm, end - arm, end]
292 });
293 }
294
295 segments
296}
297
298#[must_use]
299const fn validate_float(value: f32) -> bool {
300 matches!(value.classify(), FpCategory::Normal | FpCategory::Zero) && value.is_sign_positive()
301}
302
303#[must_use]
304const fn validate_vec2(value: Vec2) -> bool {
305 validate_float(value.x) && validate_float(value.y)
306}
307
308#[must_use]
309const fn validate_output_float(value: f32) -> bool {
310 matches!(
311 value.classify(),
312 FpCategory::Normal | FpCategory::Zero | FpCategory::Subnormal
313 ) && value.is_sign_positive()
314}