use std::rc::Rc;
day_reactive::tls_slots! {
layout;
static REPORTED: std::cell::RefCell<std::collections::HashSet<RNode>> =
std::cell::RefCell::new(std::collections::HashSet::new());
}
use day_spec::*;
use crate::tree::{Flex, RNode, Tree, TreeOps};
pub trait Layout: 'static {
fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size;
fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect);
fn baseline(&self, _cx: &mut dyn LayoutOps, _children: &[RNode], _size: Size) -> Option<f64> {
None
}
}
pub trait LayoutOps {
fn measure_child(&mut self, child: RNode, p: Proposal) -> Size;
fn place_child(&mut self, child: RNode, rect: Rect);
fn place_child_native(&mut self, child: RNode, rect: Rect) {
self.place_child(child, rect);
}
fn flex_of(&self, child: RNode) -> Flex;
fn children_of(&self, node: RNode) -> Vec<RNode>;
fn measure_leaf(&mut self, p: Proposal) -> Size;
fn baseline_of(&mut self, child: RNode, size: Size) -> Option<f64>;
fn set_scroll_content(&mut self, content: Size);
fn scroll_bar_inset(&mut self, _vertical: bool) -> f64 {
0.0
}
fn report_overflow(&mut self, _needed: f64, _available: f64) {}
}
pub struct EngineCx<'a, B: Toolkit> {
pub(crate) tree: &'a mut Tree<B>,
pub(crate) offset: Point,
pub(crate) current: RNode,
pub(crate) parent_size: Size,
}
impl<B: Toolkit> LayoutOps for EngineCx<'_, B> {
fn measure_child(&mut self, child: RNode, p: Proposal) -> Size {
measure_node(self.tree, child, p)
}
fn place_child(&mut self, child: RNode, rect: Rect) {
let rect = if crate::layout_direction() == day_geometry::LayoutDirection::Rtl {
Rect::new(
self.parent_size.width - rect.origin.x - rect.size.width,
rect.origin.y,
rect.size.width,
rect.size.height,
)
} else {
rect
};
place_node(self.tree, child, rect, self.offset, false);
}
fn place_child_native(&mut self, child: RNode, rect: Rect) {
place_node(self.tree, child, rect, self.offset, false);
}
fn flex_of(&self, child: RNode) -> Flex {
self.tree.node(child).map(|n| n.flex).unwrap_or_default()
}
fn children_of(&self, node: RNode) -> Vec<RNode> {
self.tree
.node(node)
.map(|n| n.children.clone())
.unwrap_or_default()
}
fn measure_leaf(&mut self, p: Proposal) -> Size {
let Some(n) = self.tree.node(self.current) else {
return Size::ZERO;
};
let kind = n.kind;
let Some(h) = n.handle.clone() else {
return Size::ZERO;
};
self.tree.toolkit.measure(&h, kind, p)
}
fn baseline_of(&mut self, child: RNode, size: Size) -> Option<f64> {
baseline_node(self.tree, child, size)
}
fn set_scroll_content(&mut self, content: Size) {
let current = self.current;
let Some(n) = self.tree.node_mut(current) else {
return;
};
n.scroll_content = Some(content);
let Some(h) = n.handle.clone() else { return };
self.tree.toolkit.set_scroll_content(&h, content);
}
fn scroll_bar_inset(&mut self, vertical: bool) -> f64 {
let Some(h) = self.tree.node(self.current).and_then(|n| n.handle.clone()) else {
return 0.0;
};
self.tree.toolkit.scroll_bar_inset(&h, vertical).max(0.0)
}
#[cfg(debug_assertions)]
fn report_overflow(&mut self, needed: f64, available: f64) {
if REPORTED.with(|r| !r.borrow_mut().insert(self.current)) {
return;
}
let mut ids: Vec<String> = Vec::new();
let mut stack = vec![self.current];
while let Some(node) = stack.pop() {
if ids.len() >= 6 {
break;
}
if let Some(n) = self.tree.node(node) {
ids.extend(n.id.clone());
stack.extend(n.children.iter().rev());
}
}
let who = if ids.is_empty() {
String::from("no .id in reach")
} else {
ids.join(", ")
};
log::warn!(
"day layout: children overflow their container by {:.0}pt ({needed:.0} needed, \
{available:.0} available; {who}) — give the row a fit policy: \
.fit(RowFit::Wrap/Scroll) or adaptive(..) (docs/size-classes.md)",
needed - available,
);
}
}
pub(crate) fn measure_node<B: Toolkit>(tree: &mut Tree<B>, node: RNode, p: Proposal) -> Size {
let key = p.cache_key();
let (layout, children) = {
let Some(n) = tree.node(node) else {
return Size::ZERO;
};
if !n.needs_measure
&& let Some(&(_, s)) = n.cache.iter().find(|(k, _)| *k == key)
{
return s;
}
(n.layout.clone(), n.children.clone())
};
let mut cx = EngineCx {
tree,
offset: Point::ZERO,
current: node,
parent_size: Size::ZERO, };
let size = layout.measure(&mut cx, &children, p);
if let Some(n) = tree.node_mut(node) {
n.needs_measure = false;
if n.cache.len() >= 4 {
n.cache.clear();
}
n.cache.push((key, size));
}
size
}
pub(crate) fn baseline_node<B: Toolkit>(
tree: &mut Tree<B>,
node: RNode,
size: Size,
) -> Option<f64> {
let key = Proposal::exact(size).cache_key();
let (layout, children, handle, kind) = {
let n = tree.node(node)?;
if !n.needs_measure
&& let Some((k, b)) = n.baseline_cache
&& k == key
{
return b;
}
(
n.layout.clone(),
n.children.clone(),
n.handle.clone(),
n.kind,
)
};
let baseline = match handle {
Some(h) if children.is_empty() => tree.toolkit.first_baseline(&h, kind, size),
_ => {
let mut cx = EngineCx {
tree,
offset: Point::ZERO,
current: node,
parent_size: Size::ZERO,
};
layout.baseline(&mut cx, &children, size)
}
};
if let Some(n) = tree.node_mut(node) {
n.baseline_cache = Some((key, baseline));
}
baseline
}
pub(crate) fn place_node<B: Toolkit>(
tree: &mut Tree<B>,
node: RNode,
rect: Rect,
offset: Point,
is_root: bool,
) {
let abs = Rect {
origin: rect.origin.offset(offset.x, offset.y),
size: rect.size,
};
let (layout, children, has_handle) = {
let Some(n) = tree.node(node) else { return };
(n.layout.clone(), n.children.clone(), n.handle.is_some())
};
let native_frame = tree
.node(node)
.map(|n| n.kind == day_spec::kinds::COVER)
.unwrap_or(false);
let child_offset = if has_handle {
if !is_root && !native_frame {
let changed = tree
.node(node)
.map(|n| {
n.last_native_frame
.map(|f| !f.approx_eq(&abs, 0.25))
.unwrap_or(true)
})
.unwrap_or(false);
if changed {
let h = tree.node(node).and_then(|n| n.handle.clone());
if let Some(h) = h {
let anim = tree.resolve_anim(node);
tree.toolkit.set_frame(&h, abs, anim.as_ref());
}
if tree
.node(node)
.map(|n| n.kind == day_spec::kinds::CANVAS)
.unwrap_or(false)
{
crate::tree::enqueue_event(
crate::tree::rnode_to_id(node),
day_spec::Event::FrameChanged(abs.size),
);
}
}
}
Point::ZERO
} else {
abs.origin
};
let relayout_cells = tree
.node(node)
.map(|n| {
n.kind == day_spec::kinds::LIST
&& n.last_native_frame
.map(|f| (f.size.width - abs.size.width).abs() > 0.25)
.unwrap_or(false)
})
.unwrap_or(false);
if let Some(n) = tree.node_mut(node) {
n.last_native_frame = Some(abs);
n.needs_layout = false;
}
if relayout_cells {
for key in tree.list_cell_keys(node) {
tree.list_layout_cell(node, key);
}
}
let mut cx = EngineCx {
tree,
offset: child_offset,
current: node,
parent_size: rect.size,
};
layout.place(&mut cx, &children, Rect::from_size(rect.size));
}
fn wrapper_baseline(
cx: &mut dyn LayoutOps,
children: &[RNode],
child_size: Size,
dy: f64,
) -> Option<f64> {
let &c = children.first()?;
Some(cx.baseline_of(c, child_size)? + dy)
}
pub struct PassThrough;
impl Layout for PassThrough {
fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
match children.first() {
Some(&c) => cx.measure_child(c, p),
None => Size::ZERO,
}
}
fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
if let Some(&c) = children.first() {
let s = cx.measure_child(c, Proposal::exact(bounds.size));
cx.place_child(c, Rect::from_size(s));
}
}
}
pub struct CellCenter;
impl Layout for CellCenter {
fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
match children.first() {
Some(&c) => cx.measure_child(c, p),
None => Size::ZERO,
}
}
fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
if let Some(&c) = children.first() {
let s = cx.measure_child(c, Proposal::exact(bounds.size));
let y = ((bounds.size.height - s.height) / 2.0).max(0.0);
cx.place_child(
c,
Rect::new(0.0, y, s.width, s.height.min(bounds.size.height)),
);
}
}
fn baseline(&self, cx: &mut dyn LayoutOps, children: &[RNode], size: Size) -> Option<f64> {
PassThrough::forward(cx, children, size)
}
}
pub struct CellMetrics(pub day_spec::RowMetrics);
impl CellMetrics {
fn for_child(&self, cx: &dyn LayoutOps, child: RNode) -> day_spec::RowMetrics {
let mut node = child;
for _ in 0..8 {
if cx.flex_of(node).whole_row {
let i = self.0.insets;
return day_spec::RowMetrics {
insets: day_spec::Insets {
top: 0.0,
leading: i.leading,
bottom: 0.0,
trailing: i.trailing,
},
min_height: 0.0,
};
}
match cx.children_of(node)[..] {
[only] => node = only,
_ => break,
}
}
self.0
}
}
impl Layout for CellMetrics {
fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
let Some(&c) = children.first() else {
return Size::ZERO;
};
let m = self.for_child(cx, c);
let i = m.insets;
let (dx, dy) = (i.leading + i.trailing, i.top + i.bottom);
let s = cx.measure_child(
c,
Proposal::new(
p.width.map(|w| (w - dx).max(0.0)),
p.height.map(|h| (h - dy).max(0.0)),
),
);
Size::new(s.width + dx, (s.height + dy).max(m.min_height))
}
fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
let Some(&c) = children.first() else {
return;
};
let i = self.for_child(cx, c).insets;
let inner = Size::new(
(bounds.size.width - i.leading - i.trailing).max(0.0),
(bounds.size.height - i.top - i.bottom).max(0.0),
);
let s = cx.measure_child(c, Proposal::new(Some(inner.width), None));
let (y, h) = if s.height <= inner.height {
(i.top + (inner.height - s.height) / 2.0, s.height)
} else {
let h = s.height.min(bounds.size.height);
(((bounds.size.height - h) / 2.0).max(0.0), h)
};
cx.place_child(c, Rect::new(i.leading, y, inner.width, h));
}
fn baseline(&self, cx: &mut dyn LayoutOps, children: &[RNode], size: Size) -> Option<f64> {
PassThrough::forward(cx, children, size)
}
}
impl PassThrough {
fn forward(cx: &mut dyn LayoutOps, children: &[RNode], size: Size) -> Option<f64> {
let &c = children.first()?;
let cs = cx.measure_child(c, Proposal::exact(size));
wrapper_baseline(cx, children, cs, 0.0)
}
}
pub struct FillThrough;
impl Layout for FillThrough {
fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
match children.first() {
Some(&c) => cx.measure_child(c, p),
None => Size::ZERO,
}
}
fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
if let Some(&c) = children.first() {
cx.place_child(c, Rect::from_size(bounds.size));
}
}
fn baseline(&self, cx: &mut dyn LayoutOps, children: &[RNode], size: Size) -> Option<f64> {
wrapper_baseline(cx, children, size, 0.0)
}
}
pub struct LeafLayout;
impl Layout for LeafLayout {
fn measure(&self, cx: &mut dyn LayoutOps, _children: &[RNode], p: Proposal) -> Size {
cx.measure_leaf(p)
}
fn place(&self, _cx: &mut dyn LayoutOps, _children: &[RNode], _bounds: Rect) {}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Axis {
Vertical,
Horizontal,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum CrossAlign {
Leading,
#[default]
Center,
Trailing,
FirstBaseline,
}
impl CrossAlign {
fn fraction(self) -> f64 {
match self {
CrossAlign::Leading => 0.0,
CrossAlign::Center | CrossAlign::FirstBaseline => 0.5,
CrossAlign::Trailing => 1.0,
}
}
}
pub struct StackLayout {
pub axis: Axis,
pub spacing: f64,
pub align: CrossAlign,
}
impl StackLayout {
fn main(&self, s: Size) -> f64 {
match self.axis {
Axis::Vertical => s.height,
Axis::Horizontal => s.width,
}
}
fn cross(&self, s: Size) -> f64 {
match self.axis {
Axis::Vertical => s.width,
Axis::Horizontal => s.height,
}
}
fn mk(&self, main: f64, cross: f64) -> Size {
match self.axis {
Axis::Vertical => Size::new(cross, main),
Axis::Horizontal => Size::new(main, cross),
}
}
fn split(&self, p: Proposal) -> (Option<f64>, Option<f64>) {
match self.axis {
Axis::Vertical => (p.height, p.width),
Axis::Horizontal => (p.width, p.height),
}
}
fn proposal(&self, main: Option<f64>, cross: Option<f64>) -> Proposal {
match self.axis {
Axis::Vertical => Proposal::new(cross, main),
Axis::Horizontal => Proposal::new(main, cross),
}
}
fn grows_main(&self, f: Flex) -> bool {
f.is_spacer
|| match self.axis {
Axis::Vertical => f.grow_h,
Axis::Horizontal => f.grow_w,
}
}
fn yields_main(&self, f: Flex) -> bool {
self.axis == Axis::Vertical && f.yields_h && !self.grows_main(f)
}
fn grows_cross(&self, f: Flex) -> bool {
match self.axis {
Axis::Vertical => f.grow_w,
Axis::Horizontal => f.grow_h,
}
}
fn flatten(cx: &mut dyn LayoutOps, children: &[RNode], out: &mut Vec<RNode>) {
for &c in children {
if cx.flex_of(c).is_group {
let inner = cx.children_of(c);
Self::flatten(cx, &inner, out);
} else {
out.push(c);
}
}
}
fn negotiate(&self, cx: &mut dyn LayoutOps, kids: &[RNode], p: Proposal) -> Vec<Size> {
let (main_p, cross_p) = self.split(p);
let mut sizes = vec![Size::ZERO; kids.len()];
let mut flex_idx = Vec::new();
let mut yield_idx = Vec::new();
let mut rigid_main = 0.0;
for (i, &k) in kids.iter().enumerate() {
let f = cx.flex_of(k);
if self.grows_main(f) {
flex_idx.push(i);
} else if self.yields_main(f) {
yield_idx.push(i);
} else {
let s = cx.measure_child(k, self.proposal(None, cross_p));
rigid_main += self.main(s);
sizes[i] = s;
}
}
if let Some(mp) = main_p {
let spacing_total = self.spacing * (kids.len().saturating_sub(1)) as f64;
let available = (mp - spacing_total).max(0.0);
if rigid_main > available {
let mut budget = available;
rigid_main = 0.0;
for (i, &k) in kids.iter().enumerate() {
let f = cx.flex_of(k);
if self.grows_main(f) || self.yields_main(f) {
continue;
}
if self.main(sizes[i]) > budget {
sizes[i] = cx.measure_child(k, self.proposal(Some(budget), cross_p));
}
let m = self.main(sizes[i]);
rigid_main += m;
budget = (budget - m).max(0.0);
}
}
}
for &i in &yield_idx {
let spacing_total = self.spacing * (kids.len().saturating_sub(1)) as f64;
let left = main_p.map(|mp| (mp - rigid_main - spacing_total).max(0.0));
sizes[i] = cx.measure_child(kids[i], self.proposal(left, cross_p));
rigid_main += self.main(sizes[i]);
}
if !flex_idx.is_empty() {
let spacing_total = self.spacing * (kids.len().saturating_sub(1)) as f64;
match main_p {
Some(mp) => {
let remaining = (mp - rigid_main - spacing_total).max(0.0);
let share = remaining / flex_idx.len() as f64;
for &i in &flex_idx {
let f = cx.flex_of(kids[i]);
sizes[i] = if f.is_spacer {
self.mk(share, 0.0)
} else {
cx.measure_child(kids[i], self.proposal(Some(share), cross_p))
};
}
}
None => {
for &i in &flex_idx {
let f = cx.flex_of(kids[i]);
sizes[i] = if f.is_spacer {
Size::ZERO
} else {
cx.measure_child(kids[i], self.proposal(None, cross_p))
};
}
}
}
}
sizes
}
fn baselines(
&self,
cx: &mut dyn LayoutOps,
kids: &[RNode],
sizes: &[Size],
) -> Vec<Option<f64>> {
if self.align != CrossAlign::FirstBaseline || self.axis != Axis::Horizontal {
return vec![None; kids.len()];
}
kids.iter()
.zip(sizes)
.map(|(&k, &s)| cx.baseline_of(k, s))
.collect()
}
fn baseline_cross_extent(&self, baselines: &[Option<f64>], sizes: &[Size]) -> Option<f64> {
let deepest = baselines.iter().flatten().copied().fold(f64::MIN, f64::max);
if deepest == f64::MIN {
return None;
}
let below = baselines
.iter()
.zip(sizes)
.filter_map(|(b, s)| b.map(|b| self.cross(*s) - b))
.fold(0.0, f64::max);
Some(deepest + below)
}
}
impl Layout for StackLayout {
fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
let mut kids = Vec::new();
Self::flatten(cx, children, &mut kids);
if kids.is_empty() {
return Size::ZERO;
}
let (main_p, cross_p) = self.split(p);
let sizes = self.negotiate(cx, &kids, p);
let spacing_total = self.spacing * (kids.len() - 1) as f64;
let has_flex = kids.iter().any(|&k| self.grows_main(cx.flex_of(k)));
let needed = sizes.iter().map(|&s| self.main(s)).sum::<f64>() + spacing_total;
let main_total = match main_p {
Some(mp) if has_flex => mp.max(needed),
_ => needed,
};
let grows_cross = kids.iter().any(|&k| self.grows_cross(cx.flex_of(k)));
let tallest = sizes.iter().map(|&s| self.cross(s)).fold(0.0, f64::max);
let stacked = self
.baseline_cross_extent(&self.baselines(cx, &kids, &sizes), &sizes)
.unwrap_or(0.0)
.max(tallest);
let cross_total = match cross_p {
Some(cp) if grows_cross => cp,
_ => stacked,
};
self.mk(main_total, cross_total)
}
fn baseline(&self, cx: &mut dyn LayoutOps, children: &[RNode], size: Size) -> Option<f64> {
let mut kids = Vec::new();
Self::flatten(cx, children, &mut kids);
if kids.is_empty() {
return None;
}
let sizes = self.negotiate(cx, &kids, Proposal::exact(size));
match self.axis {
Axis::Horizontal => {
let baselines: Vec<Option<f64>> = kids
.iter()
.zip(&sizes)
.map(|(&k, &s)| cx.baseline_of(k, s))
.collect();
let deepest = baselines.iter().flatten().copied().fold(f64::MIN, f64::max);
(deepest != f64::MIN).then_some(deepest)
}
Axis::Vertical => {
let mut pos = 0.0;
for (&k, &s) in kids.iter().zip(&sizes) {
if let Some(b) = cx.baseline_of(k, s) {
return Some(pos + b);
}
pos += self.main(s) + self.spacing;
}
None
}
}
}
fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
let mut kids = Vec::new();
Self::flatten(cx, children, &mut kids);
if kids.is_empty() {
return;
}
let sizes = self.negotiate(cx, &kids, Proposal::exact(bounds.size));
let needed = sizes.iter().map(|&s| self.main(s)).sum::<f64>()
+ self.spacing * (kids.len() - 1) as f64;
let available = self.main(bounds.size);
if available > 0.5 && needed > available + 0.5 {
cx.report_overflow(needed, available);
}
let bounds_cross = self.cross(bounds.size);
let baselines = self.baselines(cx, &kids, &sizes);
let deepest = baselines.iter().flatten().copied().fold(f64::MIN, f64::max);
let mut pos = 0.0;
for (i, &k) in kids.iter().enumerate() {
let s = sizes[i];
let cross_off = match (self.align, baselines[i]) {
(CrossAlign::FirstBaseline, Some(b)) => deepest - b,
(CrossAlign::Leading, _) => 0.0,
(CrossAlign::Center | CrossAlign::FirstBaseline, _) => {
((bounds_cross - self.cross(s)) / 2.0).max(0.0)
}
(CrossAlign::Trailing, _) => (bounds_cross - self.cross(s)).max(0.0),
};
let rect = match self.axis {
Axis::Vertical => Rect::new(cross_off, pos, s.width, s.height),
Axis::Horizontal => Rect::new(pos, cross_off, s.width, s.height),
};
cx.place_child(k, rect);
pos += self.main(s) + self.spacing;
}
}
}
pub struct FlowLayout {
pub spacing: f64,
pub run_spacing: f64,
pub align: CrossAlign,
pub uniform: bool,
}
struct FlowLine {
start: usize,
end: usize,
height: f64,
}
struct FlowPlan {
sizes: Vec<Size>,
lines: Vec<FlowLine>,
}
impl FlowLayout {
fn pack(&self, sizes: &[Size], max_w: Option<f64>) -> Vec<FlowLine> {
let mut lines = Vec::new();
let mut start = 0;
let mut w = 0.0;
let mut h: f64 = 0.0;
for (i, s) in sizes.iter().enumerate() {
let grown = if i == start {
s.width
} else {
w + self.spacing + s.width
};
if i > start && max_w.is_some_and(|m| grown > m + 0.5) {
lines.push(FlowLine {
start,
end: i,
height: h,
});
start = i;
w = s.width;
h = s.height;
} else {
w = grown;
h = h.max(s.height);
}
}
if start < sizes.len() {
lines.push(FlowLine {
start,
end: sizes.len(),
height: h,
});
}
lines
}
fn natural_sizes(cx: &mut dyn LayoutOps, kids: &[RNode]) -> Vec<Size> {
kids.iter()
.map(|&k| cx.measure_child(k, Proposal::UNCONSTRAINED))
.collect()
}
fn plan(&self, cx: &mut dyn LayoutOps, kids: &[RNode], max_w: Option<f64>) -> FlowPlan {
let natural = Self::natural_sizes(cx, kids);
if !self.uniform {
let lines = self.pack(&natural, max_w);
return FlowPlan {
sizes: natural,
lines,
};
}
let cell_w = natural.iter().map(|s| s.width).fold(0.0, f64::max);
let cols = match max_w {
Some(m) if cell_w > 0.0 => {
(((m + self.spacing) / (cell_w + self.spacing)).floor() as usize).max(1)
}
_ => kids.len().max(1),
};
let col_w = match max_w {
Some(m) if kids.iter().any(|&k| cx.flex_of(k).grow_w) => {
((m - self.spacing * (cols - 1) as f64) / cols as f64).max(cell_w)
}
_ => cell_w,
};
let col_w = max_w.map_or(col_w, |m| col_w.min(m));
let sizes: Vec<Size> = kids
.iter()
.map(|&k| {
let s = cx.measure_child(k, Proposal::new(Some(col_w), None));
Size::new(col_w, s.height)
})
.collect();
let mut lines = Vec::new();
let mut start = 0;
while start < sizes.len() {
let end = (start + cols).min(sizes.len());
let height = sizes[start..end]
.iter()
.map(|s| s.height)
.fold(0.0, f64::max);
lines.push(FlowLine { start, end, height });
start = end;
}
FlowPlan { sizes, lines }
}
}
impl Layout for FlowLayout {
fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
let mut kids = Vec::new();
StackLayout::flatten(cx, children, &mut kids);
if kids.is_empty() {
return Size::ZERO;
}
let plan = self.plan(cx, &kids, p.width);
let height = plan.lines.iter().map(|l| l.height).sum::<f64>()
+ self.run_spacing * plan.lines.len().saturating_sub(1) as f64;
let width = p.width.unwrap_or_else(|| {
plan.sizes.iter().map(|s| s.width).sum::<f64>()
+ self.spacing * plan.sizes.len().saturating_sub(1) as f64
});
Size::new(width, height)
}
fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
let mut kids = Vec::new();
StackLayout::flatten(cx, children, &mut kids);
if kids.is_empty() {
return;
}
let plan = self.plan(cx, &kids, Some(bounds.size.width));
let mut y = 0.0;
for line in &plan.lines {
let mut x = 0.0;
let range = line.start..line.end;
for (&k, &s) in kids[range.clone()].iter().zip(&plan.sizes[range]) {
let dy = ((line.height - s.height) * self.align.fraction()).max(0.0);
cx.place_child(k, Rect::new(x, y + dy, s.width, s.height));
x += s.width + self.spacing;
}
y += line.height + self.run_spacing;
}
}
}
struct GridCell {
node: RNode,
col: usize,
span: usize,
flex: Flex,
}
struct GridRowRef {
cells: Vec<GridCell>,
full_width: bool,
valign: Option<CrossAlign>,
}
struct GridGeom {
rows: Vec<GridRowRef>,
col_x: Vec<f64>,
col_w: Vec<f64>,
row_y: Vec<f64>,
row_h: Vec<f64>,
cell_sizes: Vec<Vec<Size>>,
size: Size,
}
pub struct GridLayout {
pub row_spacing: f64,
pub column_spacing: f64,
pub align: Alignment,
}
impl GridLayout {
fn collect(&self, cx: &mut dyn LayoutOps, children: &[RNode]) -> (Vec<GridRowRef>, usize) {
let mut tops = Vec::new();
StackLayout::flatten(cx, children, &mut tops);
let mut rows = Vec::new();
let mut ncols = 0usize;
for &t in &tops {
let f = cx.flex_of(t);
if f.grid.is_row {
let inner = cx.children_of(t);
let mut cell_nodes = Vec::new();
StackLayout::flatten(cx, &inner, &mut cell_nodes);
let mut cells = Vec::new();
let mut col = 0usize;
for &c in &cell_nodes {
let cf = cx.flex_of(c);
let span = cf.grid.col_span.max(1) as usize;
cells.push(GridCell {
node: c,
col,
span,
flex: cf,
});
col += span;
}
ncols = ncols.max(col);
rows.push(GridRowRef {
cells,
full_width: false,
valign: f.grid.row_valign,
});
} else {
rows.push(GridRowRef {
cells: vec![GridCell {
node: t,
col: 0,
span: 1,
flex: f,
}],
full_width: true,
valign: None,
});
}
}
(rows, ncols)
}
fn span_width(&self, col_w: &[f64], col: usize, span: usize) -> f64 {
let end = (col + span).min(col_w.len());
let cols: f64 = col_w[col..end].iter().sum();
cols + self.column_spacing * (end.saturating_sub(col + 1)) as f64
}
fn geometry(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> GridGeom {
let (mut rows, ncols) = self.collect(cx, children);
if rows.is_empty() {
return GridGeom {
rows,
col_x: Vec::new(),
col_w: Vec::new(),
row_y: Vec::new(),
row_h: Vec::new(),
cell_sizes: Vec::new(),
size: Size::ZERO,
};
}
let ncols = ncols.max(1);
for r in &mut rows {
if r.full_width {
r.cells[0].span = ncols;
}
}
let unconstrained = Proposal::new(None, None);
let mut col_ideal = vec![0.0f64; ncols];
let mut col_flex = vec![false; ncols];
let mut flex_ideal = vec![0.0f64; ncols];
for r in rows.iter().filter(|r| !r.full_width) {
for c in r.cells.iter().filter(|c| c.span == 1 && !c.flex.is_spacer) {
if c.flex.grow_w {
col_flex[c.col] = true;
if p.width.is_none() {
let s = cx.measure_child(c.node, unconstrained);
flex_ideal[c.col] = flex_ideal[c.col].max(s.width);
}
} else {
let s = cx.measure_child(c.node, unconstrained);
col_ideal[c.col] = col_ideal[c.col].max(s.width);
}
}
}
for r in rows.iter().filter(|r| !r.full_width) {
for c in r.cells.iter().filter(|c| c.span > 1 && !c.flex.is_spacer) {
if c.flex.grow_w {
let end = (c.col + c.span).min(ncols);
col_flex[c.col..end].fill(true);
}
}
}
for r in rows.iter().filter(|r| !r.full_width) {
for c in r.cells.iter().filter(|c| c.span > 1 && !c.flex.is_spacer) {
if c.flex.grow_w {
continue;
}
let s = cx.measure_child(c.node, unconstrained);
let end = (c.col + c.span).min(ncols);
let avail = self.span_width(&col_ideal, c.col, c.span);
let deficit = s.width - avail;
if deficit > 0.0 {
let flexed: Vec<usize> = (c.col..end).filter(|&k| col_flex[k]).collect();
let targets = if flexed.is_empty() {
(c.col..end).collect()
} else {
flexed
};
let add = deficit / targets.len() as f64;
for k in targets {
col_ideal[k] += add;
}
}
}
}
let mut fw_ideal = 0.0f64;
let mut fw_flex = false;
for r in rows.iter().filter(|r| r.full_width) {
let c = &r.cells[0];
if c.flex.is_spacer {
continue;
}
if c.flex.grow_w {
fw_flex = true;
} else {
let s = cx.measure_child(c.node, unconstrained);
fw_ideal = fw_ideal.max(s.width);
}
}
let gutters = self.column_spacing * (ncols - 1) as f64;
let has_flex_col = col_flex.iter().any(|&f| f);
let mut col_w = vec![0.0f64; ncols];
match p.width {
Some(pw) if has_flex_col => {
let rigid: f64 = (0..ncols)
.filter(|&k| !col_flex[k])
.map(|k| col_ideal[k])
.sum();
let share = (pw - rigid - gutters).max(0.0)
/ col_flex.iter().filter(|&&f| f).count() as f64;
for k in 0..ncols {
col_w[k] = if col_flex[k] { share } else { col_ideal[k] };
}
}
_ => {
for k in 0..ncols {
col_w[k] = if col_flex[k] {
flex_ideal[k].max(col_ideal[k])
} else {
col_ideal[k]
};
}
}
}
let cols_total: f64 = col_w.iter().sum::<f64>() + gutters;
let grid_w = match p.width {
Some(pw) if has_flex_col || fw_flex => pw,
_ => cols_total.max(fw_ideal),
};
let nrows = rows.len();
let mut cell_sizes: Vec<Vec<Size>> = Vec::with_capacity(nrows);
let mut row_h = vec![0.0f64; nrows];
let mut row_flex = vec![false; nrows];
for (ri, r) in rows.iter().enumerate() {
let mut sizes = Vec::with_capacity(r.cells.len());
for c in &r.cells {
if c.flex.is_spacer {
sizes.push(Size::ZERO);
continue;
}
let w = if r.full_width {
grid_w
} else {
self.span_width(&col_w, c.col, c.span)
};
let s = cx.measure_child(c.node, Proposal::new(Some(w), None));
row_h[ri] = row_h[ri].max(s.height);
if c.flex.grow_h {
row_flex[ri] = true;
}
sizes.push(s);
}
cell_sizes.push(sizes);
}
let vgutters = self.row_spacing * (nrows - 1) as f64;
if let Some(ph) = p.height
&& row_flex.iter().any(|&f| f)
{
let total: f64 = row_h.iter().sum::<f64>() + vgutters;
let extra = (ph - total).max(0.0) / row_flex.iter().filter(|&&f| f).count() as f64;
for (ri, flexed) in row_flex.iter().enumerate() {
if *flexed {
row_h[ri] += extra;
}
}
}
let grid_h = row_h.iter().sum::<f64>() + vgutters;
let mut col_x = vec![0.0f64; ncols];
let mut x = 0.0;
for k in 0..ncols {
col_x[k] = x;
x += col_w[k] + self.column_spacing;
}
let mut row_y = vec![0.0f64; nrows];
let mut y = 0.0;
for ri in 0..nrows {
row_y[ri] = y;
y += row_h[ri] + self.row_spacing;
}
GridGeom {
rows,
col_x,
col_w,
row_y,
row_h,
cell_sizes,
size: Size::new(grid_w, grid_h),
}
}
}
impl Layout for GridLayout {
fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
self.geometry(cx, children, p).size
}
fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
let g = self.geometry(cx, children, Proposal::exact(bounds.size));
for (ri, r) in g.rows.iter().enumerate() {
for (ci, c) in r.cells.iter().enumerate() {
if c.flex.is_spacer {
continue;
}
let cell = if r.full_width {
Rect::new(0.0, g.row_y[ri], g.size.width, g.row_h[ri])
} else {
Rect::new(
g.col_x[c.col],
g.row_y[ri],
self.span_width(&g.col_w, c.col, c.span),
g.row_h[ri],
)
};
let s = g.cell_sizes[ri][ci];
let w = if c.flex.grow_w {
cell.size.width
} else {
s.width.min(cell.size.width)
};
let h = if c.flex.grow_h {
cell.size.height
} else {
s.height.min(cell.size.height)
};
let hf = match c.flex.grid.align {
Some(a) => a.h_fraction(),
None => self.align.h_fraction(),
};
let vf = match (c.flex.grid.align, r.valign) {
(Some(a), _) => a.v_fraction(),
(None, Some(v)) => v.fraction(),
(None, None) => self.align.v_fraction(),
};
let x = cell.origin.x + (cell.size.width - w) * hf;
let y = cell.origin.y + (cell.size.height - h) * vf;
cx.place_child(c.node, Rect::new(x, y, w, h));
}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum Alignment {
TopLeading,
Top,
TopTrailing,
Leading,
#[default]
Center,
Trailing,
BottomLeading,
Bottom,
BottomTrailing,
}
impl Alignment {
fn h_fraction(self) -> f64 {
match self {
Alignment::TopLeading | Alignment::Leading | Alignment::BottomLeading => 0.0,
Alignment::Top | Alignment::Center | Alignment::Bottom => 0.5,
Alignment::TopTrailing | Alignment::Trailing | Alignment::BottomTrailing => 1.0,
}
}
fn v_fraction(self) -> f64 {
match self {
Alignment::TopLeading | Alignment::Top | Alignment::TopTrailing => 0.0,
Alignment::Leading | Alignment::Center | Alignment::Trailing => 0.5,
Alignment::BottomLeading | Alignment::Bottom | Alignment::BottomTrailing => 1.0,
}
}
}
pub struct OverlayLayout {
pub align: Alignment,
pub size_to_first: bool,
}
impl OverlayLayout {
fn flatten(cx: &mut dyn LayoutOps, children: &[RNode], out: &mut Vec<RNode>) {
for &c in children {
if cx.flex_of(c).is_group {
let inner = cx.children_of(c);
Self::flatten(cx, &inner, out);
} else {
out.push(c);
}
}
}
}
impl Layout for OverlayLayout {
fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
let mut kids = Vec::new();
Self::flatten(cx, children, &mut kids);
if self.size_to_first {
return match kids.first() {
Some(&c) => cx.measure_child(c, p),
None => Size::ZERO,
};
}
let mut size = Size::ZERO;
for &c in &kids {
let s = cx.measure_child(c, p);
size.width = size.width.max(s.width);
size.height = size.height.max(s.height);
}
size
}
fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
let mut kids = Vec::new();
Self::flatten(cx, children, &mut kids);
for &c in &kids {
let s = cx.measure_child(c, Proposal::exact(bounds.size));
let x = (bounds.size.width - s.width) * self.align.h_fraction();
let y = (bounds.size.height - s.height) * self.align.v_fraction();
cx.place_child(c, Rect::new(x, y, s.width, s.height));
}
}
}
pub struct PaddingLayout {
pub insets: Insets,
}
impl Layout for PaddingLayout {
fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
let inner = Proposal::new(
p.width
.map(|w| (w - self.insets.horizontal_total()).max(0.0)),
p.height
.map(|h| (h - self.insets.vertical_total()).max(0.0)),
);
let s = match children.first() {
Some(&c) => cx.measure_child(c, inner),
None => Size::ZERO,
};
Size::new(
s.width + self.insets.horizontal_total(),
s.height + self.insets.vertical_total(),
)
}
fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
if let Some(&c) = children.first() {
let inner = bounds.inset_by(self.insets);
let s = cx.measure_child(c, Proposal::exact(inner.size));
cx.place_child(
c,
Rect {
origin: inner.origin,
size: s,
},
);
}
}
fn baseline(&self, cx: &mut dyn LayoutOps, children: &[RNode], size: Size) -> Option<f64> {
let inner = Size::new(
(size.width - self.insets.horizontal_total()).max(0.0),
(size.height - self.insets.vertical_total()).max(0.0),
);
wrapper_baseline(cx, children, inner, self.insets.top)
}
}
pub struct AspectRatioLayout {
pub ratio: f64,
}
impl Layout for AspectRatioLayout {
fn measure(&self, cx: &mut dyn LayoutOps, _children: &[RNode], p: Proposal) -> Size {
match (p.width, p.height) {
(Some(w), Some(h)) => {
if w / h > self.ratio {
Size::new(h * self.ratio, h)
} else {
Size::new(w, w / self.ratio)
}
}
(Some(w), None) => Size::new(w, w / self.ratio),
(None, Some(h)) => Size::new(h * self.ratio, h),
(None, None) => cx.measure_leaf(p),
}
}
fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
for &c in children {
cx.place_child(c, Rect::from_size(bounds.size));
}
}
fn baseline(&self, cx: &mut dyn LayoutOps, children: &[RNode], size: Size) -> Option<f64> {
PassThrough::forward(cx, children, size)
}
}
pub struct GrowLayout {
pub w: bool,
pub h: bool,
}
impl Layout for GrowLayout {
fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
let cs = match children.first() {
Some(&c) => cx.measure_child(c, p),
None => Size::ZERO,
};
Size::new(
if self.w {
p.width.map_or(cs.width, |w| w.max(cs.width))
} else {
cs.width
},
if self.h {
p.height.map_or(cs.height, |h| h.max(cs.height))
} else {
cs.height
},
)
}
fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
if let Some(&c) = children.first() {
let cs = cx.measure_child(c, Proposal::exact(bounds.size));
let w = if self.w { bounds.size.width } else { cs.width };
let h = if self.h {
bounds.size.height
} else {
cs.height
};
cx.place_child(c, Rect::from_size(Size::new(w, h)));
}
}
fn baseline(&self, cx: &mut dyn LayoutOps, children: &[RNode], size: Size) -> Option<f64> {
PassThrough::forward(cx, children, size)
}
}
pub struct MaxWidthLayout {
pub max: f64,
}
impl Layout for MaxWidthLayout {
fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
let capped = Proposal::new(
Some(p.width.map(|w| w.min(self.max)).unwrap_or(self.max)),
p.height,
);
match children.first() {
Some(&c) => {
let s = cx.measure_child(c, capped);
Size::new(s.width.min(self.max), s.height)
}
None => Size::ZERO,
}
}
fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
if let Some(&c) = children.first() {
let w = bounds.size.width.min(self.max);
let s = cx.measure_child(c, Proposal::exact(Size::new(w, bounds.size.height)));
cx.place_child(c, Rect::from_size(Size::new(s.width.min(w), s.height)));
}
}
fn baseline(&self, cx: &mut dyn LayoutOps, children: &[RNode], size: Size) -> Option<f64> {
PassThrough::forward(cx, children, size)
}
}
pub struct MaxHeightLayout {
pub max: f64,
}
impl Layout for MaxHeightLayout {
fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
let capped = Proposal::new(
p.width,
Some(p.height.map(|h| h.min(self.max)).unwrap_or(self.max)),
);
match children.first() {
Some(&c) => {
let s = cx.measure_child(c, capped);
Size::new(s.width, s.height.min(self.max))
}
None => Size::ZERO,
}
}
fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
if let Some(&c) = children.first() {
let h = bounds.size.height.min(self.max);
let s = cx.measure_child(c, Proposal::exact(Size::new(bounds.size.width, h)));
cx.place_child(c, Rect::from_size(Size::new(s.width, s.height.min(h))));
}
}
fn baseline(&self, cx: &mut dyn LayoutOps, children: &[RNode], size: Size) -> Option<f64> {
PassThrough::forward(cx, children, size)
}
}
pub struct MinWidthLayout {
pub min: f64,
}
impl Layout for MinWidthLayout {
fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
let floored = Proposal::new(p.width.map(|w| w.max(self.min)), p.height);
match children.first() {
Some(&c) => {
let s = cx.measure_child(c, floored);
Size::new(s.width.max(self.min), s.height)
}
None => Size::new(self.min, 0.0),
}
}
fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
if let Some(&c) = children.first() {
let w = bounds.size.width.max(self.min);
let s = cx.measure_child(c, Proposal::exact(Size::new(w, bounds.size.height)));
cx.place_child(c, Rect::from_size(Size::new(w, s.height)));
}
}
fn baseline(&self, cx: &mut dyn LayoutOps, children: &[RNode], size: Size) -> Option<f64> {
PassThrough::forward(cx, children, size)
}
}
pub struct ReserveLayout;
impl Layout for ReserveLayout {
fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
let (Some(&sample), Some(&content)) = (children.first(), children.get(1)) else {
return children
.first()
.map(|&c| cx.measure_child(c, p))
.unwrap_or(Size::ZERO);
};
let s = cx.measure_child(sample, p);
let c = cx.measure_child(content, p);
Size::new(s.width.max(c.width), s.height.max(c.height))
}
fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
if let Some(&sample) = children.first() {
cx.place_child(sample, Rect::from_size(Size::ZERO));
}
if let Some(&content) = children.get(1) {
let s = cx.measure_child(content, Proposal::exact(bounds.size));
let _ = s;
cx.place_child(content, Rect::from_size(bounds.size));
}
}
}
pub struct FrameLayout {
pub width: Option<f64>,
pub height: Option<f64>,
}
impl Layout for FrameLayout {
fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
let child_p = Proposal::new(self.width.or(p.width), self.height.or(p.height));
let s = match children.first() {
Some(&c) => cx.measure_child(c, child_p),
None => Size::ZERO,
};
Size::new(
self.width.unwrap_or(s.width),
self.height.unwrap_or(s.height),
)
}
fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
if let Some(&c) = children.first() {
cx.place_child(c, bounds);
}
}
fn baseline(&self, cx: &mut dyn LayoutOps, children: &[RNode], size: Size) -> Option<f64> {
PassThrough::forward(cx, children, size)
}
}
pub struct ScrollLayout {
pub axis: Axis,
}
impl Layout for ScrollLayout {
fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
let content_p = match self.axis {
Axis::Vertical => Proposal::new(p.width, None),
Axis::Horizontal => Proposal::new(None, p.height),
};
let cs = match children.first() {
Some(&c) => cx.measure_child(c, content_p),
None => Size::ZERO,
};
Size::new(p.width.unwrap_or(cs.width), p.height.unwrap_or(cs.height))
}
fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
if let Some(&c) = children.first() {
let vertical = self.axis == Axis::Vertical;
let proposal = |cross: f64| match self.axis {
Axis::Vertical => Proposal::new(Some(cross), None),
Axis::Horizontal => Proposal::new(None, Some(cross)),
};
let (cross, main) = match self.axis {
Axis::Vertical => (bounds.size.width, bounds.size.height),
Axis::Horizontal => (bounds.size.height, bounds.size.width),
};
let mut cs = cx.measure_child(c, proposal(cross));
let overflows = |s: Size| if vertical { s.height } else { s.width } > main;
let mut cross = cross;
if overflows(cs) {
let inset = cx.scroll_bar_inset(vertical);
if inset > 0.0 {
cross = (cross - inset).max(0.0);
cs = cx.measure_child(c, proposal(cross));
}
}
let content = match self.axis {
Axis::Vertical => Size::new(cross, cs.height.max(bounds.size.height)),
Axis::Horizontal => Size::new(cs.width.max(bounds.size.width), cross),
};
cx.place_child(c, Rect::from_size(content));
cx.set_scroll_content(content);
}
}
}
pub struct NavLayout {
pub sizes: std::rc::Rc<std::cell::RefCell<std::collections::HashMap<RNode, Size>>>,
pub presentation: std::rc::Rc<std::cell::Cell<day_spec::props::NavPresentation>>,
pub sidebar: std::rc::Rc<std::cell::Cell<Option<RNode>>>,
pub list: std::rc::Rc<std::cell::Cell<Option<RNode>>>,
pub list_width: f64,
pub list_visible: std::rc::Rc<std::cell::Cell<bool>>,
}
pub use day_spec::{NAV_LIST_WIDTH, NAV_SIDEBAR_WIDTH};
impl NavLayout {
pub fn stack(
sizes: std::rc::Rc<std::cell::RefCell<std::collections::HashMap<RNode, Size>>>,
) -> Self {
NavLayout {
sizes,
presentation: std::rc::Rc::new(std::cell::Cell::new(
day_spec::props::NavPresentation::Stack,
)),
sidebar: std::rc::Rc::new(std::cell::Cell::new(None)),
list: std::rc::Rc::new(std::cell::Cell::new(None)),
list_width: NAV_LIST_WIDTH,
list_visible: std::rc::Rc::new(std::cell::Cell::new(true)),
}
}
}
impl Layout for NavLayout {
fn measure(&self, _cx: &mut dyn LayoutOps, _children: &[RNode], p: Proposal) -> Size {
Size::new(p.width.unwrap_or(480.0), p.height.unwrap_or(640.0))
}
fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
let pres = self.presentation.get();
let split = pres.is_split();
let sidebar = self.sidebar.get();
let list = self.list.get();
let list_w = if split && list.is_some() && self.list_visible.get() {
self.list_width
} else {
0.0
};
for &page in children {
let reported = self.sizes.borrow().get(&page).copied();
let sz = reported.unwrap_or_else(|| {
let is_sidebar = Some(page) == sidebar;
let is_list = Some(page) == list;
if pres.rows_are_chrome() {
if is_sidebar {
Size::new(NAV_SIDEBAR_WIDTH, bounds.size.height)
} else if is_list {
Size::new(self.list_width, bounds.size.height)
} else {
bounds.size
}
} else if !split {
bounds.size
} else if is_sidebar {
Size::new(NAV_SIDEBAR_WIDTH, bounds.size.height)
} else if is_list {
Size::new(self.list_width, bounds.size.height)
} else {
Size::new(
(bounds.size.width - NAV_SIDEBAR_WIDTH - list_w - 1.0).max(0.0),
bounds.size.height,
)
}
});
cx.place_child_native(page, Rect::from_size(sz));
}
}
}
pub struct InspectorLayout {
pub sizes: std::rc::Rc<std::cell::RefCell<std::collections::HashMap<RNode, Size>>>,
pub visible: std::rc::Rc<std::cell::Cell<bool>>,
pub width: f64,
}
impl Layout for InspectorLayout {
fn measure(&self, _cx: &mut dyn LayoutOps, _children: &[RNode], p: Proposal) -> Size {
Size::new(p.width.unwrap_or(480.0), p.height.unwrap_or(640.0))
}
fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
for (i, &pane) in children.iter().enumerate() {
let reported = self.sizes.borrow().get(&pane).copied();
let sz = reported.unwrap_or_else(|| {
let panel_w = if self.visible.get() { self.width } else { 0.0 };
if i == 0 {
Size::new((bounds.size.width - panel_w).max(0.0), bounds.size.height)
} else {
Size::new(panel_w, bounds.size.height)
}
});
cx.place_child_native(pane, Rect::from_size(sz));
}
}
}
pub struct SplitLayout {
pub sizes: std::rc::Rc<std::cell::RefCell<std::collections::HashMap<RNode, Size>>>,
pub stacked: std::rc::Rc<std::cell::Cell<bool>>,
pub fraction: std::rc::Rc<std::cell::Cell<f64>>,
}
impl Layout for SplitLayout {
fn measure(&self, _cx: &mut dyn LayoutOps, _children: &[RNode], p: Proposal) -> Size {
Size::new(p.width.unwrap_or(480.0), p.height.unwrap_or(640.0))
}
fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
let f = self.fraction.get().clamp(0.0, 1.0);
let (w, h) = (bounds.size.width, bounds.size.height);
for (i, &pane) in children.iter().enumerate() {
let reported = self.sizes.borrow().get(&pane).copied();
let sz = reported.unwrap_or_else(|| {
let share = if i == 0 { f } else { 1.0 - f };
if self.stacked.get() {
Size::new(w, (h * share).floor())
} else {
Size::new((w * share).floor(), h)
}
});
cx.place_child_native(pane, Rect::from_size(sz));
}
}
}
pub struct CoverLayout {
pub size: std::rc::Rc<std::cell::RefCell<Option<Size>>>,
}
impl Layout for CoverLayout {
fn measure(&self, _cx: &mut dyn LayoutOps, _children: &[RNode], _p: Proposal) -> Size {
Size::new(0.0, 0.0)
}
fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], _bounds: Rect) {
let Some(sz) = *self.size.borrow() else {
return; };
for &child in children {
cx.place_child_native(child, Rect::from_size(sz));
}
}
}
pub fn rc_layout<L: Layout>(l: L) -> Rc<dyn Layout> {
Rc::new(l)
}