polyhorn_ui/layout/algorithm/yoga/
mod.rs

1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::sync::Mutex;
4
5use super::Algorithm;
6use crate::geometry::{Dimension, Point, Size};
7use crate::layout::{Layout, LayoutAxisX, MeasureFunc};
8use crate::styles::{Position, ViewStyle};
9
10mod convert;
11
12use convert::IntoYoga;
13
14/// Concrete flexbox implementation powered by Yoga.
15pub struct Flexbox {
16    counter: usize,
17    nodes: Mutex<HashMap<usize, RefCell<yoga::Node>>>,
18}
19
20impl Flexbox {
21    fn next_id(&mut self) -> usize {
22        let id = self.counter;
23        self.counter += 1;
24        id
25    }
26}
27
28/// Index into the flexbox's node arena.
29#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
30pub struct Node(usize);
31
32impl Algorithm for Flexbox {
33    type Node = Node;
34
35    fn new() -> Self {
36        Flexbox {
37            counter: 0,
38            nodes: Default::default(),
39        }
40    }
41
42    fn new_node(&mut self, style: ViewStyle, children: &[Self::Node]) -> Self::Node {
43        let id = self.next_id();
44
45        let mut nodes = self.nodes.lock().unwrap();
46        let mut node = yoga::Node::new();
47
48        for (i, child) in children.iter().enumerate() {
49            node.insert_child(&mut nodes.get(&child.0).unwrap().borrow_mut(), i as u32);
50        }
51
52        nodes.insert(id, RefCell::new(node));
53
54        std::mem::drop(nodes);
55
56        let node = Node(id);
57
58        self.set_style(node, style);
59
60        node
61    }
62
63    fn new_leaf(&mut self, style: ViewStyle, measure: MeasureFunc) -> Self::Node {
64        let id = self.next_id();
65        let node = yoga::Node::new();
66
67        self.nodes.lock().unwrap().insert(id, RefCell::new(node));
68
69        let node = Node(id);
70
71        self.set_style(node, style);
72        self.set_measure(node, measure);
73
74        node
75    }
76
77    fn add_child(&mut self, parent: Self::Node, child: Self::Node) {
78        let nodes = self.nodes.lock().unwrap();
79        let mut parent = nodes.get(&parent.0).unwrap().borrow_mut();
80        let mut child = nodes.get(&child.0).unwrap().borrow_mut();
81        let child_count = parent.child_count();
82        parent.insert_child(&mut child, child_count);
83    }
84
85    fn remove_child(&mut self, parent: Self::Node, child: Self::Node) {
86        let nodes = self.nodes.lock().unwrap();
87        let mut parent = nodes.get(&parent.0).unwrap().borrow_mut();
88        let mut child = nodes.get(&child.0).unwrap().borrow_mut();
89        parent.remove_child(&mut child);
90    }
91
92    fn child_count(&self, parent: Self::Node) -> usize {
93        self.nodes
94            .lock()
95            .unwrap()
96            .get(&parent.0)
97            .unwrap()
98            .borrow()
99            .child_count() as usize
100    }
101
102    fn remove(&mut self, node: Self::Node) {
103        let _ = self.nodes.lock().unwrap().remove(&node.0);
104    }
105
106    fn set_style(&mut self, node: Self::Node, style: ViewStyle) {
107        let nodes = self.nodes.lock().unwrap();
108        let mut node = nodes.get(&node.0).unwrap().borrow_mut();
109
110        match style.position {
111            Position::Absolute(absolute) => {
112                node.set_position_type(yoga::PositionType::Absolute);
113
114                node.set_position(yoga::Edge::Top, absolute.distances.vertical.top.into_yoga());
115                node.set_position(
116                    yoga::Edge::Bottom,
117                    absolute.distances.vertical.bottom.into_yoga(),
118                );
119
120                match absolute.distances.horizontal {
121                    LayoutAxisX::DirectionDependent { leading, trailing } => {
122                        node.set_position(yoga::Edge::Start, leading.into_yoga());
123                        node.set_position(yoga::Edge::End, trailing.into_yoga());
124                    }
125                    LayoutAxisX::DirectionIndependent { left, right } => {
126                        node.set_position(yoga::Edge::Left, left.into_yoga());
127                        node.set_position(yoga::Edge::Right, right.into_yoga());
128                    }
129                }
130            }
131            Position::Relative(relative) => {
132                node.set_position_type(yoga::PositionType::Relative);
133                node.set_flex_basis(relative.flex_basis.into_yoga());
134                node.set_flex_grow(relative.flex_grow);
135                node.set_flex_shrink(relative.flex_shrink);
136            }
137        };
138
139        node.set_flex_direction(style.flex_direction.into_yoga());
140        node.set_align_items(style.align_items.into_yoga());
141        node.set_justify_content(style.justify_content.into_yoga());
142
143        node.set_min_width(style.min_size.width.into_yoga());
144        node.set_width(style.size.width.into_yoga());
145        node.set_max_width(style.max_size.width.into_yoga());
146
147        node.set_min_height(style.min_size.height.into_yoga());
148        node.set_height(style.size.height.into_yoga());
149        node.set_max_height(style.max_size.height.into_yoga());
150
151        node.set_padding(yoga::Edge::Top, style.padding.vertical.top.into_yoga());
152        node.set_padding(
153            yoga::Edge::Bottom,
154            style.padding.vertical.bottom.into_yoga(),
155        );
156
157        match style.padding.horizontal {
158            LayoutAxisX::DirectionDependent { leading, trailing } => {
159                node.set_padding(yoga::Edge::Start, leading.into_yoga());
160                node.set_padding(yoga::Edge::End, trailing.into_yoga());
161            }
162            LayoutAxisX::DirectionIndependent { left, right } => {
163                node.set_padding(yoga::Edge::Left, left.into_yoga());
164                node.set_padding(yoga::Edge::Right, right.into_yoga());
165            }
166        }
167
168        node.set_margin(yoga::Edge::Top, style.margin.vertical.top.into_yoga());
169        node.set_margin(yoga::Edge::Bottom, style.margin.vertical.bottom.into_yoga());
170
171        match style.margin.horizontal {
172            LayoutAxisX::DirectionDependent { leading, trailing } => {
173                node.set_margin(yoga::Edge::Start, leading.into_yoga());
174                node.set_margin(yoga::Edge::End, trailing.into_yoga());
175            }
176            LayoutAxisX::DirectionIndependent { left, right } => {
177                node.set_margin(yoga::Edge::Left, left.into_yoga());
178                node.set_margin(yoga::Edge::Right, right.into_yoga());
179            }
180        }
181
182        node.set_overflow(style.overflow.into_yoga());
183    }
184
185    fn set_measure(&mut self, node: Self::Node, measure: MeasureFunc) {
186        let nodes = self.nodes.lock().unwrap();
187        let mut node = nodes.get(&node.0).unwrap().borrow_mut();
188
189        node.set_context(Some(yoga::Context::new(measure)));
190
191        extern "C" fn measure_fn(
192            node: yoga::NodeRef,
193            width: f32,
194            _width_mode: yoga::MeasureMode,
195            height: f32,
196            _height_mode: yoga::MeasureMode,
197        ) -> yoga::Size {
198            let measure = yoga::Node::get_context(&node)
199                .unwrap()
200                .downcast_ref::<MeasureFunc>()
201                .unwrap();
202
203            match measure {
204                MeasureFunc::Boxed(boxed) => {
205                    let result = boxed(Size {
206                        width: Dimension::Points(width),
207                        height: Dimension::Points(height),
208                    });
209
210                    yoga::Size {
211                        width: result.width,
212                        height: result.height,
213                    }
214                }
215            }
216        }
217
218        node.set_measure_func(Some(measure_fn))
219    }
220
221    fn compute_layout(&mut self, node: Self::Node, size: Size<Dimension<f32>>) {
222        let nodes = self.nodes.lock().unwrap();
223        let mut node = nodes.get(&node.0).unwrap().borrow_mut();
224
225        node.calculate_layout(
226            match size.width {
227                Dimension::Points(width) => width,
228                _ => 0.0,
229            },
230            match size.height {
231                Dimension::Points(height) => height,
232                _ => 0.0,
233            },
234            yoga::Direction::LTR,
235        );
236    }
237
238    fn layout(&self, node: Self::Node) -> Layout {
239        let nodes = self.nodes.lock().unwrap();
240        let node = nodes.get(&node.0).unwrap().borrow();
241
242        Layout {
243            origin: Point {
244                x: node.get_layout_left(),
245                y: node.get_layout_top(),
246            },
247            size: Size {
248                width: node.get_layout_width(),
249                height: node.get_layout_height(),
250            },
251        }
252    }
253}
254
255/// Send and sync are not implemented for `yoga::Node` but they can be sent
256/// between threads and they are synced by the mutex of Flexbox.
257unsafe impl Send for Flexbox {}
258unsafe impl Sync for Flexbox {}