orinium_browser/engine/ui/components/
custom_node_bridge.rs1use ui_layout::{
9 BoxModel, CustomLayouter, Display, InlineBox, LayoutBox, LayoutContext, OuterDisplay, Rect,
10 Style,
11};
12
13use crate::engine::ui::custom_node::{ContentSize, CustomNode};
14
15use super::inline_cache::resolve_border_box_size;
16
17#[derive(Debug)]
36pub struct CustomNodeBridge {
37 node: std::sync::Arc<dyn CustomNode>,
38 layout_style: Style,
39}
40
41impl CustomNodeBridge {
42 pub fn new(node: std::sync::Arc<dyn CustomNode>, layout_style: Style) -> Self {
43 Self { node, layout_style }
44 }
45
46 pub fn style(&self) -> &Style {
51 &self.layout_style
52 }
53
54 fn resolve_size(
55 &self,
56 containing_width: Option<f32>,
57 containing_height: Option<f32>,
58 viewport_width: f32,
59 viewport_height: f32,
60 ) -> ContentSize {
61 resolve_border_box_size(
62 self.node.as_ref(),
63 &self.layout_style,
64 containing_width,
65 containing_height,
66 viewport_width,
67 viewport_height,
68 )
69 }
70}
71
72impl CustomLayouter for CustomNodeBridge {
73 fn layout(&mut self, ctx: &LayoutContext) -> LayoutBox {
74 match self.layout_style.display {
75 Display::OutsideInner {
76 outer: OuterDisplay::Inline,
77 ..
78 } => {
79 let x = ctx.start_pos.0;
80 let y = ctx.start_pos.1;
81
82 let resolved = self.resolve_size(
83 Some(ctx.available_inline_size),
84 None,
85 ctx.viewport_width,
86 ctx.viewport_height,
87 );
88 let (use_width, use_height) = (resolved.width, resolved.height);
89
90 let rect = Rect {
91 x,
92 y,
93 width: use_width,
94 height: use_height,
95 };
96 let box_model = BoxModel {
97 sticky_edges: None,
98 border_box: rect,
99 padding_box: rect,
100 content_box: rect,
101 children_box: rect,
102 };
103
104 LayoutBox::InlineBox(InlineBox {
105 box_model,
106 line_spans: Vec::new(),
111 })
112 }
113 Display::OutsideInner {
114 outer: OuterDisplay::Block,
115 ..
116 } => {
117 let resolved = self.resolve_size(
118 ctx.containing_block_width,
119 ctx.containing_block_height,
120 ctx.viewport_width,
121 ctx.viewport_height,
122 );
123
124 let rect = Rect {
125 x: 0.0,
126 y: 0.0,
127 width: resolved.width,
128 height: resolved.height,
129 };
130 let box_model = BoxModel {
131 sticky_edges: None,
132 border_box: rect,
133 padding_box: rect,
134 content_box: rect,
135 children_box: rect,
136 };
137
138 LayoutBox::BlockBox(box_model)
139 }
140 Display::None | Display::Contents => LayoutBox::None,
141 }
142 }
143
144 fn measure(&self, ctx: &LayoutContext) -> ui_layout::MeasureResult {
145 let resolved = self.resolve_size(
146 ctx.containing_block_width,
147 ctx.containing_block_height,
148 ctx.viewport_width,
149 ctx.viewport_height,
150 );
151
152 ui_layout::MeasureResult {
153 width: resolved.width,
154 height: resolved.height,
155 }
156 }
157
158 fn write_debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159 write!(f, "CustomNodeBridge")
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 use crate::engine::layouter::types::{TextFlowStyle, TextStyle};
168 use crate::engine::renderer_model::DrawCommand;
169 use ui_layout::InnerDisplay;
170
171 #[derive(Debug)]
172 struct TestNode {
173 width: f32,
174 height: f32,
175 }
176
177 impl CustomNode for TestNode {
178 fn draw_sized(
179 &self,
180 _cmd_buf: &mut Vec<DrawCommand>,
181 _text_style: &TextStyle,
182 _text_flow_style: &TextFlowStyle,
183 _style: &Style,
184 _size: ContentSize,
185 ) {
186 }
187
188 fn intrinsic_size(&self) -> ContentSize {
189 ContentSize {
190 width: self.width,
191 height: self.height,
192 }
193 }
194 }
195
196 fn bridge(display: Display) -> CustomNodeBridge {
197 let mut style = Style::default();
198 style.display = display;
199 CustomNodeBridge::new(
200 std::sync::Arc::new(TestNode {
201 width: 200.0,
202 height: 100.0,
203 }),
204 style,
205 )
206 }
207
208 #[test]
209 fn block_context_reports_block() {
210 assert_eq!(
211 bridge(Display::OutsideInner {
212 outer: OuterDisplay::Block,
213 inner: InnerDisplay::Flow,
214 })
215 .style()
216 .display
217 .outer(),
218 Some(OuterDisplay::Block)
219 );
220 }
221
222 #[test]
223 fn block_layout_returns_block_box_at_origin() {
224 let mut b = bridge(Display::OutsideInner {
225 outer: OuterDisplay::Block,
226 inner: InnerDisplay::Flow,
227 });
228 match b.layout(&LayoutContext::default()) {
229 LayoutBox::BlockBox(bm) => {
230 assert_eq!(bm.border_box.x, 0.0);
231 assert_eq!(bm.border_box.y, 0.0);
232 assert_eq!(bm.border_box.width, 200.0);
233 assert_eq!(bm.border_box.height, 100.0);
234 }
235 other => panic!("expected BlockBox, got {:?}", other),
236 }
237 }
238
239 #[test]
240 fn block_measure_returns_intrinsic_size() {
241 let b = bridge(Display::OutsideInner {
242 outer: OuterDisplay::Block,
243 inner: InnerDisplay::Flow,
244 });
245 let m = b.measure(&LayoutContext::default());
246 assert_eq!(m.width, 200.0);
247 assert_eq!(m.height, 100.0);
248 }
249
250 #[test]
251 fn inline_context_reports_inline() {
252 assert_eq!(
253 bridge(Display::OutsideInner {
254 outer: OuterDisplay::Inline,
255 inner: InnerDisplay::Flow,
256 })
257 .style()
258 .display
259 .outer(),
260 Some(OuterDisplay::Inline)
261 );
262 }
263
264 #[test]
265 fn inline_layout_returns_an_atomic_unpositioned_box() {
266 let mut b = bridge(Display::OutsideInner {
267 outer: OuterDisplay::Inline,
268 inner: InnerDisplay::Flow,
269 });
270 let ctx = LayoutContext {
271 start_pos: (10.0, 20.0),
272 available_inline_size: 300.0,
273 viewport_width: 800.0,
274 viewport_height: 600.0,
275 ..LayoutContext::default()
276 };
277 match b.layout(&ctx) {
278 LayoutBox::InlineBox(inline) => {
279 assert_eq!(inline.box_model.border_box.x, 10.0);
280 assert_eq!(inline.box_model.border_box.y, 20.0);
281 assert_eq!(inline.box_model.border_box.width, 200.0);
282 assert_eq!(inline.box_model.border_box.height, 100.0);
283 assert!(inline.line_spans.is_empty());
284 }
285 other => panic!("expected InlineBox, got {:?}", other),
286 }
287 }
288
289 #[test]
290 fn inline_measure_returns_intrinsic_size() {
291 let b = bridge(Display::OutsideInner {
292 outer: OuterDisplay::Inline,
293 inner: InnerDisplay::Flow,
294 });
295 let m = b.measure(&LayoutContext::default());
296 assert_eq!(m.width, 200.0);
297 assert_eq!(m.height, 100.0);
298 }
299
300 #[test]
301 fn none_context_skips_element() {
302 let mut b = bridge(Display::None);
303 assert_eq!(b.style().display, Display::None);
304 assert!(matches!(
305 b.layout(&LayoutContext::default()),
306 LayoutBox::None
307 ));
308 }
309
310 #[test]
311 fn inline_size_resolves_against_available_inline_size() {
312 use ui_layout::{Length, LengthOrAuto};
313
314 let mut style = Style {
315 size: ui_layout::SizeStyle {
316 width: LengthOrAuto::Length(Length::Percent(50.0)),
317 ..Default::default()
318 },
319 ..Default::default()
320 };
321 style.display = Display::OutsideInner {
322 outer: OuterDisplay::Inline,
323 inner: InnerDisplay::Flow,
324 };
325 let b = CustomNodeBridge::new(
326 std::sync::Arc::new(TestNode {
327 width: 200.0,
328 height: 100.0,
329 }),
330 style,
331 );
332 let mut b = b;
333 let ctx = LayoutContext {
334 start_pos: (0.0, 0.0),
335 available_inline_size: 150.0,
336 viewport_width: 800.0,
337 viewport_height: 600.0,
338 ..LayoutContext::default()
339 };
340 match b.layout(&ctx) {
341 LayoutBox::InlineBox(inline) => {
342 assert_eq!(inline.box_model.border_box.width, 75.0);
343 }
344 other => panic!("expected InlineBox, got {:?}", other),
345 }
346 }
347}