1use std::cell::RefCell;
7use std::collections::HashSet;
8use std::time::Instant;
9
10use taffy::prelude::*;
11use taffy::style::Direction;
12
13use super::{Frame, Layer, LayoutOutput, ListOutput, NodeDelta, PlacedFrame, ScrollOutput, Surface};
14use crate::engine::LeafAnswer;
15use crate::layout_tree::{LayerPlacement, NodeKind};
16use crate::measure::{HeightRequest, Intrinsics, LeafRequest, Measure, MeasureMemo, MeasureRequest};
17use crate::overlay::{place_overlay, OverlayAlign, OverlayPlacement, OverlaySide, PlaceOptions, Rect as ORect, Size as OSize, OVERLAY_PADDING};
18use crate::style::{self, BoxKind};
19
20pub const DIALOG_MAX_WIDTH: f32 = 512.0;
22pub const DRAWER_MAX_WIDTH: f32 = 360.0;
23pub const SHEET_MAX_HEIGHT: f32 = 0.9;
25pub const TOAST_MAX_WIDTH: f32 = 420.0;
27pub const TOAST_GAP: f32 = 8.0;
28
29type Placed = (Option<OverlayPlacement>, String, Option<Frame>);
31
32#[derive(Debug, Clone)]
34pub(super) struct PassState {
35 started: Instant,
36 upcalls: u32,
37 rounds: u32,
38 missing: Vec<usize>,
39 heights: Vec<HeightRequest>,
40}
41
42#[derive(Debug, Clone, PartialEq)]
44pub struct LeafData {
45 pub index: u32,
46 pub id: String,
47 pub component: String,
48 pub part: Option<String>,
49 pub owner_component: Option<String>,
50 pub props: serde_json::Map<String, serde_json::Value>,
51 pub text_style: crate::measure::TextStyle,
52 pub control: crate::measure::ControlBox,
53 pub lines: Option<u32>,
54}
55
56impl LeafData {
57 pub fn request(&self) -> LeafRequest<'_> {
59 LeafRequest { index: self.index, id: &self.id, component: &self.component, part: self.part.as_deref(), owner_component: self.owner_component.as_deref(), props: &self.props, text_style: &self.text_style, control: self.control, lines: self.lines }
60 }
61}
62
63#[derive(Debug, Clone, PartialEq)]
65pub enum LayoutStep {
66 Intrinsics(Vec<LeafData>),
68 Heights(Vec<LeafData>, Vec<HeightRequest>),
70 Done(Box<LayoutOutput>),
72 Idle,
74}
75
76#[derive(Debug, Clone, Copy)]
78struct View {
79 x: f32,
80 y: f32,
81 w: f32,
82 h: f32,
83}
84
85impl Surface {
86 fn leaf_request(&self, i: usize) -> LeafRequest<'_> {
87 let n = &self.nodes[i];
88 let s = &self.node_state[i];
89 LeafRequest { index: i as u32, id: &n.id, component: &n.component, part: n.part.as_deref(), owner_component: n.owner_component.as_deref(), props: &n.props, text_style: &s.text, control: s.control, lines: n.lines }
90 }
91
92 fn is_measured_leaf(&self, i: usize) -> bool {
93 self.live[i] && self.nodes[i].kind == NodeKind::Leaf && !self.nodes[i].hidden
94 }
95
96 fn prepare(&mut self, measurer: u64) {
98 self.ensure_reduced();
99 if self.last_measurer.is_some_and(|m| m != measurer) {
100 self.invalidate_measures();
101 }
102 self.last_measurer = Some(measurer);
103 if self.needs_build {
104 self.subtree_pending.clear();
105 self.rebuild();
106 } else {
107 for id in std::mem::take(&mut self.subtree_pending) {
108 if !self.rebuild_subtree(&id) {
109 self.rebuild();
110 self.subtree_pending.clear();
111 break;
112 }
113 }
114 }
115 self.restyle();
116 }
117
118 fn missing_leaves(&self) -> Vec<usize> {
120 (0..self.nodes.len())
121 .filter(|&i| self.is_measured_leaf(i) && (!self.memo.intrinsics.contains_key(&(i as u32)) || self.memo_versions.get(&(i as u32)).copied() != Some(self.node_state[i].content_version)))
122 .collect()
123 }
124
125 fn apply_intrinsics(&mut self, missing: &[usize], answers: &[Intrinsics]) {
129 for (k, &i) in missing.iter().enumerate() {
130 if let Some(a) = answers.get(k) {
131 self.memo.intrinsics.insert(i as u32, a.sanitised());
132 self.memo_versions.insert(i as u32, self.node_state[i].content_version);
133 self.engine.mark_dirty(i as u32);
134 }
135 }
136 }
137
138 fn apply_heights(&mut self, requests: &[HeightRequest], heights: &[f32]) {
139 for (k, r) in requests.iter().enumerate() {
140 if let Some(h) = heights.get(k) {
141 self.memo.insert_height(r.index, MeasureMemo::width_key(r.width), crate::measure::finite_or_zero(*h));
142 self.engine.mark_dirty(r.index);
143 }
144 }
145 }
146
147 fn compute_all(&mut self) -> (Vec<Frame>, Vec<Placed>, Vec<HeightRequest>) {
150 crate::roomy(|| self.compute_all_now())
151 }
152
153 fn compute_all_now(&mut self) -> (Vec<Frame>, Vec<Placed>, Vec<HeightRequest>) {
154 let (vw, vh) = self.viewport;
155 let guesses = RefCell::new(Vec::<HeightRequest>::new());
156 let mut frames = vec![Frame::default(); self.nodes.len()];
157 let mut placed = Vec::with_capacity(self.layers.len());
158 if let Some(root) = self.main_root {
159 let root_node = &self.nodes[root as usize];
164 let inline_root = matches!(root_node.component.as_str(), "Button" | "Toggle" | "Link" | "Icon" | "Ring" | "Spinner")
165 || (root_node.component == "Segmented" && (root_node.props.get("variant").and_then(|v| v.as_str()) != Some("bar") || crate::segmented::is_rail(&root_node.props)));
166 if !inline_root && self.engine.style(root).size.width == Dimension::auto() {
167 let mut style = self.engine.style(root).clone();
168 style.size.width = Dimension::percent(1.0);
169 self.engine.set_style(root, style);
170 }
171 let available = Size { width: AvailableSpace::Definite(vw.max(0.0)), height: if vh > 0.0 { AvailableSpace::Definite(vh) } else { AvailableSpace::MaxContent } };
172 self.compute(root, available, &guesses);
173 self.walk_frames(root, 0.0, 0.0, &mut frames);
174 }
175 let main_h = self.main_root.map(|r| frames[r as usize].h).unwrap_or(0.0);
176 let vh_eff = if vh > 0.0 { vh } else { self.max_height.unwrap_or(main_h) };
177 let ins = self.settings.insets;
178 let view = View { x: ins.left, y: ins.top, w: (vw - ins.left - ins.right).max(0.0), h: (vh_eff - ins.top - ins.bottom).max(0.0) };
179 let mut toast_stack = 0.0f32;
180 for k in 0..self.layers.len() {
181 let p = self.place_layer(k, view, vw, vh_eff, &mut frames, &guesses, &mut toast_stack);
182 placed.push(p);
183 }
184 let mut seen = HashSet::new();
185 let pending: Vec<HeightRequest> = guesses.into_inner().into_iter().filter(|r| seen.insert((r.index, MeasureMemo::width_key(r.width)))).collect();
186 (frames, placed, pending)
187 }
188
189 fn finish(&mut self, frames: Vec<Frame>, placed: Vec<Placed>, state: PassState) -> LayoutOutput {
190 let (_, vh) = self.viewport;
191 let bounded_h = if vh > 0.0 { Some(vh) } else { self.max_height };
192 let layout_ns = state.started.elapsed().as_nanos() as u64;
193 self.output(frames, placed, bounded_h, state.rounds, state.upcalls, layout_ns)
194 }
195
196 pub fn layout(&mut self, measure: &mut dyn Measure) -> LayoutOutput {
202 let mut state = PassState { started: Instant::now(), upcalls: 0, rounds: 0, missing: Vec::new(), heights: Vec::new() };
203 self.prepare(measure.measure_id());
204 loop {
205 state.rounds += 1;
206 let missing = self.missing_leaves();
207 if !missing.is_empty() {
208 let requests: Vec<LeafRequest> = missing.iter().map(|&i| self.leaf_request(i)).collect();
209 let answers = measure.measure_intrinsics(&requests);
210 state.upcalls += 1;
211 self.apply_intrinsics(&missing, &answers);
212 }
213 let (frames, placed, pending) = self.compute_all();
214 if pending.is_empty() || state.rounds >= 3 {
215 return self.finish(frames, placed, state);
216 }
217 let leaves: Vec<LeafRequest> = pending.iter().map(|r| self.leaf_request(r.index as usize)).collect();
218 let heights = measure.measure_heights(&leaves, &pending);
219 state.upcalls += 1;
220 self.apply_heights(&pending, &heights);
221 }
222 }
223
224 fn owned_leaves(&self, indices: impl Iterator<Item = usize>) -> Vec<LeafData> {
225 indices
226 .map(|i| {
227 let n = &self.nodes[i];
228 let s = &self.node_state[i];
229 LeafData { index: i as u32, id: n.id.clone(), component: n.component.clone(), part: n.part.clone(), owner_component: n.owner_component.clone(), props: n.props.clone(), text_style: s.text.clone(), control: s.control, lines: n.lines }
230 })
231 .collect()
232 }
233
234 pub fn layout_begin(&mut self, measure_id: u64) -> LayoutStep {
240 self.prepare(measure_id);
241 self.step_state = Some(PassState { started: Instant::now(), upcalls: 0, rounds: 0, missing: Vec::new(), heights: Vec::new() });
242 self.step_round()
243 }
244
245 fn step_round(&mut self) -> LayoutStep {
246 let missing = self.missing_leaves();
247 let Some(state) = self.step_state.as_mut() else { return LayoutStep::Idle };
248 state.rounds += 1;
249 if !missing.is_empty() {
250 let leaves = self.owned_leaves(missing.iter().copied());
251 self.step_state.as_mut().expect("state").missing = missing;
252 return LayoutStep::Intrinsics(leaves);
253 }
254 self.step_compute()
255 }
256
257 fn step_compute(&mut self) -> LayoutStep {
258 let (frames, placed, pending) = self.compute_all();
259 let Some(mut state) = self.step_state.take() else { return LayoutStep::Idle };
260 if pending.is_empty() || state.rounds >= 3 {
261 return LayoutStep::Done(Box::new(self.finish(frames, placed, state)));
262 }
263 let leaves = self.owned_leaves(pending.iter().map(|r| r.index as usize));
264 state.heights = pending.clone();
265 self.step_state = Some(state);
266 LayoutStep::Heights(leaves, pending)
267 }
268
269 pub fn layout_abort(&mut self) {
272 self.step_state = None;
273 }
274
275 pub fn layout_in_progress(&self) -> bool {
277 self.step_state.is_some()
278 }
279
280 pub fn layout_intrinsics(&mut self, answers: &[Intrinsics]) -> LayoutStep {
282 let Some(state) = self.step_state.as_mut() else { return LayoutStep::Idle };
283 state.upcalls += 1;
284 let missing = std::mem::take(&mut state.missing);
285 self.apply_intrinsics(&missing, answers);
286 self.step_compute()
287 }
288
289 pub fn layout_heights(&mut self, heights: &[f32]) -> LayoutStep {
291 let Some(state) = self.step_state.as_mut() else { return LayoutStep::Idle };
292 state.upcalls += 1;
293 let requests = std::mem::take(&mut state.heights);
294 self.apply_heights(&requests, heights);
295 self.step_round()
296 }
297
298 fn compute(&mut self, root: u32, available: Size<AvailableSpace>, guesses: &RefCell<Vec<HeightRequest>>) {
300 let Surface { engine, memo, nodes, .. } = self;
301 let mut measure_fn = |index: u32, known: Size<Option<f32>>, avail: Size<AvailableSpace>| -> LeafAnswer {
302 if nodes[index as usize].hidden {
303 return LeafAnswer::default();
304 }
305 let req = MeasureRequest { index, known_width: known.width, known_height: known.height, available_width: avail.width, available_height: avail.height };
306 let (size, guessed, baseline) = memo.answer(&req);
307 if guessed {
308 guesses.borrow_mut().push(HeightRequest { index, width: size.width });
309 }
310 LeafAnswer { size, baseline }
311 };
312 engine.compute(root, available, &mut measure_fn);
313 }
314
315 fn walk_frames(&self, slot: u32, ox: f32, oy: f32, out: &mut [Frame]) {
316 let l = self.engine.layout(slot);
317 let x = ox + l.location.x;
318 let y = oy + l.location.y;
319 out[slot as usize] = Frame { x, y, w: l.size.width, h: l.size.height };
320 for &c in &self.nodes[slot as usize].children {
321 self.walk_frames(c, x, y, out);
322 }
323 }
324
325 fn layer_root_style(&mut self, root: u32, pad_extra: [f32; 4], edit: impl FnOnce(&mut Style)) {
329 let mut flat = self.node_state[root as usize].flat.clone();
330 if pad_extra.iter().any(|p| *p != 0.0) {
331 let base = style::sides_px(&flat, "padding", "paddingHorizontal", "paddingVertical", ["paddingTop", "paddingRight", "paddingBottom", "paddingLeft"]).unwrap_or([0.0; 4]);
332 for (k, key) in ["paddingTop", "paddingRight", "paddingBottom", "paddingLeft"].iter().enumerate() {
333 flat.insert((*key).to_string(), serde_json::json!(base[k] + pad_extra[k]));
334 }
335 }
336 let mut s = style::to_taffy(&flat, self.direction, BoxKind::Container).unwrap_or_default();
337 edit(&mut s);
338 self.engine.set_style(root, s);
339 }
340
341 #[allow(clippy::too_many_arguments)]
342 fn place_layer(&mut self, k: usize, view: View, vw: f32, vh: f32, frames: &mut [Frame], guesses: &RefCell<Vec<HeightRequest>>, toast_stack: &mut f32) -> Placed {
343 let spec = self.layers[k].clone();
344 let root = spec.root;
345 let pad = OVERLAY_PADDING as f32;
346 let rtl = self.direction == Direction::Rtl;
347 let ins = self.settings.insets;
348 let finite = vh.is_finite() && vh > 0.0;
349 let body_shrinks = spec.scroll_body.is_some();
350 let (placement, position, anchor_frame, x, y) = match spec.placement {
351 LayerPlacement::Centered => {
352 let max_h = finite.then_some((view.h - 2.0 * pad).max(0.0));
353 self.layer_root_style(root, [0.0; 4], |s| {
354 if let (Some(h), true) = (max_h, body_shrinks) {
355 s.max_size.height = length(h);
356 }
357 });
358 let width = (view.w - 2.0 * pad).clamp(0.0, DIALOG_MAX_WIDTH);
359 self.compute(root, Size { width: AvailableSpace::Definite(width), height: AvailableSpace::MaxContent }, guesses);
360 let size = self.engine.layout(root).size;
361 let x = view.x + ((view.w - size.width) / 2.0).max(pad);
362 let y = if finite { view.y + ((view.h - size.height) / 2.0).max(pad) } else { view.y + pad };
363 (None, "centered".to_string(), None, x, y)
364 }
365 LayerPlacement::Edge(side) => {
366 let vertical = matches!(side, OverlaySide::Top | OverlaySide::Bottom);
367 let extra = match side {
370 OverlaySide::Bottom => [0.0, ins.right, ins.bottom, ins.left],
371 OverlaySide::Top => [ins.top, ins.right, 0.0, ins.left],
372 OverlaySide::Left => [ins.top, 0.0, ins.bottom, ins.left],
373 OverlaySide::Right => [ins.top, ins.right, ins.bottom, 0.0],
374 };
375 self.layer_root_style(root, extra, |s| {
376 if vertical && finite && body_shrinks {
377 s.max_size.height = length((vh * SHEET_MAX_HEIGHT).max(0.0));
378 }
379 });
380 let available = if vertical {
381 Size { width: AvailableSpace::Definite(vw), height: AvailableSpace::MaxContent }
382 } else {
383 Size { width: AvailableSpace::Definite((vw - 2.0 * pad).clamp(0.0, DRAWER_MAX_WIDTH)), height: if finite { AvailableSpace::Definite(vh) } else { AvailableSpace::MaxContent } }
384 };
385 self.compute(root, available, guesses);
386 let size = self.engine.layout(root).size;
387 let (x, y) = match side {
388 OverlaySide::Top | OverlaySide::Left => (0.0, 0.0),
389 OverlaySide::Bottom => (0.0, if finite { vh - size.height } else { 0.0 }),
390 OverlaySide::Right => (vw - size.width, 0.0),
391 };
392 (None, side.as_str().to_string(), None, x, y)
393 }
394 LayerPlacement::Anchored { anchor, side, align } => {
395 let a = frames.get(anchor as usize).copied().unwrap_or_default();
396 let side = if spec.mirror && rtl {
397 match side {
398 OverlaySide::Left => OverlaySide::Right,
399 OverlaySide::Right => OverlaySide::Left,
400 s => s,
401 }
402 } else {
403 side
404 };
405 let size = self.shrink_to_content(root, view, pad, spec.match_anchor_width.then_some(a.w), guesses);
406 let p = self.anchor_place(a, size, view, side, align, rtl);
407 let pos = p.side.as_str().to_string();
408 (Some(p), pos, Some(a), p.x as f32, p.y as f32)
409 }
410 LayerPlacement::AtPoint { x, y } => {
411 let a = Frame { x, y, w: 0.0, h: 0.0 };
412 let size = self.shrink_to_content(root, view, pad, None, guesses);
413 let p = self.anchor_place(a, size, view, OverlaySide::Bottom, OverlayAlign::Start, rtl);
414 (Some(p), "point".to_string(), Some(a), p.x as f32, p.y as f32)
415 }
416 LayerPlacement::Toast { .. } => {
417 let max = (view.w - 2.0 * pad).clamp(0.0, TOAST_MAX_WIDTH);
418 self.compute(root, Size { width: AvailableSpace::MaxContent, height: AvailableSpace::MaxContent }, guesses);
419 let natural = self.engine.layout(root).size.width;
420 if natural > max {
421 self.compute(root, Size { width: AvailableSpace::Definite(max), height: AvailableSpace::MaxContent }, guesses);
422 }
423 let size = self.engine.layout(root).size;
424 let wide = self.breakpoints().get("md").is_some_and(|md| vw >= *md);
425 let x = if wide {
426 if rtl {
427 view.x + pad
428 } else {
429 view.x + view.w - pad - size.width
430 }
431 } else {
432 view.x + (view.w - size.width) / 2.0
433 };
434 let y = view.y + view.h - pad - *toast_stack - size.height;
435 *toast_stack += size.height + TOAST_GAP;
436 (None, "toast".to_string(), None, x, y)
437 }
438 };
439 self.walk_frames(root, x, y, frames);
440 (placement, position, anchor_frame)
441 }
442
443 fn shrink_to_content(&mut self, root: u32, view: View, pad: f32, min_width: Option<f32>, guesses: &RefCell<Vec<HeightRequest>>) -> OSize {
446 self.layer_root_style(root, [0.0; 4], |s| {
447 if let Some(w) = min_width {
448 s.min_size.width = length(w);
449 }
450 });
451 let max = (view.w - 2.0 * pad).max(0.0);
452 self.compute(root, Size { width: AvailableSpace::MaxContent, height: AvailableSpace::MaxContent }, guesses);
453 let natural = self.engine.layout(root).size.width;
454 if natural > max {
455 self.compute(root, Size { width: AvailableSpace::Definite(max), height: AvailableSpace::MaxContent }, guesses);
456 }
457 let size = self.engine.layout(root).size;
458 OSize { width: size.width as f64, height: size.height as f64 }
459 }
460
461 fn anchor_place(&self, a: Frame, size: OSize, view: View, side: OverlaySide, align: OverlayAlign, rtl: bool) -> OverlayPlacement {
464 let align = match (align, rtl) {
465 (OverlayAlign::Start, true) => OverlayAlign::End,
466 (OverlayAlign::End, true) => OverlayAlign::Start,
467 (a, _) => a,
468 };
469 let anchor = ORect { x: (a.x - view.x) as f64, y: (a.y - view.y) as f64, width: a.w as f64, height: a.h as f64 };
470 let vp = OSize { width: view.w as f64, height: if view.h > 0.0 { view.h as f64 } else { f64::INFINITY } };
471 let mut p = place_overlay(&anchor, &size, &vp, &PlaceOptions { side, align, ..PlaceOptions::default() });
472 p.x += view.x as f64;
473 p.y += view.y as f64;
474 p
475 }
476
477 fn preorder(&self, slot: u32, out: &mut Vec<u32>) {
478 out.push(slot);
479 for &c in &self.nodes[slot as usize].children {
480 self.preorder(c, out);
481 }
482 }
483
484 fn list_view(&self, spec: &crate::layout_tree::ListSpec, frames: &[Frame], scrolls: &[ScrollOutput]) -> crate::layout_tree::ListView {
488 use crate::layout_tree::{ListView, ListViewSource};
489 let h = spec.horizontal;
490 let ext = |f: Frame| if h { f.w } else { f.h };
491 let start = |f: Frame| if h { f.x } else { f.y };
492 let scrolls_on_axis = |idx: u32| {
493 scrolls.iter().any(|s| s.index == idx && if h { s.scroll_x && s.content_width > frames[idx as usize].w + 0.5 } else { s.scroll_y && s.content_height > frames[idx as usize].h + 0.5 })
494 };
495 let f = frames[spec.node as usize];
496 if scrolls_on_axis(spec.node) {
497 return ListView { source: ListViewSource::Own, viewport: ext(f) };
498 }
499 let mut cur = self.nodes[spec.node as usize].parent;
500 while let Some(p) = cur {
501 if scrolls_on_axis(p) {
502 let pf = frames[p as usize];
503 return ListView { source: ListViewSource::Ancestor { id: self.scroll_key(p), rel: start(f) - start(pf) }, viewport: ext(pf) };
504 }
505 cur = self.nodes[p as usize].parent;
506 }
507 let viewport = if h {
508 self.viewport.0
509 } else if self.viewport.1 > 0.0 {
510 self.viewport.1
511 } else {
512 self.max_height.unwrap_or(2000.0)
513 };
514 ListView { source: ListViewSource::Host { rel: start(f) }, viewport }
515 }
516
517 fn sticky_offsets(&mut self, frames: &[Frame], scrolls: &[ScrollOutput], drawn: &[u32]) -> Vec<super::StickyOutput> {
521 let mut out = Vec::new();
522 for spec in self.lists.iter().filter(|l| l.sticky && !l.headers.is_empty()) {
524 let Some(window) = self.local.lists.get_mut(&spec.id) else { continue };
525 let Some(view) = self.local.list_views.get(&spec.id) else { continue };
526 let axis = |p: (f32, f32)| if spec.horizontal { p.0 } else { p.1 };
527 let scroll = match &view.source {
528 crate::layout_tree::ListViewSource::Own => self.local.scroll.get(&spec.id).copied().map(axis).unwrap_or(0.0),
529 crate::layout_tree::ListViewSource::Ancestor { id, rel } => self.local.scroll.get(id).copied().map(axis).unwrap_or(0.0) - rel,
530 crate::layout_tree::ListViewSource::Host { rel } => self.local.surface_scroll.map(|p| axis(p) - rel).unwrap_or_else(|| self.local.scroll.get(&spec.id).copied().map(axis).unwrap_or(0.0)),
531 }
532 .max(0.0);
533 window.gap = spec.gap;
534 let Some((pin, row_offset)) = window.sticky_header_cached(&spec.keys, &spec.headers, scroll as f64) else { continue };
535 let key = &spec.keys[pin.row];
536 let Some(&header) = self.nodes[spec.node as usize].children.iter().find(|c| self.nodes[**c as usize].props.get("key").and_then(serde_json::Value::as_str) == Some(key.as_str())) else { continue };
537 let delta = (pin.offset - row_offset) as f32;
538 let (dx, dy) = if spec.horizontal { (delta, 0.0) } else { (0.0, delta) };
541 if dx.abs() > 0.01 || dy.abs() > 0.01 {
542 out.push(super::StickyOutput { index: header, dx, dy });
543 }
544 }
545 for &i in drawn {
547 let flat = &self.node_state[i as usize].flat;
548 if flat.get("position").and_then(serde_json::Value::as_str) != Some("sticky") {
549 continue;
550 }
551 let all = flat.get("inset").and_then(style::px);
554 let inset = |k: &str| flat.get(k).and_then(style::px).or(all);
555 let (top, bottom, left, right) = (inset("top"), inset("bottom"), inset("left"), inset("right"));
556 let n = frames[i as usize];
557 let parent_index = self.nodes[i as usize].parent;
558 let mut parent = parent_index.map(|p| frames[p as usize]).unwrap_or(n);
559 let mut cur = parent_index;
562 let mut port: Option<(Frame, f32, f32)> = None;
563 while let Some(p) = cur {
564 if let Some(s) = scrolls.iter().find(|s| s.index == p && ((s.scroll_y && s.content_height > frames[p as usize].h + 0.5) || (s.scroll_x && s.content_width > frames[p as usize].w + 0.5))) {
565 port = Some((frames[p as usize], s.offset_x, s.offset_y));
566 if Some(p) == parent_index {
570 parent = Frame { w: s.content_width.max(parent.w), h: s.content_height.max(parent.h), ..parent };
571 }
572 break;
573 }
574 cur = self.nodes[p as usize].parent;
575 }
576 let (pf, ox, oy) = port.unwrap_or((Frame { x: 0.0, y: 0.0, w: self.viewport.0, h: self.viewport.1 }, self.local.surface_scroll.unwrap_or_default().0, self.local.surface_scroll.unwrap_or_default().1));
577 let mut dy = 0.0f32;
581 if let Some(t) = top {
582 let edge = pf.y + oy + t;
583 if n.y < edge {
584 dy = (edge.min(parent.y + parent.h - n.h) - n.y).max(0.0);
585 }
586 } else if let (Some(b), true) = (bottom, pf.h > 0.0) {
587 let edge = pf.y + oy + pf.h - b - n.h;
588 if n.y > edge {
589 dy = (edge.max(parent.y) - n.y).min(0.0);
590 }
591 }
592 let mut dx = 0.0f32;
593 if let Some(l) = left {
594 let edge = pf.x + ox + l;
595 if n.x < edge {
596 dx = (edge.min(parent.x + parent.w - n.w) - n.x).max(0.0);
597 }
598 } else if let (Some(r), true) = (right, pf.w > 0.0) {
599 let edge = pf.x + ox + pf.w - r - n.w;
600 if n.x > edge {
601 dx = (edge.max(parent.x) - n.x).min(0.0);
602 }
603 }
604 if dx.abs() > 0.01 || dy.abs() > 0.01 {
605 out.push(super::StickyOutput { index: i, dx, dy });
606 }
607 }
608 out
609 }
610
611 fn output(&mut self, frames: Vec<Frame>, placed: Vec<Placed>, bounded_h: Option<f32>, rounds: u32, upcalls: u32, layout_ns: u64) -> LayoutOutput {
612 let mut main = Vec::new();
613 let mut drawn: Vec<u32> = Vec::new();
614 if let Some(root) = self.main_root {
615 self.preorder(root, &mut drawn);
616 }
617 for &i in &drawn {
618 let f = frames[i as usize];
619 main.push(PlacedFrame { index: i, x: f.x, y: f.y, w: f.w, h: f.h });
620 }
621 let mut layers = Vec::new();
622 for (spec, (placement, position, anchor_frame)) in self.layers.clone().into_iter().zip(placed) {
623 let mut order = Vec::new();
624 self.preorder(spec.root, &mut order);
625 let lf = order.iter().map(|&i| {
626 let f = frames[i as usize];
627 PlacedFrame { index: i, x: f.x, y: f.y, w: f.w, h: f.h }
628 });
629 let frames_out: Vec<PlacedFrame> = lf.collect();
630 drawn.extend(order);
631 layers.push(Layer { layer: spec.layer, kind: spec.kind.clone(), owner: spec.owner.clone(), root: spec.root, anchor_frame, placement, position, class: spec.class, modal: spec.modal, dismissible: spec.dismissible, frames: frames_out });
632 }
633 for &i in &drawn {
635 let s = &self.node_state[i as usize];
636 let Some(pct) = s.radius_pct else { continue };
637 let f = frames[i as usize];
638 let side = f.w.min(f.h);
639 let mut v = s.visual.clone();
640 if let Some(all) = pct.all {
641 v.border_radius = Some(all * side);
642 }
643 if pct.corners.iter().any(Option::is_some) {
644 let base = v.border_radius.unwrap_or(0.0);
645 let mut radii = v.corner_radii.unwrap_or([base; 4]);
646 for (k, c) in pct.corners.iter().enumerate() {
647 if let Some(c) = c {
648 radii[k] = c * side;
649 }
650 }
651 v.corner_radii = Some(radii);
652 }
653 if v != s.visual {
654 self.node_state[i as usize].visual = v;
655 self.visuals_dirty.insert(i);
656 }
657 }
658 let mut scrolls = Vec::new();
660 let mut reclamped = false;
661 for &i in &drawn {
662 let s = &self.node_state[i as usize];
663 let (sx, sy) = style::is_scroll_container(&s.flat);
664 if !sx && !sy {
665 continue;
666 }
667 let l = self.engine.layout(i);
668 let f = frames[i as usize];
669 let content_w = l.content_size.width.max(f.w);
670 let content_h = l.content_size.height.max(f.h);
671 let id = self.scroll_key(i);
672 let asked = self.local.scroll.get(&id).copied();
673 let (ox, oy) = asked.unwrap_or((0.0, 0.0));
674 let ox = if sx { ox.clamp(0.0, (content_w - f.w).max(0.0)) } else { 0.0 };
675 let oy = if sy { oy.clamp(0.0, (content_h - f.h).max(0.0)) } else { 0.0 };
676 let own_x = content_w > f.w + 0.5;
682 let own_y = content_h > f.h + 0.5;
683 if let Some((ax, ay)) = asked {
684 let (kx, ky) = (if own_x && sx { ox } else { ax }, if own_y && sy { oy } else { ay });
685 if (ax - kx).abs() > 0.01 || (ay - ky).abs() > 0.01 {
686 let (ox, oy) = (kx, ky);
687 self.local.scroll.insert(id.clone(), (ox, oy));
688 if let Some(w) = self.local.lists.get_mut(&id) {
689 w.scroll_offset = oy;
690 if (ay - oy).abs() > 0.01 && self.lists.iter().any(|l| l.id == id && l.windowed) {
691 self.subtree_pending.insert(id.clone());
692 reclamped = true;
693 }
694 }
695 }
696 }
697 scrolls.push(ScrollOutput { index: i, offset_x: ox, offset_y: oy, content_width: content_w, content_height: content_h, scroll_x: sx, scroll_y: sy });
698 }
699 let mut lists = Vec::new();
703 let mut rewindow = false;
704 for spec in self.lists.clone() {
705 let view = self.list_view(&spec, &frames, &scrolls);
706 self.local.list_views.insert(spec.id.clone(), view.clone());
707 let axis = |p: (f32, f32)| if spec.horizontal { p.0 } else { p.1 };
708 let scroll = match &view.source {
709 crate::layout_tree::ListViewSource::Own => self.local.scroll.get(&spec.id).copied().map(axis).unwrap_or(0.0),
710 crate::layout_tree::ListViewSource::Ancestor { id, rel } => self.local.scroll.get(id).copied().map(axis).unwrap_or(0.0) - rel,
711 crate::layout_tree::ListViewSource::Host { rel } => self.local.surface_scroll.map(|p| axis(p) - rel).unwrap_or_else(|| self.local.scroll.get(&spec.id).copied().map(axis).unwrap_or(0.0)),
712 }
713 .max(0.0);
714 let estimate = self.effective.as_ref().and_then(|t| t.tokens.control.get("row").copied()).map(|v| v as f32).unwrap_or(crate::list::DEFAULT_ESTIMATED_ITEM_HEIGHT);
715 let window = self.local.lists.entry(spec.id.clone()).or_insert_with(|| crate::list::ListWindow::new(estimate, crate::list::DEFAULT_OVERSCAN, spec.gap));
716 let mut changed = false;
717 for child in &self.nodes[spec.node as usize].children {
718 let c = &self.nodes[*child as usize];
719 if matches!(c.part.as_deref(), Some("divider" | "empty" | "spacer")) {
720 continue;
721 }
722 let key = c.props.get("key").and_then(serde_json::Value::as_str).filter(|_| matches!(c.part.as_deref(), Some("row" | "section"))).map(str::to_string).unwrap_or_else(|| c.id.clone());
723 let f = frames[*child as usize];
724 changed |= window.record(&key, if spec.horizontal { f.w } else { f.h });
725 }
726 if spec.windowed {
727 window.scroll_offset = scroll;
728 let offsets = window.offsets_cached(&spec.keys);
729 let range = window.visible_range(&offsets, view.viewport.max(1.0));
730 let count = spec.keys.len();
731 let pad = |o: &[f32], r: crate::list::VisibleRange| (o[r.start], o[count] - o[r.end.min(count)]);
732 let covered = range.start >= spec.range.start && range.end <= spec.range.end;
733 if (changed && (range != spec.range || pad(&offsets, range) != pad(&spec.offsets, spec.range))) || !covered {
737 self.subtree_pending.insert(spec.id.clone());
738 rewindow |= !covered;
739 }
740 }
741 let content_height = window.offsets_cached(&spec.keys).last().copied().unwrap_or(0.0);
742 lists.push(ListOutput { id: spec.id.clone(), node: spec.node, content_height, start: spec.range.start as u32, end: spec.range.end as u32, count: spec.keys.len() as u32, windowed: spec.windowed, horizontal: spec.horizontal });
743 }
744 if rewindow && !self.pending.iter().any(|e| matches!(e, super::OutEvent::Relayout)) {
745 self.pending.push(super::OutEvent::Relayout);
746 }
747 let sticky = self.sticky_offsets(&frames, &scrolls, &drawn);
748 self.local.sticky_any = drawn.iter().any(|&i| self.node_state[i as usize].flat.get("position").and_then(serde_json::Value::as_str) == Some("sticky"));
749 self.scrolls = scrolls.clone();
750 if reclamped && !self.pending.iter().any(|e| matches!(e, super::OutEvent::Relayout)) {
751 self.pending.push(super::OutEvent::Relayout);
753 }
754 let root_frame = self.main_root.map(|r| frames[r as usize]).unwrap_or_default();
755 self.last_frames = frames;
756 let mut visual_changes: Vec<u32> = self.visuals_dirty.drain().collect();
757 visual_changes.sort_unstable();
758 let delta = NodeDelta {
759 added: std::mem::take(&mut self.delta_added).into_iter().collect(),
760 removed: std::mem::take(&mut self.delta_removed).into_iter().collect(),
761 changed: std::mem::take(&mut self.delta_changed).into_iter().collect(),
762 renumbered: std::mem::take(&mut self.renumbered),
763 };
764 LayoutOutput {
765 frames: main,
766 layers,
767 lists,
768 scrolls,
769 sticky,
770 toasts: self.toasts.clone(),
771 visual_changes,
772 delta,
773 structure_version: self.structure_version,
774 surface_width: root_frame.w,
775 surface_height: root_frame.h,
776 overflow: bounded_h.is_some_and(|h| root_frame.h > h + 0.5),
777 direction: if self.direction == Direction::Rtl { "rtl".into() } else { "ltr".into() },
778 breakpoint: self.breakpoint.clone(),
779 measure_rounds: rounds,
780 upcalls,
781 restyled: std::mem::take(&mut self.restyled),
782 rebuilt: std::mem::take(&mut self.rebuilt),
783 built_nodes: std::mem::take(&mut self.built_nodes),
784 layout_ns,
785 }
786 }
787}