use std::any::Any;
use skia_safe::{Rect, Size};
use crate::control::LayoutCx;
use crate::fonts::Fonts;
use crate::tree::{Base, ControlId, ControlProps, Cx, Tree};
use crate::types::{CacheType, Dirty, LayoutOptions, Thickness};
pub(crate) fn fills_width(p: &ControlProps) -> bool {
p.horizontal_options == LayoutOptions::Fill && p.width_request < 0.0
}
pub(crate) fn fills_height(p: &ControlProps) -> bool {
p.vertical_options == LayoutOptions::Fill && p.height_request < 0.0
}
fn smart_max(a: f32, b: f32) -> f32 {
if !a.is_finite() || (b.is_finite() && b > a) { b } else { a }
}
fn smart_min(a: f32, b: f32) -> f32 {
if !a.is_finite() || (b.is_finite() && b < a) { b } else { a }
}
pub(crate) const ROUND_CENTER_ALIGNMENT: bool = true;
pub(crate) fn snap(pixels: f32) -> f32 {
pixels.round_ties_even()
}
pub(crate) fn margins(p: &ControlProps, scale: f32) -> Thickness {
let m = p.margin;
Thickness::new(snap(m.left * scale), snap(m.top * scale), snap(m.right * scale), snap(m.bottom * scale))
}
pub(crate) fn content_box(p: &ControlProps, width: f32, height: f32, scale: f32) -> (Size, Size) {
let (m, pad, scale64) = (p.margin, p.padding, scale as f64);
let rounded = margins(p, scale);
let raw = |lead: f32, trail: f32| ((lead as f64 * scale64) + (trail as f64 * scale64)) as f32;
let inner = Size::new(
snap(width + rounded.horizontal() - raw(pad.left + m.left, pad.right + m.right)),
snap(height + rounded.vertical() - raw(pad.top + m.top, pad.bottom + m.bottom)),
);
let both = |lead: f32, trail: f32| snap(lead * scale) + snap(trail * scale);
let added = Size::new(
both(m.left + pad.left, m.right + pad.right) - rounded.horizontal(),
both(m.top + pad.top, m.bottom + pad.bottom) - rounded.vertical(),
);
(inner, added)
}
pub(crate) fn content_rect(base: &Base, scale: f32) -> Rect {
let (r, pad) = (base.rect, base.p.padding);
let inset = |points: f32| (points * scale).min(snap(points * scale));
let (left, top, right, bottom) = (inset(pad.left), inset(pad.top), inset(pad.right), inset(pad.bottom));
Rect::new(r.left + left, r.top + top, r.right - right, r.bottom - bottom)
}
pub(crate) fn desired(base: &Base) -> Size {
let (p, scale) = (&base.p, base.scale);
let m = margins(p, scale);
let side = |measured: f32, margins: f32, minimum: f32, maximum: f32, request: f32| {
if minimum >= 0.0 && measured == snap(minimum * scale + margins) {
minimum * scale + margins
} else if maximum >= 0.0 && request < 0.0 && measured == snap(maximum * scale + margins) {
maximum * scale + margins
} else {
measured
}
};
Size::new(
side(base.measured.width, m.horizontal(), p.minimum_width_request, p.maximum_width_request, p.width_request),
side(base.measured.height, m.vertical(), p.minimum_height_request, p.maximum_height_request, p.height_request),
)
}
pub(crate) fn measure(
tree: &mut Tree,
fonts: &Fonts,
state: &dyn Any,
id: ControlId,
width: f32,
height: f32,
scale: f32,
) -> Size {
let Some(node) = tree.node_mut(id) else { return Size::default() };
let b = &mut node.base;
if !b.need_measure && b.scale == scale && b.last_constraints.0.to_bits() == width.to_bits() && b.last_constraints.1.to_bits() == height.to_bits() {
return b.measured;
}
b.last_constraints = (width, height);
b.scale = scale;
if width == 0.0 || height == 0.0 {
b.measured = Size::default();
b.need_measure = false;
b.need_arrange = true;
return b.measured;
}
let p = &b.p;
let m = margins(p, scale);
let (mx, my) = (m.horizontal(), m.vertical());
let (mut req_w, mut req_h) = (p.width_request, p.height_request);
if p.lock_ratio != 0.0 && (req_w >= 0.0 || req_h >= 0.0) {
let both = req_w >= 0.0 && req_h >= 0.0;
let side = if both && p.lock_ratio < 0.0 { req_w.min(req_h) } else { req_w.max(req_h) };
(req_w, req_h) = (side, side);
}
let mut w = width - mx;
let mut h = height - my;
if req_w >= 0.0 {
w = snap(req_w * scale + mx).min(width) - mx;
} else if p.maximum_width_request >= 0.0 {
w = w.min(p.maximum_width_request * scale);
}
if req_h >= 0.0 {
h = snap(req_h * scale + my).min(height) - my;
} else if p.maximum_height_request >= 0.0 {
h = h.min(p.maximum_height_request * scale);
}
let mut locked = false;
if p.lock_ratio != 0.0 && req_w < 0.0 && req_h < 0.0 {
let side = if p.lock_ratio > 0.0 { smart_max(w, h) } else { smart_min(w, h) } * p.lock_ratio.abs();
if side > 0.0 && side.is_finite() {
(w, h) = (side, side);
locked = true;
}
}
let content = match node.kind.take() {
Some(mut kind) => {
let size = kind.measure(&mut LayoutCx { tree, fonts, state, id, scale }, w, h);
if let Some(node) = tree.node_mut(id) {
node.kind = Some(kind);
}
size
}
None => Size::default(),
};
let Some(node) = tree.node_mut(id) else { return Size::default() };
let p = &node.base.p;
let (h_fill, v_fill) = (p.horizontal_options == LayoutOptions::Fill, p.vertical_options == LayoutOptions::Fill);
let (min_w, min_h, max_w, max_h) =
(p.minimum_width_request, p.minimum_height_request, p.maximum_width_request, p.maximum_height_request);
let mut rw = if locked || req_w >= 0.0 || (h_fill && w.is_finite()) { w } else { content.width };
let mut rh = if locked || req_h >= 0.0 || (v_fill && h.is_finite()) { h } else { content.height };
if !locked {
rw = rw.min((width - mx).max(0.0));
rh = rh.min((height - my).max(0.0));
}
if min_w >= 0.0 {
rw = rw.max(min_w * scale);
}
if min_h >= 0.0 {
rh = rh.max(min_h * scale);
}
if max_w >= 0.0 && req_w < 0.0 {
rw = rw.min(max_w * scale);
}
if max_h >= 0.0 && req_h < 0.0 {
rh = rh.min(max_h * scale);
}
let measured = Size::new(snap(rw + mx), snap(rh + my));
node.base.measured = measured;
node.base.need_measure = false;
node.base.need_arrange = true;
measured
}
fn place_axis(
options: LayoutOptions,
fill: bool,
available: f32,
wanted: f32,
shift: f32,
cap: f32,
ratio: f32,
) -> (f32, f32) {
let mut size = match fill {
true if cap >= 0.0 => available.min(cap),
true => available,
false => wanted.min(available),
};
if ratio != 1.0 {
size *= ratio;
}
let mut size = if size.is_finite() { size.ceil() } else { f32::MAX };
if !available.is_finite() || available <= size {
return (0.0, size.min(available));
}
match options {
LayoutOptions::Center => {
let shift = if wanted > 0.0 { shift } else { 0.0 };
let mut real = size + shift;
if ROUND_CENTER_ALIGNMENT && (snap(available) - snap(real)) % 2.0 != 0.0 {
size += 1.0;
real += 1.0;
}
let start = (available / 2.0 - real / 2.0).ceil();
let (start, end) = if start < 0.0 {
(0.0, size)
} else if start + size > available {
(available - size, available)
} else {
(start, start + size)
};
(start + shift, end + shift)
}
LayoutOptions::End => ((available - size).max(0.0), available),
_ => (0.0, size),
}
}
pub(crate) fn place(base: &Base, area: Size, scale: f32) -> Rect {
let p = &base.p;
let (m, wanted) = (margins(p, scale), desired(base));
let wanted = Size::new(wanted.width / scale * scale, wanted.height / scale * scale);
let cap = |maximum: f32, margins: f32| if maximum >= 0.0 { maximum * scale + margins } else { -1.0 };
let (left, right) = place_axis(
p.horizontal_options,
fills_width(p),
area.width,
wanted.width,
(p.margin.left - p.margin.right) * scale,
cap(p.maximum_width_request, m.horizontal()),
p.horizontal_fill_ratio,
);
let (top, bottom) = place_axis(
p.vertical_options,
fills_height(p),
area.height,
wanted.height,
(p.margin.top - p.margin.bottom) * scale,
cap(p.maximum_height_request, m.vertical()),
p.vertical_fill_ratio,
);
Rect::new(snap(left), snap(top), snap(right), snap(bottom))
}
pub(crate) fn arrange(
tree: &mut Tree,
fonts: &Fonts,
state: &dyn Any,
id: ControlId,
destination: Rect,
scale: f32,
) {
let Some(node) = tree.node_mut(id) else { return };
let b = &node.base;
if !b.need_arrange && b.last_destination == destination {
return;
}
let (fill_w, fill_h) = (fills_width(&b.p), fills_height(&b.p));
if !b.need_measure && b.scale == scale {
let (for_w, for_h) = b.last_constraints;
let differs = |side: f32, measured_for: f32, measured: f32| {
side.is_finite() && (side - if measured_for.is_finite() { measured_for } else { measured }).abs() > 1.0
};
let again_w = fill_w && differs(destination.width(), for_w, b.measured.width);
let again_h = fill_h && differs(destination.height(), for_h, b.measured.height);
if again_w || again_h {
let width = if again_w { destination.width() } else { for_w };
let height = if again_h { destination.height() } else { for_h };
measure(tree, fonts, state, id, width, height, scale);
}
}
let Some(node) = tree.node_mut(id) else { return };
let b = &mut node.base;
b.last_destination = destination;
b.need_arrange = false;
let (placed, m) = (place(b, destination.size(), scale), margins(&b.p, scale));
let rect = Rect::new(
snap(placed.left + destination.left + m.left),
snap(placed.top + destination.top + m.top),
snap(placed.right + destination.left - m.right),
snap(placed.bottom + destination.top - m.bottom),
);
let moved = b.rect != rect;
b.rect = rect;
if moved {
tree.rearrange_trackers(id, Some(id));
}
let Some(node) = tree.node_mut(id) else { return };
if let Some(mut kind) = node.kind.take() {
kind.arrange(&mut LayoutCx { tree, fonts, state, id, scale });
if let Some(node) = tree.node_mut(id) {
node.kind = Some(kind);
}
}
}
pub(crate) fn commit(tree: &mut Tree) {
flush(tree, None);
}
fn remove_pending(tree: &mut Tree) {
for id in std::mem::take(&mut tree.removals) {
let parent = tree.parent(id);
tree.remove_now(id);
if let Some(parent) = parent {
tree.invalidate(parent, Dirty::MEASURE);
}
}
}
pub(crate) fn flush(tree: &mut Tree, within: Option<ControlId>) {
remove_pending(tree);
while !tree.queue.is_empty() {
let mut batch = std::mem::take(&mut tree.queue_spare);
std::mem::swap(&mut batch, &mut tree.queue);
for id in batch.drain(..) {
let Some(node) = tree.node_mut(id) else { continue };
let dirty = std::mem::take(&mut node.base.dirty);
if dirty.contains(Dirty::APPLY)
&& let Some(mut kind) = node.kind.take()
{
kind.on_props_changed(&mut Cx { tree });
if let Some(node) = tree.node_mut(id) {
node.kind = Some(kind);
}
}
let mut measure = dirty.contains(Dirty::MEASURE);
let own = measure || dirty.contains(Dirty::DRAW);
if !own && !dirty.contains(Dirty::REPAINT) {
continue;
}
let mut current = Some(id);
let mut is_self = true;
let mut through = None;
while let Some(node) = current.and_then(|c| tree.node_mut(c)) {
if !is_self && Some(node.id) == within {
measure = false;
}
if measure {
node.base.need_measure = true;
}
if own || !is_self {
node.base.content_epoch = node.base.content_epoch.wrapping_add(1);
}
if own && is_self {
node.base.own_epoch = node.base.own_epoch.wrapping_add(1);
}
let (at, composite) = (node.id, node.base.p.use_cache.resolved() == CacheType::ImageComposite);
current = node.parent;
if composite && (own || !is_self) {
crate::paint::composite_changed(&mut tree.render, at, if is_self || measure { None } else { through });
}
through = Some(at);
is_self = false;
}
if measure && within.is_some() {
tree.needs_frame = true;
}
}
tree.queue_spare = batch;
}
}