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 refits: u32,
40 missing: Vec<usize>,
41 heights: Vec<HeightRequest>,
42}
43
44#[derive(Debug, Clone, PartialEq)]
46pub struct LeafData {
47 pub index: u32,
48 pub id: String,
49 pub component: String,
50 pub part: Option<String>,
51 pub owner_component: Option<String>,
52 pub props: serde_json::Map<String, serde_json::Value>,
53 pub text_style: crate::measure::TextStyle,
54 pub control: crate::measure::ControlBox,
55 pub lines: Option<u32>,
56}
57
58impl LeafData {
59 pub fn request(&self) -> LeafRequest<'_> {
61 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 }
62 }
63}
64
65#[derive(Debug, Clone, PartialEq)]
67pub enum LayoutStep {
68 Intrinsics(Vec<LeafData>),
70 Heights(Vec<LeafData>, Vec<HeightRequest>),
72 Done(Box<LayoutOutput>),
74 Idle,
76}
77
78#[derive(Debug, Clone, Copy)]
80struct View {
81 x: f32,
82 y: f32,
83 w: f32,
84 h: f32,
85}
86
87impl Surface {
88 fn leaf_request(&self, i: usize) -> LeafRequest<'_> {
89 let n = &self.nodes[i];
90 let s = &self.node_state[i];
91 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 }
92 }
93
94 fn is_measured_leaf(&self, i: usize) -> bool {
95 self.live[i] && self.nodes[i].kind == NodeKind::Leaf && !self.nodes[i].hidden
96 }
97
98 fn prepare(&mut self, measurer: u64) {
100 self.ensure_reduced();
101 if self.last_measurer.is_some_and(|m| m != measurer) {
102 self.invalidate_measures();
103 }
104 self.last_measurer = Some(measurer);
105 if self.needs_build {
106 self.subtree_pending.clear();
107 self.rebuild();
108 } else {
109 for id in std::mem::take(&mut self.subtree_pending) {
110 if !self.rebuild_subtree(&id) {
111 self.rebuild();
112 self.subtree_pending.clear();
113 break;
114 }
115 }
116 }
117 self.restyle();
118 }
119
120 fn missing_leaves(&self) -> Vec<usize> {
122 (0..self.nodes.len())
123 .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)))
124 .collect()
125 }
126
127 fn apply_intrinsics(&mut self, missing: &[usize], answers: &[Intrinsics]) {
131 for (k, &i) in missing.iter().enumerate() {
132 if let Some(a) = answers.get(k) {
133 self.memo.intrinsics.insert(i as u32, a.sanitised());
134 self.memo_versions.insert(i as u32, self.node_state[i].content_version);
135 self.engine.mark_dirty(i as u32);
136 }
137 }
138 }
139
140 fn apply_heights(&mut self, requests: &[HeightRequest], heights: &[f32]) {
141 for (k, r) in requests.iter().enumerate() {
142 if let Some(h) = heights.get(k) {
143 self.memo.insert_height(r.index, MeasureMemo::width_key(r.width), crate::measure::finite_or_zero(*h));
144 self.engine.mark_dirty(r.index);
145 }
146 }
147 }
148
149 fn compute_all(&mut self) -> (Vec<Frame>, Vec<Placed>, Vec<HeightRequest>) {
152 crate::roomy(|| self.compute_all_now())
153 }
154
155 fn compute_all_now(&mut self) -> (Vec<Frame>, Vec<Placed>, Vec<HeightRequest>) {
156 let (vw, vh) = self.viewport;
157 let guesses = RefCell::new(Vec::<HeightRequest>::new());
158 self.dialog_holds_next.clear();
159 let mut frames = vec![Frame::default(); self.nodes.len()];
160 let mut placed = Vec::with_capacity(self.layers.len());
161 if let Some(root) = self.main_root {
162 let root_node = &self.nodes[root as usize];
167 let inline_root = matches!(root_node.component.as_str(), "Button" | "Toggle" | "Link" | "Icon" | "Ring" | "Spinner")
168 || (root_node.component == "Segmented" && (root_node.props.get("variant").and_then(|v| v.as_str()) != Some("bar") || crate::segmented::is_rail(&root_node.props)));
169 if !inline_root && self.engine.style(root).size.width == Dimension::auto() {
170 let mut style = self.engine.style(root).clone();
171 style.size.width = Dimension::percent(1.0);
172 self.engine.set_style(root, style);
173 }
174 let available = Size { width: AvailableSpace::Definite(vw.max(0.0)), height: if vh > 0.0 { AvailableSpace::Definite(vh) } else { AvailableSpace::MaxContent } };
175 self.compute(root, available, &guesses);
176 self.walk_frames(root, 0.0, 0.0, &mut frames);
177 }
178 let main_h = self.main_root.map(|r| frames[r as usize].h).unwrap_or(0.0);
179 let vh_eff = if vh > 0.0 { vh } else { self.max_height.unwrap_or(main_h) };
180 let ins = self.settings.insets;
181 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) };
182 let mut toast_stack = 0.0f32;
183 let mut popup_view = view;
186 for k in 0..self.layers.len() {
187 let popup = matches!(self.layers[k].placement, LayerPlacement::Anchored { .. } | LayerPlacement::AtPoint { .. });
188 let p = self.place_layer(k, if popup { popup_view } else { view }, vw, vh_eff, &mut frames, &guesses, &mut toast_stack);
189 placed.push(p);
190 if self.layers[k].modal {
191 popup_view = match self.layers[k].placement {
192 LayerPlacement::Edge(OverlaySide::Top | OverlaySide::Bottom) => {
193 let (w, h) = self.sheet_viewport(vw, vh_eff);
194 View { x: ins.left, y: ins.top, w: (w - ins.left - ins.right).max(0.0), h: (h - ins.top - ins.bottom).max(0.0) }
195 }
196 _ => view,
197 };
198 }
199 }
200 let mut seen = HashSet::new();
201 let pending: Vec<HeightRequest> = guesses.into_inner().into_iter().filter(|r| seen.insert((r.index, MeasureMemo::width_key(r.width)))).collect();
202 (frames, placed, pending)
203 }
204
205 fn sheet_viewport(&self, vw: f32, vh: f32) -> (f32, f32) {
208 match self.layer_viewport {
209 Some((w, h)) => (w, if h > 0.0 { h } else { vh }),
210 None => (vw, vh),
211 }
212 }
213
214 fn finish(&mut self, frames: Vec<Frame>, placed: Vec<Placed>, state: PassState) -> LayoutOutput {
215 let (_, vh) = self.viewport;
216 let bounded_h = if vh > 0.0 { Some(vh) } else { self.max_height };
217 let layout_ns = state.started.elapsed().as_nanos() as u64;
218 self.dialog_holds = std::mem::take(&mut self.dialog_holds_next);
221 self.output(frames, placed, bounded_h, state.rounds, state.upcalls, layout_ns)
222 }
223
224 pub fn layout(&mut self, measure: &mut dyn Measure) -> LayoutOutput {
230 let mut state = PassState { started: Instant::now(), upcalls: 0, rounds: 0, refits: 0, missing: Vec::new(), heights: Vec::new() };
231 self.prepare(measure.measure_id());
232 loop {
233 state.rounds += 1;
234 let missing = self.missing_leaves();
235 if !missing.is_empty() {
236 let requests: Vec<LeafRequest> = missing.iter().map(|&i| self.leaf_request(i)).collect();
237 let answers = measure.measure_intrinsics(&requests);
238 state.upcalls += 1;
239 self.apply_intrinsics(&missing, &answers);
240 }
241 let (frames, placed, pending) = self.compute_all();
242 if pending.is_empty() && self.refit_tab_strips(&frames, &mut state) {
243 continue;
244 }
245 if pending.is_empty() || state.rounds >= 3 {
246 return self.finish(frames, placed, state);
247 }
248 let leaves: Vec<LeafRequest> = pending.iter().map(|r| self.leaf_request(r.index as usize)).collect();
249 let heights = measure.measure_heights(&leaves, &pending);
250 state.upcalls += 1;
251 self.apply_heights(&pending, &heights);
252 }
253 }
254
255 fn refit_tab_strips(&mut self, frames: &[Frame], state: &mut PassState) -> bool {
258 if state.refits >= 2 || !self.fit_tab_strips(frames) {
259 return false;
260 }
261 state.refits += 1;
262 state.rounds = state.rounds.saturating_sub(1);
263 for id in std::mem::take(&mut self.subtree_pending) {
264 if !self.rebuild_subtree(&id) {
265 self.rebuild();
266 self.subtree_pending.clear();
267 break;
268 }
269 }
270 self.restyle();
271 true
272 }
273
274 fn owned_leaves(&self, indices: impl Iterator<Item = usize>) -> Vec<LeafData> {
275 indices
276 .map(|i| {
277 let n = &self.nodes[i];
278 let s = &self.node_state[i];
279 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 }
280 })
281 .collect()
282 }
283
284 pub fn layout_begin(&mut self, measure_id: u64) -> LayoutStep {
290 self.prepare(measure_id);
291 self.step_state = Some(PassState { started: Instant::now(), upcalls: 0, rounds: 0, refits: 0, missing: Vec::new(), heights: Vec::new() });
292 self.step_round()
293 }
294
295 fn step_round(&mut self) -> LayoutStep {
296 let missing = self.missing_leaves();
297 let Some(state) = self.step_state.as_mut() else { return LayoutStep::Idle };
298 state.rounds += 1;
299 if !missing.is_empty() {
300 let leaves = self.owned_leaves(missing.iter().copied());
301 self.step_state.as_mut().expect("state").missing = missing;
302 return LayoutStep::Intrinsics(leaves);
303 }
304 self.step_compute()
305 }
306
307 fn step_compute(&mut self) -> LayoutStep {
308 let (frames, placed, pending) = self.compute_all();
309 let Some(mut state) = self.step_state.take() else { return LayoutStep::Idle };
310 if pending.is_empty() && self.refit_tab_strips(&frames, &mut state) {
311 self.step_state = Some(state);
312 return self.step_round();
313 }
314 if pending.is_empty() || state.rounds >= 3 {
315 return LayoutStep::Done(Box::new(self.finish(frames, placed, state)));
316 }
317 let leaves = self.owned_leaves(pending.iter().map(|r| r.index as usize));
318 state.heights = pending.clone();
319 self.step_state = Some(state);
320 LayoutStep::Heights(leaves, pending)
321 }
322
323 pub fn layout_abort(&mut self) {
326 self.step_state = None;
327 }
328
329 pub fn layout_in_progress(&self) -> bool {
331 self.step_state.is_some()
332 }
333
334 pub fn layout_intrinsics(&mut self, answers: &[Intrinsics]) -> LayoutStep {
336 let Some(state) = self.step_state.as_mut() else { return LayoutStep::Idle };
337 state.upcalls += 1;
338 let missing = std::mem::take(&mut state.missing);
339 self.apply_intrinsics(&missing, answers);
340 self.step_compute()
341 }
342
343 pub fn layout_heights(&mut self, heights: &[f32]) -> LayoutStep {
345 let Some(state) = self.step_state.as_mut() else { return LayoutStep::Idle };
346 state.upcalls += 1;
347 let requests = std::mem::take(&mut state.heights);
348 self.apply_heights(&requests, heights);
349 self.step_round()
350 }
351
352 fn compute(&mut self, root: u32, available: Size<AvailableSpace>, guesses: &RefCell<Vec<HeightRequest>>) {
354 let Surface { engine, memo, nodes, .. } = self;
355 let mut measure_fn = |index: u32, known: Size<Option<f32>>, avail: Size<AvailableSpace>| -> LeafAnswer {
356 if nodes[index as usize].hidden {
357 return LeafAnswer::default();
358 }
359 let req = MeasureRequest { index, known_width: known.width, known_height: known.height, available_width: avail.width, available_height: avail.height };
360 let (size, guessed, baseline) = memo.answer(&req);
361 if guessed {
362 guesses.borrow_mut().push(HeightRequest { index, width: size.width });
363 }
364 LeafAnswer { size, baseline }
365 };
366 engine.compute(root, available, &mut measure_fn);
367 }
368
369 fn walk_frames(&self, slot: u32, ox: f32, oy: f32, out: &mut [Frame]) {
370 let l = self.engine.layout(slot);
371 let x = ox + l.location.x;
372 let y = oy + l.location.y;
373 out[slot as usize] = Frame { x, y, w: l.size.width, h: l.size.height };
374 for &c in &self.nodes[slot as usize].children {
375 self.walk_frames(c, x, y, out);
376 }
377 }
378
379 fn layer_root_style(&mut self, root: u32, pad_extra: [f32; 4], edit: impl FnOnce(&mut Style)) {
383 let mut flat = self.node_state[root as usize].flat.clone();
384 if pad_extra.iter().any(|p| *p != 0.0) {
385 let base = style::sides_px(&flat, "padding", "paddingHorizontal", "paddingVertical", ["paddingTop", "paddingRight", "paddingBottom", "paddingLeft"]).unwrap_or([0.0; 4]);
386 for (k, key) in ["paddingTop", "paddingRight", "paddingBottom", "paddingLeft"].iter().enumerate() {
387 flat.insert((*key).to_string(), serde_json::json!(base[k] + pad_extra[k]));
388 }
389 }
390 let mut s = style::to_taffy(&flat, self.direction, BoxKind::Container).unwrap_or_default();
391 edit(&mut s);
392 self.engine.set_style(root, s);
393 }
394
395 #[allow(clippy::too_many_arguments)]
396 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 {
397 let spec = self.layers[k].clone();
398 let root = spec.root;
399 let pad = OVERLAY_PADDING as f32;
400 let rtl = self.direction == Direction::Rtl;
401 let ins = self.settings.insets;
402 let finite = vh.is_finite() && vh > 0.0;
403 let body_shrinks = spec.scroll_body.is_some();
404 let (placement, position, anchor_frame, x, y) = match spec.placement {
405 LayerPlacement::Centered => {
406 let id = self.nodes[root as usize].id.clone();
411 let centre = view.y + view.h / 2.0;
412 let full = (view.h - 2.0 * pad).max(0.0);
413 let held = if finite { self.dialog_holds.get(&id).copied() } else { None };
414 let max_h = finite.then(|| held.map_or(full, |h0| (view.h / 2.0 + h0 / 2.0 - pad).clamp(0.0, full)));
415 self.layer_root_style(root, [0.0; 4], |s| {
416 if let (Some(h), true) = (max_h, body_shrinks) {
417 s.max_size.height = length(h);
418 }
419 });
420 let width = (view.w - 2.0 * pad).clamp(0.0, DIALOG_MAX_WIDTH);
421 self.compute(root, Size { width: AvailableSpace::Definite(width), height: AvailableSpace::MaxContent }, guesses);
422 let size = self.engine.layout(root).size;
423 let x = view.x + ((view.w - size.width) / 2.0).max(pad);
424 let y = match (finite, held) {
425 (false, _) => view.y + pad,
426 (true, Some(h0)) => (centre + h0 / 2.0 - size.height).max(view.y + pad),
427 (true, None) => view.y + ((view.h - size.height) / 2.0).max(pad),
428 };
429 if finite {
430 self.dialog_holds_next.insert(id, held.unwrap_or(size.height.min(full)));
431 }
432 (None, "centered".to_string(), None, x, y)
433 }
434 LayerPlacement::Edge(side) => {
435 let vertical = matches!(side, OverlaySide::Top | OverlaySide::Bottom);
436 let extra = match side {
439 OverlaySide::Bottom => [0.0, ins.right, ins.bottom, ins.left],
440 OverlaySide::Top => [ins.top, ins.right, 0.0, ins.left],
441 OverlaySide::Left => [ins.top, 0.0, ins.bottom, ins.left],
442 OverlaySide::Right => [ins.top, ins.right, ins.bottom, 0.0],
443 };
444 let (sheet_w, sheet_h) = self.sheet_viewport(vw, vh);
445 let sheet_finite = sheet_h.is_finite() && sheet_h > 0.0;
446 self.layer_root_style(root, extra, |s| {
447 if vertical && sheet_finite && body_shrinks {
448 s.max_size.height = length((sheet_h * SHEET_MAX_HEIGHT).max(0.0));
449 }
450 });
451 let available = if vertical {
452 Size { width: AvailableSpace::Definite(sheet_w), height: AvailableSpace::MaxContent }
453 } else {
454 Size { width: AvailableSpace::Definite((vw - 2.0 * pad).clamp(0.0, DRAWER_MAX_WIDTH)), height: if finite { AvailableSpace::Definite(vh) } else { AvailableSpace::MaxContent } }
455 };
456 self.compute(root, available, guesses);
457 let size = self.engine.layout(root).size;
458 let (x, y) = match side {
459 OverlaySide::Top | OverlaySide::Left => (0.0, 0.0),
460 OverlaySide::Bottom => (0.0, if sheet_finite { sheet_h - size.height } else { 0.0 }),
461 OverlaySide::Right => (vw - size.width, 0.0),
462 };
463 (None, side.as_str().to_string(), None, x, y)
464 }
465 LayerPlacement::Anchored { anchor, side, align } => {
466 let a = frames.get(anchor as usize).copied().unwrap_or_default();
467 let side = if spec.mirror && rtl {
468 match side {
469 OverlaySide::Left => OverlaySide::Right,
470 OverlaySide::Right => OverlaySide::Left,
471 s => s,
472 }
473 } else {
474 side
475 };
476 let size = self.shrink_to_content(root, view, pad, spec.match_anchor_width.then_some(a.w), guesses);
477 let p = self.anchor_place(a, size, view, side, align, rtl);
478 let pos = p.side.as_str().to_string();
479 (Some(p), pos, Some(a), p.x as f32, p.y as f32)
480 }
481 LayerPlacement::AtPoint { x, y } => {
482 let a = Frame { x, y, w: 0.0, h: 0.0 };
483 let size = self.shrink_to_content(root, view, pad, None, guesses);
484 let p = self.anchor_place(a, size, view, OverlaySide::Bottom, OverlayAlign::Start, rtl);
485 (Some(p), "point".to_string(), Some(a), p.x as f32, p.y as f32)
486 }
487 LayerPlacement::Toast { .. } => {
488 let max = (view.w - 2.0 * pad).clamp(0.0, TOAST_MAX_WIDTH);
489 self.compute(root, Size { width: AvailableSpace::MaxContent, height: AvailableSpace::MaxContent }, guesses);
490 let natural = self.engine.layout(root).size.width;
491 if natural > max {
492 self.compute(root, Size { width: AvailableSpace::Definite(max), height: AvailableSpace::MaxContent }, guesses);
493 }
494 let size = self.engine.layout(root).size;
495 let wide = self.breakpoints().get("md").is_some_and(|md| vw >= *md);
496 let x = if wide {
497 if rtl {
498 view.x + pad
499 } else {
500 view.x + view.w - pad - size.width
501 }
502 } else {
503 view.x + (view.w - size.width) / 2.0
504 };
505 let y = view.y + view.h - pad - *toast_stack - size.height;
506 *toast_stack += size.height + TOAST_GAP;
507 (None, "toast".to_string(), None, x, y)
508 }
509 };
510 self.walk_frames(root, x, y, frames);
511 (placement, position, anchor_frame)
512 }
513
514 fn shrink_to_content(&mut self, root: u32, view: View, pad: f32, min_width: Option<f32>, guesses: &RefCell<Vec<HeightRequest>>) -> OSize {
517 self.layer_root_style(root, [0.0; 4], |s| {
518 if let Some(w) = min_width {
519 s.min_size.width = length(w);
520 }
521 });
522 let max = (view.w - 2.0 * pad).max(0.0);
523 self.compute(root, Size { width: AvailableSpace::MaxContent, height: AvailableSpace::MaxContent }, guesses);
524 let natural = self.engine.layout(root).size.width;
525 if natural > max {
526 self.compute(root, Size { width: AvailableSpace::Definite(max), height: AvailableSpace::MaxContent }, guesses);
527 }
528 let size = self.engine.layout(root).size;
529 OSize { width: size.width as f64, height: size.height as f64 }
530 }
531
532 fn anchor_place(&self, a: Frame, size: OSize, view: View, side: OverlaySide, align: OverlayAlign, rtl: bool) -> OverlayPlacement {
535 let align = match (align, rtl) {
536 (OverlayAlign::Start, true) => OverlayAlign::End,
537 (OverlayAlign::End, true) => OverlayAlign::Start,
538 (a, _) => a,
539 };
540 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 };
541 let vp = OSize { width: view.w as f64, height: if view.h > 0.0 { view.h as f64 } else { f64::INFINITY } };
542 let mut p = place_overlay(&anchor, &size, &vp, &PlaceOptions { side, align, ..PlaceOptions::default() });
543 p.x += view.x as f64;
544 p.y += view.y as f64;
545 p
546 }
547
548 fn preorder(&self, slot: u32, out: &mut Vec<u32>) {
549 out.push(slot);
550 for &c in &self.nodes[slot as usize].children {
551 self.preorder(c, out);
552 }
553 }
554
555 fn list_view(&self, spec: &crate::layout_tree::ListSpec, frames: &[Frame], scrolls: &[ScrollOutput]) -> crate::layout_tree::ListView {
559 use crate::layout_tree::{ListView, ListViewSource};
560 let h = spec.horizontal;
561 let ext = |f: Frame| if h { f.w } else { f.h };
562 let start = |f: Frame| if h { f.x } else { f.y };
563 let scrolls_on_axis = |idx: u32| {
564 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 })
565 };
566 let f = frames[spec.node as usize];
567 if scrolls_on_axis(spec.node) {
568 return ListView { source: ListViewSource::Own, viewport: ext(f) };
569 }
570 let mut cur = self.nodes[spec.node as usize].parent;
571 while let Some(p) = cur {
572 if scrolls_on_axis(p) {
573 let pf = frames[p as usize];
574 return ListView { source: ListViewSource::Ancestor { id: self.scroll_key(p), rel: start(f) - start(pf) }, viewport: ext(pf) };
575 }
576 cur = self.nodes[p as usize].parent;
577 }
578 let viewport = if h {
579 self.viewport.0
580 } else if self.viewport.1 > 0.0 {
581 self.viewport.1
582 } else {
583 self.max_height.unwrap_or(2000.0)
584 };
585 ListView { source: ListViewSource::Host { rel: start(f) }, viewport }
586 }
587
588 fn sticky_offsets(&mut self, frames: &[Frame], scrolls: &[ScrollOutput], drawn: &[u32]) -> Vec<super::StickyOutput> {
592 let mut out = Vec::new();
593 for spec in self.lists.iter().filter(|l| l.sticky && !l.headers.is_empty()) {
595 let Some(window) = self.local.lists.get_mut(&spec.id) else { continue };
596 let Some(view) = self.local.list_views.get(&spec.id) else { continue };
597 let axis = |p: (f32, f32)| if spec.horizontal { p.0 } else { p.1 };
598 let scroll = match &view.source {
599 crate::layout_tree::ListViewSource::Own => self.local.scroll.get(&spec.id).copied().map(axis).unwrap_or(0.0),
600 crate::layout_tree::ListViewSource::Ancestor { id, rel } => self.local.scroll.get(id).copied().map(axis).unwrap_or(0.0) - rel,
601 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)),
602 }
603 .max(0.0);
604 window.gap = spec.gap;
605 let Some((pin, row_offset)) = window.sticky_header_cached(&spec.keys, &spec.headers, scroll as f64) else { continue };
606 let key = &spec.keys[pin.row];
607 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 };
608 let delta = (pin.offset - row_offset) as f32;
609 let (dx, dy) = if spec.horizontal { (delta, 0.0) } else { (0.0, delta) };
612 if dx.abs() > 0.01 || dy.abs() > 0.01 {
613 out.push(super::StickyOutput { index: header, dx, dy });
614 }
615 }
616 for &i in drawn {
618 let flat = &self.node_state[i as usize].flat;
619 if flat.get("position").and_then(serde_json::Value::as_str) != Some("sticky") {
620 continue;
621 }
622 let all = flat.get("inset").and_then(style::px);
625 let inset = |k: &str| flat.get(k).and_then(style::px).or(all);
626 let (top, bottom, left, right) = (inset("top"), inset("bottom"), inset("left"), inset("right"));
627 let n = frames[i as usize];
628 let parent_index = self.nodes[i as usize].parent;
629 let mut parent = parent_index.map(|p| frames[p as usize]).unwrap_or(n);
630 let mut cur = parent_index;
633 let mut port: Option<(Frame, f32, f32)> = None;
634 while let Some(p) = cur {
635 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))) {
636 let pad = &self.node_state[p as usize].flat;
639 let side = |k: &str| pad.get(k).or_else(|| pad.get("borderWidth")).and_then(style::px).unwrap_or(0.0);
640 let (l, t, r, b) = (side("borderLeftWidth"), side("borderTopWidth"), side("borderRightWidth"), side("borderBottomWidth"));
641 let f = frames[p as usize];
642 port = Some((Frame { x: f.x + l, y: f.y + t, w: f.w - l - r, h: f.h - t - b }, s.offset_x, s.offset_y));
643 if Some(p) == parent_index {
647 parent = Frame { w: s.content_width.max(parent.w), h: s.content_height.max(parent.h), ..parent };
648 }
649 break;
650 }
651 cur = self.nodes[p as usize].parent;
652 }
653 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));
654 let mut dy = 0.0f32;
658 if let Some(t) = top {
659 let edge = pf.y + oy + t;
660 if n.y < edge {
661 dy = (edge.min(parent.y + parent.h - n.h) - n.y).max(0.0);
662 }
663 } else if let (Some(b), true) = (bottom, pf.h > 0.0) {
664 let edge = pf.y + oy + pf.h - b - n.h;
665 if n.y > edge {
666 dy = (edge.max(parent.y) - n.y).min(0.0);
667 }
668 }
669 let mut dx = 0.0f32;
670 if let Some(l) = left {
671 let edge = pf.x + ox + l;
672 if n.x < edge {
673 dx = (edge.min(parent.x + parent.w - n.w) - n.x).max(0.0);
674 }
675 } else if let (Some(r), true) = (right, pf.w > 0.0) {
676 let edge = pf.x + ox + pf.w - r - n.w;
677 if n.x > edge {
678 dx = (edge.max(parent.x) - n.x).min(0.0);
679 }
680 }
681 if dx.abs() > 0.01 || dy.abs() > 0.01 {
682 out.push(super::StickyOutput { index: i, dx, dy });
683 }
684 }
685 out
686 }
687
688 fn output(&mut self, frames: Vec<Frame>, placed: Vec<Placed>, bounded_h: Option<f32>, rounds: u32, upcalls: u32, layout_ns: u64) -> LayoutOutput {
689 let mut main = Vec::new();
690 let mut drawn: Vec<u32> = Vec::new();
691 if let Some(root) = self.main_root {
692 self.preorder(root, &mut drawn);
693 }
694 for &i in &drawn {
695 let f = frames[i as usize];
696 main.push(PlacedFrame { index: i, x: f.x, y: f.y, w: f.w, h: f.h });
697 }
698 let mut layers = Vec::new();
699 for (spec, (placement, position, anchor_frame)) in self.layers.clone().into_iter().zip(placed) {
700 let mut order = Vec::new();
701 self.preorder(spec.root, &mut order);
702 let lf = order.iter().map(|&i| {
703 let f = frames[i as usize];
704 PlacedFrame { index: i, x: f.x, y: f.y, w: f.w, h: f.h }
705 });
706 let frames_out: Vec<PlacedFrame> = lf.collect();
707 drawn.extend(order);
708 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 });
709 }
710 for &i in &drawn {
712 let s = &self.node_state[i as usize];
713 let Some(pct) = s.radius_pct else { continue };
714 let f = frames[i as usize];
715 let side = f.w.min(f.h);
716 let mut v = s.visual.clone();
717 if let Some(all) = pct.all {
718 v.border_radius = Some(all * side);
719 }
720 if pct.corners.iter().any(Option::is_some) {
721 let base = v.border_radius.unwrap_or(0.0);
722 let mut radii = v.corner_radii.unwrap_or([base; 4]);
723 for (k, c) in pct.corners.iter().enumerate() {
724 if let Some(c) = c {
725 radii[k] = c * side;
726 }
727 }
728 v.corner_radii = Some(radii);
729 }
730 if v != s.visual {
731 self.node_state[i as usize].visual = v;
732 self.visuals_dirty.insert(i);
733 }
734 }
735 let mut scrolls = Vec::new();
737 let mut reclamped = false;
738 for &i in &drawn {
739 let s = &self.node_state[i as usize];
740 let (sx, sy) = style::is_scroll_container(&s.flat);
741 if !sx && !sy {
742 continue;
743 }
744 let l = self.engine.layout(i);
745 let f = frames[i as usize];
746 let content_w = l.content_size.width.max(f.w);
747 let content_h = l.content_size.height.max(f.h);
748 let id = self.scroll_key(i);
749 let asked = self.local.scroll.get(&id).copied();
750 let (ox, oy) = asked.unwrap_or((0.0, 0.0));
751 let ox = if sx { ox.clamp(0.0, (content_w - f.w).max(0.0)) } else { 0.0 };
752 let oy = if sy { oy.clamp(0.0, (content_h - f.h).max(0.0)) } else { 0.0 };
753 let own_x = content_w > f.w + 0.5;
759 let own_y = content_h > f.h + 0.5;
760 if let Some((ax, ay)) = asked {
761 let (kx, ky) = (if own_x && sx { ox } else { ax }, if own_y && sy { oy } else { ay });
762 if (ax - kx).abs() > 0.01 || (ay - ky).abs() > 0.01 {
763 let (ox, oy) = (kx, ky);
764 self.local.scroll.insert(id.clone(), (ox, oy));
765 if let Some(w) = self.local.lists.get_mut(&id) {
766 w.scroll_offset = oy;
767 if (ay - oy).abs() > 0.01 && self.lists.iter().any(|l| l.id == id && l.windowed) {
768 self.subtree_pending.insert(id.clone());
769 reclamped = true;
770 }
771 }
772 }
773 }
774 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 });
775 }
776 let mut lists = Vec::new();
780 let mut rewindow = false;
781 for spec in self.lists.clone() {
782 let view = self.list_view(&spec, &frames, &scrolls);
783 self.local.list_views.insert(spec.id.clone(), view.clone());
784 let axis = |p: (f32, f32)| if spec.horizontal { p.0 } else { p.1 };
785 let scroll = match &view.source {
786 crate::layout_tree::ListViewSource::Own => self.local.scroll.get(&spec.id).copied().map(axis).unwrap_or(0.0),
787 crate::layout_tree::ListViewSource::Ancestor { id, rel } => self.local.scroll.get(id).copied().map(axis).unwrap_or(0.0) - rel,
788 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)),
789 }
790 .max(0.0);
791 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);
792 let window = self.local.lists.entry(spec.id.clone()).or_insert_with(|| crate::list::ListWindow::new(estimate, crate::list::DEFAULT_OVERSCAN, spec.gap));
793 let mut changed = false;
794 for child in &self.nodes[spec.node as usize].children {
795 let c = &self.nodes[*child as usize];
796 if matches!(c.part.as_deref(), Some("divider" | "empty" | "spacer")) {
797 continue;
798 }
799 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());
800 let f = frames[*child as usize];
801 changed |= window.record(&key, if spec.horizontal { f.w } else { f.h });
802 }
803 if spec.windowed {
804 window.scroll_offset = scroll;
805 let offsets = window.offsets_cached(&spec.keys);
806 let range = window.visible_range(&offsets, view.viewport.max(1.0));
807 let count = spec.keys.len();
808 let pad = |o: &[f32], r: crate::list::VisibleRange| (o[r.start], o[count] - o[r.end.min(count)]);
809 let covered = range.start >= spec.range.start && range.end <= spec.range.end;
810 if (changed && (range != spec.range || pad(&offsets, range) != pad(&spec.offsets, spec.range))) || !covered {
814 self.subtree_pending.insert(spec.id.clone());
815 rewindow |= !covered;
816 }
817 }
818 let content_height = window.offsets_cached(&spec.keys).last().copied().unwrap_or(0.0);
819 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 });
820 }
821 if rewindow && !self.pending.iter().any(|e| matches!(e, super::OutEvent::Relayout)) {
822 self.pending.push(super::OutEvent::Relayout);
823 }
824 let sticky = self.sticky_offsets(&frames, &scrolls, &drawn);
825 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"));
826 self.scrolls = scrolls.clone();
827 if reclamped && !self.pending.iter().any(|e| matches!(e, super::OutEvent::Relayout)) {
828 self.pending.push(super::OutEvent::Relayout);
830 }
831 let root_frame = self.main_root.map(|r| frames[r as usize]).unwrap_or_default();
832 self.last_frames = frames;
833 let mut visual_changes: Vec<u32> = self.visuals_dirty.drain().collect();
834 visual_changes.sort_unstable();
835 let delta = NodeDelta {
836 added: std::mem::take(&mut self.delta_added).into_iter().collect(),
837 removed: std::mem::take(&mut self.delta_removed).into_iter().collect(),
838 changed: std::mem::take(&mut self.delta_changed).into_iter().collect(),
839 renumbered: std::mem::take(&mut self.renumbered),
840 };
841 LayoutOutput {
842 frames: main,
843 layers,
844 lists,
845 scrolls,
846 sticky,
847 toasts: self.toasts.clone(),
848 visual_changes,
849 delta,
850 structure_version: self.structure_version,
851 surface_width: root_frame.w,
852 surface_height: root_frame.h,
853 overflow: bounded_h.is_some_and(|h| root_frame.h > h + 0.5),
854 direction: if self.direction == Direction::Rtl { "rtl".into() } else { "ltr".into() },
855 breakpoint: self.breakpoint.clone(),
856 measure_rounds: rounds,
857 upcalls,
858 restyled: std::mem::take(&mut self.restyled),
859 rebuilt: std::mem::take(&mut self.rebuilt),
860 built_nodes: std::mem::take(&mut self.built_nodes),
861 layout_ns,
862 }
863 }
864}