use std::any::Any;
use std::collections::VecDeque;
use std::rc::Rc;
use std::time::Instant;
use skia_safe::{Rect, Size};
use super::{LayoutProps, LayoutType, MeasureBudget, MeasuringStrategy, RecyclingTemplate, SkiaLayout, cell, cross_slot};
use crate::control::{Control, LayoutCx, part_mut};
use crate::controls::scroll::SkiaScroll;
use crate::layout::{self, fills_height};
use crate::tree::{Base, Build, ControlId, ControlProps, Cx, Detached, Mut, Tree, wrong_state};
use crate::types::{CacheType, Dirty, LayoutOptions, Thickness};
const RESERVE_TEMPLATES: usize = 2;
#[derive(Default)]
struct Sizes {
len: usize,
first_only: bool,
uniform: f32,
heights: Vec<f32>,
sums: Vec<f64>,
counts: Vec<u32>,
sum: f64,
measured: usize,
}
impl Sizes {
fn reset(&mut self, len: usize, first_only: bool) {
(self.len, self.first_only, self.uniform) = (len, first_only, f32::NAN);
self.heights.clear();
if !first_only {
self.heights.resize(len, f32::NAN);
}
self.rebuild();
}
fn insert(&mut self, index: usize, count: usize) {
self.len += count;
if !self.first_only {
self.heights.splice(index..index, std::iter::repeat_n(f32::NAN, count));
}
self.rebuild();
}
fn remove(&mut self, index: usize, count: usize) {
self.len -= count;
if !self.first_only {
self.heights.drain(index..index + count);
}
self.rebuild();
}
fn move_row(&mut self, from: usize, to: usize) {
if !self.first_only {
let height = self.heights.remove(from);
self.heights.insert(to, height);
self.rebuild();
}
}
fn reorder(&mut self, order: &[usize]) {
if !self.first_only {
let was = std::mem::take(&mut self.heights);
self.heights = order.iter().map(|old| was[*old]).collect();
self.rebuild();
}
}
fn rebuild(&mut self) {
let n = self.heights.len();
self.sums.clear();
self.sums.resize(n + 1, 0.0);
self.counts.clear();
self.counts.resize(n + 1, 0);
(self.sum, self.measured) = (0.0, 0);
for i in 1..=n {
let h = self.heights[i - 1];
if !h.is_nan() {
self.sums[i] += h as f64;
self.counts[i] += 1;
self.sum += h as f64;
self.measured += 1;
}
let parent = i + (i & i.wrapping_neg());
if parent <= n {
self.sums[parent] += self.sums[i];
self.counts[parent] += self.counts[i];
}
}
}
fn known(&self, index: usize) -> bool {
if self.first_only { !self.uniform.is_nan() } else { !self.heights[index].is_nan() }
}
fn average(&self) -> f64 {
if self.measured > 0 { (self.sum / self.measured as f64).round() } else { 0.0 }
}
fn height(&self, index: usize) -> f32 {
let h = if self.first_only { self.uniform } else { self.heights[index] };
if !h.is_nan() {
h
} else if self.first_only {
0.0
} else {
self.average() as f32
}
}
fn set(&mut self, index: usize, height: f32) -> bool {
if self.first_only {
if !self.uniform.is_nan() || height.is_nan() {
return false;
}
self.uniform = height;
} else {
let old = self.heights[index];
if (old.is_nan() && height.is_nan()) || (old - height).abs() <= 0.01 {
return false;
}
self.heights[index] = height;
let value = |h: f32| if h.is_nan() { 0.0 } else { h as f64 };
let (by, more) = (value(height) - value(old), height.is_nan() as i32 - old.is_nan() as i32);
self.sum += by;
self.measured = (self.measured as i32 - more) as usize;
let mut i = index + 1;
while i <= self.len {
self.sums[i] += by;
self.counts[i] = (self.counts[i] as i32 - more) as u32;
i += i & i.wrapping_neg();
}
}
true
}
fn offset(&self, index: usize, gap: f32) -> f32 {
if self.first_only {
return index as f32 * (self.height(0) + gap);
}
let (mut sum, mut count, mut i) = (0f64, 0usize, index);
while i > 0 {
sum += self.sums[i];
count += self.counts[i] as usize;
i -= i & i.wrapping_neg();
}
(sum + (index - count) as f64 * self.average() + index as f64 * gap as f64) as f32
}
fn index_at(&self, y: f32, gap: f32) -> usize {
if self.first_only {
let stride = self.height(0) + gap;
return if stride > 0.0 { ((y / stride).max(0.0) as usize).min(self.len - 1) } else { 0 };
}
let (average, gap, y) = (self.average(), gap as f64, y as f64);
let (mut at, mut sum, mut count) = (0usize, 0f64, 0usize);
let mut step = 1usize << self.len.ilog2();
while step > 0 {
let next = at + step;
if next <= self.len {
let (s, c) = (sum + self.sums[next], count + self.counts[next] as usize);
if s + (next - c) as f64 * average + next as f64 * gap <= y {
(at, sum, count) = (next, s, c);
}
}
step >>= 1;
}
at.min(self.len - 1)
}
fn range(&self, top: f32, bottom: f32, gap: f32) -> (usize, usize) {
let (first, last) = (self.index_at(top, gap), self.index_at(bottom, gap));
(first, if last > first && self.offset(last, gap) >= bottom { last - 1 } else { last })
}
fn total(&self, gap: f32) -> f32 {
if self.len == 0 { 0.0 } else { self.offset(self.len, gap) - gap }
}
}
#[derive(Clone, Copy)]
struct Work {
budget: MeasureBudget,
started: Option<Instant>,
rows: u32,
}
impl Work {
fn new(budget: MeasureBudget) -> Self {
Self { budget, started: None, rows: 0 }
}
fn fits(&mut self, synthetic: bool) -> bool {
match self.budget {
MeasureBudget::Items(items) => self.rows < items,
MeasureBudget::Millis(_) if self.rows == 0 => {
self.started = Some(Instant::now());
true
}
MeasureBudget::Millis(ms) if synthetic => (self.rows as f32) < ms,
MeasureBudget::Millis(ms) => self.started.is_none_or(|at| at.elapsed().as_secs_f32() * 1000.0 < ms),
}
}
}
struct Cell {
id: ControlId,
handles: Box<dyn Any>,
bound: Option<usize>,
}
enum Change {
Reset,
Inserted(usize, usize),
Removed(usize, usize),
Changed(usize),
Moved(usize, usize),
Reordered(Vec<usize>),
}
#[derive(Clone, Copy, PartialEq)]
struct Placement {
horizontal_options: LayoutOptions,
vertical_options: LayoutOptions,
requests: [f32; 6],
margin: Thickness,
lock_ratio: f32,
fill_ratios: (f32, f32),
spans: (i32, i32),
is_visible: bool,
z_index: i32,
}
impl Placement {
fn of(p: &ControlProps) -> Self {
Self {
horizontal_options: p.horizontal_options,
vertical_options: p.vertical_options,
requests: [
p.width_request,
p.height_request,
p.minimum_width_request,
p.minimum_height_request,
p.maximum_width_request,
p.maximum_height_request,
],
margin: p.margin,
lock_ratio: p.lock_ratio,
fill_ratios: (p.horizontal_fill_ratio, p.vertical_fill_ratio),
spans: (p.column_span, p.row_span),
is_visible: p.is_visible,
z_index: p.z_index,
}
}
fn apply(self, p: &mut ControlProps) {
(p.horizontal_options, p.vertical_options) = (self.horizontal_options, self.vertical_options);
let [w, h, min_w, min_h, max_w, max_h] = self.requests;
(p.width_request, p.height_request, p.minimum_width_request, p.minimum_height_request) = (w, h, min_w, min_h);
(p.maximum_width_request, p.maximum_height_request) = (max_w, max_h);
(p.margin, p.lock_ratio) = (self.margin, self.lock_ratio);
(p.horizontal_fill_ratio, p.vertical_fill_ratio) = self.fill_ratios;
(p.column_span, p.row_span) = self.spans;
(p.is_visible, p.z_index) = (self.is_visible, self.z_index);
}
}
struct SlotControl;
impl Control for SlotControl {}
struct Slot {
node: ControlId,
view: Option<Cell>,
measured_for: Option<(u32, u32, u32)>,
}
fn arranged_rect(base: &Base, destination: Rect, scale: f32) -> Rect {
let (placed, m) = (layout::place(base, destination.size(), scale), layout::margins(&base.p, scale));
Rect::new(
layout::snap(placed.left + destination.left + m.left),
layout::snap(placed.top + destination.top + m.top),
layout::snap(placed.right + destination.left - m.right),
layout::snap(placed.bottom + destination.top - m.bottom),
)
}
fn stale_above(tree: &mut Tree, from: ControlId) {
let mut current = Some(from);
while let Some(node) = current.and_then(|c| tree.node_mut(c)) {
node.base.content_epoch = node.base.content_epoch.wrapping_add(1);
current = node.parent;
}
}
pub(crate) fn is_list(p: &LayoutProps) -> bool {
p.layout_type == LayoutType::Column && p.split <= 1
}
type Bind = Box<dyn FnMut(&dyn Any, &dyn Any, usize, &mut Cx<'_>)>;
pub(crate) struct Items {
count_of: Rc<dyn Fn(&dyn Any) -> usize>,
template: Box<dyn FnMut() -> (Detached, Box<dyn Any>)>,
bind: Bind,
pending: Vec<Change>,
sizes: Sizes,
key: Option<(f32, f32, MeasuringStrategy, RecyclingTemplate)>,
gap: f32,
lead: f32,
max_width: f32,
cells: VecDeque<Cell>,
first: usize,
pool: Vec<Cell>,
fresh: Vec<(usize, Cell)>,
frontier: usize,
work: Work,
cells_dirty: bool,
shift: f32,
total: f32,
placed: (usize, usize, f32, f32, f32, f32),
list: bool,
laid: Vec<(f32, f32)>,
created: u32,
touched: bool,
slotted: bool,
slots: Vec<Slot>,
model: Option<Placement>,
props_changed: bool,
relaid: bool,
arranged_at: Rect,
}
fn is_permutation(order: &[usize]) -> bool {
let mut seen = vec![false; order.len()];
order.iter().all(|i| *i < seen.len() && !std::mem::replace(&mut seen[*i], true))
}
fn state<S: Any>(state: &dyn Any) -> &S {
state.downcast_ref().unwrap_or_else(|| wrong_state::<S>())
}
fn bind_cell(bind: &mut Bind, cell: &mut Cell, cx: &mut LayoutCx, index: usize) -> bool {
cell.bound = Some(index);
cx.tree.hover_check = true;
bind(&*cell.handles, cx.state, index, &mut Cx { tree: cx.tree });
layout::flush(cx.tree, Some(cx.id));
true
}
fn show(cx: &mut LayoutCx, id: ControlId, index: Option<usize>) {
if let Some(node) = cx.tree.node_mut(id) {
node.base.p.is_visible = index.is_some();
node.base.context_index = index;
}
}
impl Items {
fn recycles(&self) -> bool {
self.key.is_none_or(|k| k.3 == RecyclingTemplate::Enabled)
}
fn realized(&self, index: usize) -> Option<ControlId> {
match self.slotted {
true => self.slots.get(index)?.view.as_ref().map(|c| c.id),
false => self.cells.get(index.checked_sub(self.first)?).map(|c| c.id),
}
}
fn window(&self, cx: &LayoutCx) -> Option<(f32, f32)> {
let visible = cx.visible_rect();
if visible.is_empty() || self.sizes.len == 0 {
return None;
}
let base = cx.base();
let top = layout::content_rect(base, cx.scale).top - self.shift;
let inflate = base.p.virtualisation_inflated * cx.scale;
Some((visible.top - inflate - top, visible.bottom + inflate - top))
}
fn anchor(&self, cx: &LayoutCx) -> Option<(usize, f32)> {
let (top, bottom) = self.window(cx)?;
let (first, last) = self.sizes.range(top, bottom, self.gap);
let anchor = (first..=last).find(|i| self.sizes.known(*i)).unwrap_or(first);
Some((anchor, self.sizes.offset(anchor, self.gap)))
}
fn create(&mut self, cx: &mut LayoutCx) -> Cell {
let (detached, handles) = (self.template)();
let pending = cx.tree.needs_frame;
let id = cx.tree.mount(Some(cx.id), detached);
cx.tree.needs_frame = pending;
cx.tree.run_observers_under(id, cx.state);
self.created += 1;
Cell { id, handles, bound: None }
}
fn acquire(&mut self, cx: &mut LayoutCx, index: usize) -> Cell {
let cell = if let Some(at) = self.fresh.iter().position(|f| f.0 == index) {
self.fresh.swap_remove(at).1
} else {
let spare = self.pool.iter().position(|c| c.bound == Some(index)).or_else(|| {
let away = |c: &Cell| c.bound.map_or(usize::MAX, |bound| bound.abs_diff(index));
let (at, cell) = self.pool.iter().enumerate().max_by_key(|(_, c)| away(c))?;
(self.recycles() || cell.bound.is_none()).then_some(at)
});
let mut cell = match spare {
Some(at) => self.pool.swap_remove(at),
None => self.create(cx),
};
if cell.bound != Some(index) {
bind_cell(&mut self.bind, &mut cell, cx, index);
}
cell
};
self.touched = true;
show(cx, cell.id, Some(index));
if self.list {
self.measure_cell(cx, cell.id, index);
}
cell
}
fn release(&mut self, cx: &mut LayoutCx, cell: Cell) {
self.touched = true;
show(cx, cell.id, None);
let mut at = 0;
while let Some(animator) = cx.tree.animators.get(at) {
let inside = animator.control == cell.id || cx.tree.is_ancestor(cell.id, animator.control);
if animator.overlay.is_some() && inside {
cx.tree.animators.remove(at);
} else {
at += 1;
}
}
self.pool.push(cell);
}
fn release_all(&mut self, cx: &mut LayoutCx) {
while let Some(cell) = self.cells.pop_back() {
self.release(cx, cell);
}
}
fn release_outside(&mut self, cx: &mut LayoutCx, first: usize, last: usize) {
while self.first < first
&& let Some(cell) = self.cells.pop_front()
{
self.release(cx, cell);
self.first += 1;
}
while self.first + self.cells.len() > last + 1
&& let Some(cell) = self.cells.pop_back()
{
self.release(cx, cell);
}
}
fn realize(&mut self, cx: &mut LayoutCx, first: usize, last: usize) {
self.release_outside(cx, first, last);
if self.cells.is_empty() {
self.first = first;
}
while self.first > first {
let cell = self.acquire(cx, self.first - 1);
self.first -= 1;
self.cells.push_front(cell);
}
while self.first + self.cells.len() <= last {
let cell = self.acquire(cx, self.first + self.cells.len());
self.cells.push_back(cell);
}
}
fn drop_cells(&mut self, cx: &mut LayoutCx) {
for cell in self.cells.drain(..).chain(self.pool.drain(..)).chain(self.fresh.drain(..).map(|f| f.1)) {
cx.tree.remove_now(cell.id);
}
for slot in self.slots.drain(..) {
cx.tree.remove_now(slot.node);
}
}
fn measure_cell(&mut self, cx: &mut LayoutCx, id: ControlId, index: usize) {
let width = self.key.map_or(0.0, |k| k.0);
let size = cx.measure_child(id, width, f32::INFINITY);
let first = self.sizes.set(index, size.height);
if !self.sizes.first_only || first {
self.max_width = if self.sizes.first_only { size.width } else { self.max_width.max(size.width) };
}
}
fn measure_item(&mut self, cx: &mut LayoutCx, index: usize) {
match self.realized(index) {
Some(id) => self.measure_cell(cx, id, index),
None => {
let cell = self.acquire(cx, index);
self.release(cx, cell);
}
}
}
fn ensure(&mut self, cx: &mut LayoutCx, index: usize) {
if let Some(id) = self.realized(index) {
if self.cells_dirty {
self.measure_cell(cx, id, index);
}
} else {
let cell = self.acquire(cx, index);
self.fresh.push((index, cell));
}
}
fn sync(&mut self, cx: &mut LayoutCx, anchor: &mut Option<(usize, f32)>) {
let mut pending = std::mem::take(&mut self.pending);
for change in pending.drain(..) {
self.apply(cx, change, anchor);
}
self.pending = pending;
let (len, count) = (self.sizes.len, (self.count_of)(cx.state));
if count > len {
self.apply(cx, Change::Inserted(len, count - len), anchor);
} else if count < len {
self.apply(cx, Change::Reset, anchor);
}
}
fn apply(&mut self, cx: &mut LayoutCx, change: Change, anchor: &mut Option<(usize, f32)>) {
if self.slotted {
return self.apply_to_slots(cx, change);
}
let len = self.sizes.len;
match change {
Change::Inserted(index, count) if index <= len => {
self.sizes.insert(index, count);
let above = anchor.as_mut().filter(|(a, _)| index <= *a).map(|(a, _)| *a += count).is_some();
self.reindex(cx, index, index, |i| Some(if i >= index { i + count } else { i }));
if above && self.list && self.key.is_some_and(|k| k.2 == MeasuringStrategy::MeasureVisible) {
for row in index..index + count {
if !self.work.fits(cx.tree.synthetic_clock) {
break;
}
self.measure_item(cx, row);
self.work.rows += 1;
}
}
}
Change::Removed(index, count) if index + count <= len => {
self.sizes.remove(index, count);
*anchor = match *anchor {
Some((a, offset)) if index + count <= a => Some((a - count, offset)),
Some(kept) if index > kept.0 => Some(kept),
_ => None,
};
self.reindex(cx, index, index + count, |i| match i {
i if i >= index + count => Some(i - count),
i if i >= index => None,
i => Some(i),
});
}
Change::Moved(from, to) if from < len && to < len => {
if from == to {
return;
}
self.sizes.move_row(from, to);
let (low, high) = (from.min(to), from.max(to));
let moved = move |i: usize| match i {
i if i == from => to,
i if from < to && i > from && i <= to => i - 1,
i if to < from && i >= to && i < from => i + 1,
i => i,
};
*anchor = None;
self.reindex(cx, low, high + 1, |i| Some(moved(i)));
}
Change::Reordered(order) if order.len() == len && is_permutation(&order) => {
self.sizes.reorder(&order);
let mut now = vec![0; len];
for (new, old) in order.iter().enumerate() {
now[*old] = new;
}
*anchor = None;
self.reindex(cx, 0, len, |i| Some(now[i]));
}
Change::Changed(index) if index < len => {
self.sizes.set(index, f32::NAN);
self.frontier = self.frontier.min(index);
if let Some(cell) = index.checked_sub(self.first).and_then(|at| self.cells.get_mut(at)) {
self.touched = bind_cell(&mut self.bind, cell, cx, index);
}
self.pool.iter_mut().filter(|cell| cell.bound == Some(index)).for_each(|cell| cell.bound = None);
}
_ => {
*anchor = None;
self.release_all(cx);
self.pool.iter_mut().for_each(|cell| cell.bound = None);
self.sizes.reset((self.count_of)(cx.state), self.sizes.first_only);
(self.frontier, self.max_width) = (0, 0.0);
}
}
}
fn reindex(&mut self, cx: &mut LayoutCx, index: usize, end: usize, new_index: impl Fn(usize) -> Option<usize>) {
self.frontier = self.frontier.min(index);
if end <= self.first {
self.first = new_index(self.first).unwrap_or(self.first);
for (at, cell) in self.cells.iter_mut().enumerate() {
cell.bound = Some(self.first + at);
show(cx, cell.id, cell.bound);
}
} else if index < self.first + self.cells.len() {
self.release_all(cx);
}
for cell in &mut self.pool {
cell.bound = cell.bound.and_then(&new_index);
}
}
pub(crate) fn measure(&mut self, cx: &mut LayoutCx, p: &LayoutProps, width: f32, _height: f32) -> Size {
cx.track_viewport();
self.list = true;
self.work = Work::new(p.measure_budget);
let scale = cx.scale;
let (inner, padding) = layout::content_box(&cx.base().p, width, f32::INFINITY, scale);
let mut anchor = self.anchor(cx);
let lead = cx.base().p.padding.top * scale;
(self.gap, self.lead) = (layout::snap(p.spacing * scale), lead.min(layout::snap(lead)));
let key = (inner.width, scale, p.measure_items_strategy, p.recycling_template);
if self.key != Some(key) {
if self.key.is_some_and(|k| k.3 != key.3) {
self.drop_cells(cx);
}
self.key = Some(key);
self.sizes.reset(self.sizes.len, key.2 == MeasuringStrategy::MeasureFirst);
(self.frontier, self.max_width, anchor) = (0, 0.0, None);
}
self.sync(cx, &mut anchor);
let len = self.sizes.len;
if key.2 == MeasuringStrategy::MeasureAll {
while self.frontier < len {
if !self.sizes.known(self.frontier) {
self.measure_item(cx, self.frontier);
}
self.frontier += 1;
}
} else if len > 0 && !self.sizes.known(0) && self.sizes.measured == 0 {
self.measure_item(cx, 0);
}
if let Some((index, before)) = anchor {
self.shift += self.sizes.offset(index, self.gap) - before;
}
self.cells_dirty = true;
self.total = self.sizes.total(self.gap);
Size::new(self.max_width + padding.width, self.total + padding.height)
}
pub(crate) fn realize_all(&mut self, cx: &mut LayoutCx, p: &LayoutProps, slots: bool) -> bool {
self.list = false;
let key = (0.0, cx.scale, p.measure_items_strategy, p.recycling_template);
if self.key != Some(key) {
if self.key.is_some_and(|k| k.3 != key.3) {
self.drop_cells(cx);
}
self.key = Some(key);
self.sizes.reset(self.sizes.len, true);
}
self.slotted = slots && p.recycling_template == RecyclingTemplate::Enabled;
if self.slotted {
return self.realize_slots(cx);
}
self.sync(cx, &mut None);
match self.sizes.len {
0 => self.release_all(cx),
len => self.realize(cx, 0, len - 1),
}
self.trim_pool(cx, self.cells.len(), p.item_template_pool_size);
if let Some(node) = cx.tree.node_mut(cx.id) {
node.children.clear();
node.children.extend(self.cells.iter().chain(&self.pool).map(|c| c.id));
}
std::mem::take(&mut self.touched)
}
pub(crate) fn after_arrange(&mut self, cx: &mut LayoutCx, p: &LayoutProps) {
let rect = cx.base().rect;
if self.lays_out(rect) {
let ids = self.slots.iter().map(|s| s.node).chain(self.cells.iter().map(|c| c.id));
self.laid.clear();
self.laid.extend(ids.map(|id| {
let r = cx.child_base(id).rect;
(r.top - rect.top, r.height())
}));
}
if self.slotted {
(self.relaid, self.arranged_at) = (false, rect);
self.show_slots(cx, p.item_template_pool_size);
}
}
pub(crate) fn lays_out(&self, rect: Rect) -> bool {
!self.slotted || self.relaid || self.arranged_at != rect
}
pub(crate) fn arrange(&mut self, cx: &mut LayoutCx, p: &LayoutProps) {
let (gap, len) = (self.gap, self.sizes.len);
if !self.cells_dirty {
self.work = Work::new(p.measure_budget);
}
let window = self.window(cx);
let mut shift = std::mem::take(&mut self.shift);
if let Some((top, bottom)) = window {
let (mut first, mut last) = self.sizes.range(top, bottom, gap);
self.release_outside(cx, first, last);
let anchor = (first..=last).find(|i| self.sizes.known(*i)).unwrap_or(first);
let before = self.sizes.offset(anchor, gap);
if self.cells_dirty || !(first..=last).all(|i| self.sizes.known(i)) {
let lead = before - top;
let from_top = |sizes: &Sizes, row: usize| sizes.offset(row, gap) - sizes.offset(anchor, gap) + lead;
self.ensure(cx, anchor);
(first, last) = (anchor, anchor);
while first > 0 && from_top(&self.sizes, first) > 0.0 {
first -= 1;
self.ensure(cx, first);
}
while last + 1 < len && from_top(&self.sizes, last + 1) < bottom - top {
last += 1;
self.ensure(cx, last);
}
}
self.realize(cx, first, last);
debug_assert!(self.fresh.is_empty(), "every row met on the way is in the window");
self.trim_pool(cx, self.cells.len(), p.item_template_pool_size);
self.measure_ahead(cx);
shift += self.sizes.offset(anchor, gap) - before;
} else {
self.release_all(cx);
}
let dirty = std::mem::take(&mut self.cells_dirty);
self.place(cx, dirty);
cx.base_mut().viewport_shift = shift;
let total = self.sizes.total(gap);
let grew = total - self.total;
if grew != 0.0 {
self.total = total;
let base = cx.base_mut();
let bp = &base.p;
if base.last_constraints.1 == f32::INFINITY
&& !fills_height(bp)
&& bp.height_request < 0.0
&& bp.minimum_height_request < 0.0
&& bp.maximum_height_request < 0.0
&& bp.lock_ratio == 0.0
{
base.measured.height += grew;
base.rect.bottom += grew;
} else {
cx.tree.invalidate(cx.id, Dirty::MEASURE);
}
}
let measuring = self.frontier < len && p.measure_budget != MeasureBudget::Items(0);
if grew != 0.0 || shift != 0.0 || (measuring && p.measure_items_strategy == MeasuringStrategy::MeasureVisible) {
cx.tree.needs_frame = true;
let mut current = Some(cx.id);
while let Some(node) = current.and_then(|c| cx.tree.node_mut(c)) {
node.base.need_arrange = true;
current = node.parent;
}
}
}
fn trim_pool(&mut self, cx: &mut LayoutCx, visible: usize, pool_size: i32) {
if !self.recycles() {
return;
}
let limit = if pool_size > 0 { pool_size as usize } else { (visible * 4).max(visible + RESERVE_TEMPLATES) };
while self.pool.len() > limit.saturating_sub(visible)
&& let Some(cell) = self.pool.pop()
{
cx.tree.remove_now(cell.id);
}
}
fn measure_ahead(&mut self, cx: &mut LayoutCx) {
if self.key.is_none_or(|k| k.2 != MeasuringStrategy::MeasureVisible) {
return;
}
while self.frontier < self.sizes.len {
if !self.sizes.known(self.frontier) {
if !self.work.fits(cx.tree.synthetic_clock) {
break;
}
self.measure_item(cx, self.frontier);
self.work.rows += 1;
}
self.frontier += 1;
}
}
fn place(&mut self, cx: &mut LayoutCx, force: bool) {
let (scale, gap) = (cx.scale, self.gap);
let (rect, inner) = (cx.base().rect, layout::content_rect(cx.base(), scale));
let top = if self.cells.is_empty() { 0.0 } else { self.sizes.offset(self.first, gap) };
let placed = (self.first, self.cells.len(), top, rect.left, rect.top, rect.width());
if placed == self.placed && !force {
return;
}
self.placed = placed;
let width = inner.width().min(self.key.map_or(0.0, |k| k.0));
let mut y = top;
for (at, row) in self.cells.iter().enumerate() {
let height = self.sizes.height(self.first + at);
let base = cx.child_base(row.id);
let bp = &base.p;
let (left, right) = cross_slot(bp.horizontal_options, 0.0, width, base.measured.width);
let own = bp.vertical_options == LayoutOptions::Start && bp.horizontal_options != LayoutOptions::Start;
let slot = Rect::new(left, y, right, if own { f32::INFINITY } else { y + height });
cx.arrange_child(row.id, cell(base, slot, scale).with_offset((inner.left, inner.top)));
y += height + gap;
}
let mut current = Some(cx.id);
while let Some(node) = current.and_then(|c| cx.tree.node_mut(c)) {
node.base.content_epoch = node.base.content_epoch.wrapping_add(1);
current = node.parent;
}
}
}
impl Items {
fn realize_slots(&mut self, cx: &mut LayoutCx) -> bool {
cx.track_viewport();
self.sync(cx, &mut None);
let len = self.sizes.len;
while self.slots.len() > len
&& let Some(slot) = self.slots.pop()
{
self.drop_slot(cx, slot);
}
while self.slots.len() < len {
let slot = self.mount_slot(cx, self.slots.len());
self.slots.push(slot);
}
(self.relaid, self.props_changed) = (true, false);
self.write_children(cx);
std::mem::take(&mut self.touched)
}
fn mount_slot(&mut self, cx: &mut LayoutCx, index: usize) -> Slot {
self.touched = true;
if self.model.is_none() {
let cell = self.create(cx);
self.model = cx.tree.node(cell.id).map(|n| Placement::of(&n.base.p));
self.release(cx, cell);
}
let pending = cx.tree.needs_frame;
let node = cx.tree.mount(Some(cx.id), Build::new(SlotControl));
cx.tree.needs_frame = pending;
if let Some(n) = cx.tree.node_mut(node) {
if let Some(model) = self.model {
model.apply(&mut n.base.p);
}
n.base.context_index = Some(index);
n.children.reserve_exact(1);
}
Slot { node, view: None, measured_for: None }
}
fn drop_slot(&mut self, cx: &mut LayoutCx, mut slot: Slot) {
if let Some(mut cell) = Self::detach(cx, &mut slot) {
cell.bound = None;
self.release(cx, cell);
}
cx.tree.remove_now(slot.node);
self.touched = true;
}
fn detach(cx: &mut LayoutCx, slot: &mut Slot) -> Option<Cell> {
let cell = slot.view.take()?;
if let Some(n) = cx.tree.node_mut(slot.node) {
n.children.clear();
}
if let Some(n) = cx.tree.node_mut(cell.id) {
n.parent = Some(cx.id);
}
stale_above(cx.tree, slot.node);
Some(cell)
}
fn write_children(&self, cx: &mut LayoutCx) {
if let Some(node) = cx.tree.node_mut(cx.id) {
node.children.clear();
node.children.extend(self.slots.iter().map(|s| s.node).chain(self.pool.iter().map(|c| c.id)));
}
}
fn reindex_slots(&mut self, cx: &mut LayoutCx, from: usize) {
for (index, slot) in self.slots.iter_mut().enumerate().skip(from) {
if let Some(n) = cx.tree.node_mut(slot.node) {
n.base.context_index = Some(index);
}
if let Some(cell) = &mut slot.view {
cell.bound = Some(index);
show(cx, cell.id, Some(index));
}
}
}
fn apply_to_slots(&mut self, cx: &mut LayoutCx, change: Change) {
let len = self.sizes.len;
self.touched = true;
let remap = |pool: &mut Vec<Cell>, new_index: &dyn Fn(usize) -> Option<usize>| {
for cell in pool {
cell.bound = cell.bound.and_then(new_index);
}
};
match change {
Change::Inserted(index, count) if index <= len => {
self.sizes.insert(index, count);
let mounted: Vec<Slot> = (index..index + count).map(|at| self.mount_slot(cx, at)).collect();
self.slots.splice(index..index, mounted);
self.reindex_slots(cx, index + count);
remap(&mut self.pool, &|i| Some(if i >= index { i + count } else { i }));
}
Change::Removed(index, count) if index + count <= len => {
self.sizes.remove(index, count);
let gone: Vec<Slot> = self.slots.drain(index..index + count).collect();
for slot in gone {
self.drop_slot(cx, slot);
}
self.reindex_slots(cx, index);
remap(&mut self.pool, &|i| match i {
i if i >= index + count => Some(i - count),
i if i >= index => None,
i => Some(i),
});
}
Change::Moved(from, to) if from < len && to < len => {
let slot = self.slots.remove(from);
self.slots.insert(to, slot);
self.reindex_slots(cx, from.min(to));
remap(&mut self.pool, &|i| {
Some(match i {
i if i == from => to,
i if from < to && i > from && i <= to => i - 1,
i if to < from && i >= to && i < from => i + 1,
i => i,
})
});
}
Change::Reordered(order) if order.len() == len && is_permutation(&order) => {
let mut was: Vec<Option<Slot>> = self.slots.drain(..).map(Some).collect();
self.slots.extend(order.iter().filter_map(|old| was[*old].take()));
self.reindex_slots(cx, 0);
let mut now = vec![0; len];
for (new, old) in order.iter().enumerate() {
now[*old] = new;
}
remap(&mut self.pool, &|i| now.get(i).copied());
}
Change::Changed(index) if index < len => {
let slot = &mut self.slots[index];
slot.measured_for = None;
if let Some(cell) = &mut slot.view {
bind_cell(&mut self.bind, cell, cx, index);
}
remap(&mut self.pool, &|i| (i != index).then_some(i));
}
_ => {
for index in 0..self.slots.len() {
self.slots[index].measured_for = None;
if let Some(mut cell) = Self::detach(cx, &mut self.slots[index]) {
cell.bound = None;
self.release(cx, cell);
}
}
remap(&mut self.pool, &|_| None);
self.sizes.reset((self.count_of)(cx.state), true);
}
}
}
fn slot_of(&self, cx: &LayoutCx, child: ControlId) -> Option<usize> {
if !self.slotted {
return None;
}
let index = cx.child_base(child).context_index?;
(self.slots.get(index)?.node == child).then_some(index)
}
pub(crate) fn measure_child(&mut self, cx: &mut LayoutCx, child: ControlId, width: f32, height: f32) -> Size {
match self.slot_of(cx, child) {
Some(index) => self.measure_slot(cx, index, width, height),
None => cx.measure_child(child, width, height),
}
}
pub(crate) fn arrange_child(&mut self, cx: &mut LayoutCx, child: ControlId, destination: Rect) {
let Some(index) = self.slot_of(cx, child) else { return cx.arrange_child(child, destination) };
let rect = arranged_rect(cx.child_base(child), destination, cx.scale);
if let Some(n) = cx.tree.node_mut(child) {
(n.base.rect, n.base.last_destination, n.base.need_arrange) = (rect, destination, false);
}
if let Some(cell) = &self.slots[index].view {
cx.arrange_child(cell.id, destination);
}
}
pub(crate) fn take_props_changed(&mut self) -> bool {
std::mem::take(&mut self.props_changed)
}
fn measure_slot(&mut self, cx: &mut LayoutCx, index: usize, width: f32, height: f32) -> Size {
let node = self.slots[index].node;
let key = (width.to_bits(), height.to_bits(), cx.scale.to_bits());
let base = cx.child_base(node);
if !base.need_measure && self.slots[index].measured_for == Some(key) {
return base.measured;
}
let (cell, spare) = match self.slots[index].view.take() {
Some(cell) => (cell, false),
None => (self.acquire(cx, index), true),
};
let size = cx.measure_child(cell.id, width, height);
let (placement, measured, scale) = {
let view = cx.child_base(cell.id);
(Placement::of(&view.p), view.measured, view.scale)
};
if let Some(n) = cx.tree.node_mut(node) {
self.props_changed |= Placement::of(&n.base.p) != placement;
placement.apply(&mut n.base.p);
(n.base.measured, n.base.scale, n.base.need_measure) = (measured, scale, false);
}
self.model = Some(placement);
self.slots[index].measured_for = Some(key);
match spare {
true => self.release(cx, cell),
false => self.slots[index].view = Some(cell),
}
size
}
fn slot_area(&self, cx: &LayoutCx) -> Option<Rect> {
let mut current = Some(cx.id);
while let Some(node) = current.and_then(|id| cx.tree.node(id)) {
let scroll = node.id != cx.id && (!node.base.content_offset.is_zero() || cx.tree.find::<SkiaScroll>(node.id).is_some());
if scroll {
break;
}
if node.base.p.use_cache.resolved() != CacheType::None {
return None;
}
current = node.parent;
}
let (visible, inflate) = (cx.visible_rect(), cx.base().p.virtualisation_inflated * cx.scale);
Some(Rect::new(visible.left - inflate, visible.top - inflate, visible.right + inflate, visible.bottom + inflate))
}
fn show_slots(&mut self, cx: &mut LayoutCx, pool_size: i32) {
let area = self.slot_area(cx);
let mut changed = false;
for index in 0..self.slots.len() {
let base = cx.child_base(self.slots[index].node);
let r = base.rect;
let seen = base.p.is_visible
&& area.is_none_or(|a| r.right > a.left && r.left < a.right && r.bottom > a.top && r.top < a.bottom);
match (seen, self.slots[index].view.is_some()) {
(true, false) => {
self.attach(cx, index);
changed = true;
}
(false, true) => {
if let Some(cell) = Self::detach(cx, &mut self.slots[index]) {
self.release(cx, cell);
}
changed = true;
}
_ => {}
}
}
if changed {
let in_use = self.slots.iter().filter(|s| s.view.is_some()).count();
self.trim_pool(cx, in_use, pool_size);
self.write_children(cx);
}
}
fn attach(&mut self, cx: &mut LayoutCx, index: usize) {
let cell = self.acquire(cx, index);
let node = self.slots[index].node;
if let Some((w, h, _)) = self.slots[index].measured_for {
cx.measure_child(cell.id, f32::from_bits(w), f32::from_bits(h));
}
if let Some(n) = cx.tree.node_mut(cell.id) {
n.parent = Some(node);
}
if let Some(n) = cx.tree.node_mut(node) {
n.children.push(cell.id);
}
let destination = cx.child_base(node).last_destination;
cx.arrange_child(cell.id, destination);
stale_above(cx.tree, node);
self.slots[index].view = Some(cell);
}
}
impl<T: Control> Build<T> {
pub fn items<S: Any, C: Into<Detached>, H: 'static>(
mut self,
count: impl Fn(&S) -> usize + 'static,
mut template: impl FnMut() -> (C, H) + 'static,
mut bind: impl FnMut(&H, &S, usize, &mut Cx<'_>) + 'static,
) -> Self {
let count = Rc::new(count);
let counted = count.clone();
let layout = part_mut::<SkiaLayout>(self.control_mut()).expect("items: the control embeds no SkiaLayout");
layout.items = Some(Box::new(Items {
count_of: Rc::new(move |s: &dyn Any| counted(state::<S>(s))),
template: Box::new(move || {
let (cell, handles) = template();
(cell.into(), Box::new(handles) as Box<dyn Any>)
}),
bind: Box::new(move |handles: &dyn Any, s: &dyn Any, index: usize, cx: &mut Cx<'_>| {
bind(handles.downcast_ref::<H>().expect("handles of this template"), state::<S>(s), index, cx)
}),
pending: Vec::new(),
sizes: Sizes::default(),
key: None,
gap: 0.0,
lead: 0.0,
max_width: 0.0,
cells: VecDeque::new(),
first: 0,
pool: Vec::new(),
fresh: Vec::new(),
frontier: 0,
work: Work::new(MeasureBudget::Items(0)),
cells_dirty: false,
shift: 0.0,
total: 0.0,
placed: (0, 0, 0.0, 0.0, 0.0, 0.0),
list: true,
laid: Vec::new(),
created: 0,
touched: false,
slotted: false,
slots: Vec::new(),
model: None,
props_changed: false,
relaid: false,
arranged_at: Rect::default(),
}));
self.observe(move |me: &mut Mut<'_, T>, app: &S| {
let layout = part_mut::<SkiaLayout>(me.control_mut());
if layout.is_some_and(|l| l.items.as_ref().is_some_and(|items| items.sizes.len != count(app))) {
me.mark(Dirty::MEASURE);
}
})
}
}
impl SkiaLayout {
pub fn items_count(&self) -> usize {
self.items.as_ref().map_or(0, |items| items.sizes.len)
}
pub fn item_offset_pixels(&self, index: usize) -> f32 {
let Some(items) = self.items.as_ref() else { return 0.0 };
match items.list {
true => items.lead + items.sizes.offset(index.min(items.sizes.len), items.gap),
false => items.laid.get(index).map_or(0.0, |laid| laid.0),
}
}
pub fn item_height_pixels(&self, index: usize) -> f32 {
let Some(items) = self.items.as_ref().filter(|items| index < items.sizes.len) else { return 0.0 };
match items.list {
true => items.sizes.height(index),
false => items.laid.get(index).map_or(0.0, |laid| laid.1),
}
}
pub fn item_at_pixels(&self, y: f32) -> Option<usize> {
let items = self.items.as_ref().filter(|items| items.sizes.len > 0)?;
match items.list {
true => {
let y = y - items.lead;
(y >= 0.0 && y < items.sizes.total(items.gap)).then(|| items.sizes.index_at(y, items.gap))
}
false => items.laid.iter().position(|(top, height)| y >= *top && y < top + height),
}
}
pub fn cell_in_use(&self, index: usize) -> Option<ControlId> {
self.items.as_ref()?.realized(index)
}
pub fn cells_in_use(&self) -> impl Iterator<Item = (usize, ControlId)> + '_ {
let items = self.items.as_deref();
let first = items.map_or(0, |items| items.first);
let cells = items.into_iter().flat_map(|items| items.cells.iter()).enumerate().map(move |(at, cell)| (first + at, cell.id));
let slots = items.into_iter().flat_map(|items| items.slots.iter()).enumerate();
cells.chain(slots.filter_map(|(index, slot)| Some((index, slot.view.as_ref()?.id))))
}
pub fn debug_string(&self) -> String {
let Some(items) = self.items.as_ref() else { return "no items".to_owned() };
let (n, m) = (items.sizes.len, self.measured_items());
let visible = self.visible_items().map_or("-".to_owned(), |(first, last)| format!("{first}-{last}"));
let estimating = items.list && items.key.is_some_and(|k| k.2 == MeasuringStrategy::MeasureVisible);
let measured = if estimating { format!(" measured {m}/{n}") } else { String::new() };
let in_use = items.cells.len() + items.slots.iter().filter(|s| s.view.is_some()).count();
format!("items {n} visible {visible}{measured} inuse {in_use} pool {} created {}", items.pool.len(), items.created)
}
pub fn is_item_measured(&self, index: usize) -> bool {
self.items.as_ref().is_some_and(|items| index < items.sizes.len && items.sizes.known(index))
}
pub fn measured_items(&self) -> usize {
let Some(sizes) = self.items.as_ref().map(|items| &items.sizes) else { return 0 };
if sizes.first_only { if sizes.uniform.is_nan() { 0 } else { sizes.len } } else { sizes.measured }
}
pub fn visible_items(&self) -> Option<(usize, usize)> {
let items = self.items.as_ref()?;
if items.slotted {
let mut seen = items.slots.iter().enumerate().filter(|(_, s)| s.view.is_some()).map(|(index, _)| index);
let first = seen.next()?;
return Some((first, seen.last().unwrap_or(first)));
}
(!items.cells.is_empty()).then(|| (items.first, items.first + items.cells.len() - 1))
}
}
impl Cx<'_> {
pub fn item_rect(&self, list: impl Into<ControlId>, index: usize) -> Option<Rect> {
let tree = &*self.tree;
let cell = tree.find::<SkiaLayout>(list)?.cell_in_use(index)?;
let mut rect = tree.base(cell)?.rect;
let mut current = tree.parent(cell);
while let Some(ancestor) = current {
rect.offset(tree.base(ancestor)?.content_offset);
current = tree.parent(ancestor);
}
Some(rect)
}
fn items_change(&mut self, list: ControlId, change: Change) {
let Some(mut layout) = self.tree.find_mut::<SkiaLayout>(list) else { return };
if let Some(items) = layout.control_mut().items.as_deref_mut() {
items.pending.push(change);
layout.mark(Dirty::MEASURE);
}
}
pub fn items_reset(&mut self, list: impl Into<ControlId>) {
self.items_change(list.into(), Change::Reset)
}
pub fn items_inserted(&mut self, list: impl Into<ControlId>, index: usize, count: usize) {
self.items_change(list.into(), Change::Inserted(index, count))
}
pub fn items_removed(&mut self, list: impl Into<ControlId>, index: usize, count: usize) {
self.items_change(list.into(), Change::Removed(index, count))
}
pub fn items_changed(&mut self, list: impl Into<ControlId>, index: usize) {
self.items_change(list.into(), Change::Changed(index))
}
pub fn items_moved(&mut self, list: impl Into<ControlId>, from: usize, to: usize) {
self.items_change(list.into(), Change::Moved(from, to))
}
pub fn items_reordered(&mut self, list: impl Into<ControlId>, order: Vec<usize>) {
self.items_change(list.into(), Change::Reordered(order))
}
}