#![doc = include_str!("../README.md")]
#![cfg_attr(not(feature = "std"), no_std)]
#![forbid(unsafe_code)]
extern crate alloc;
use alloc::vec::Vec;
use denise::Rect;
use denise_ui::{NodeId, Offer, Ui};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Flow {
#[default]
Row,
Column,
Layer,
}
impl Flow {
const fn main(self, rect: Rect) -> i32 {
match self {
Self::Row => rect.width,
Self::Column | Self::Layer => rect.height,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Sizing {
Fixed(i32),
Flex(u16),
Hug,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Group(usize);
#[derive(Clone, Copy, Debug)]
enum Kind {
Node(NodeId),
Group(usize),
}
#[derive(Clone, Copy, Debug)]
struct Child {
kind: Kind,
sizing: Sizing,
}
#[derive(Clone, Debug)]
struct Container {
flow: Flow,
padding: i32,
gap: i32,
children: Vec<Child>,
node: Option<NodeId>,
}
#[derive(Clone, Debug)]
pub struct Arrange {
groups: Vec<Container>,
}
impl Arrange {
#[must_use]
pub fn new(flow: Flow) -> Self {
Self {
groups: alloc::vec![Container {
flow,
padding: 0,
gap: 0,
children: Vec::new(),
node: None,
}],
}
}
#[must_use]
pub const fn root(&self) -> Group {
Group(0)
}
pub fn set_padding(&mut self, group: Group, padding: i32) {
if let Some(container) = self.groups.get_mut(group.0) {
container.padding = padding.max(0);
}
}
pub fn set_gap(&mut self, group: Group, gap: i32) {
if let Some(container) = self.groups.get_mut(group.0) {
container.gap = gap.max(0);
}
}
pub fn node(&mut self, parent: Group, id: NodeId, sizing: Sizing) {
self.push(parent, Kind::Node(id), sizing);
}
pub fn group(
&mut self,
parent: Group,
flow: Flow,
sizing: Sizing,
node: Option<NodeId>,
) -> Group {
let index = self.groups.len();
self.groups.push(Container {
flow,
padding: 0,
gap: 0,
children: Vec::new(),
node,
});
self.push(parent, Kind::Group(index), sizing);
Group(index)
}
fn push(&mut self, parent: Group, kind: Kind, sizing: Sizing) {
if let Some(container) = self.groups.get_mut(parent.0) {
container.children.push(Child { kind, sizing });
}
}
pub fn apply<M>(&self, ui: &mut Ui<M>, within: Rect) {
self.lay_out(ui, 0, within);
}
fn lay_out<M>(&self, ui: &mut Ui<M>, index: usize, box_of: Rect) {
let Some(container) = self.groups.get(index) else {
return;
};
let pad = container.padding;
let content = Rect::from_edges(
box_of.x + pad,
box_of.y + pad,
(box_of.right() - pad).max(box_of.x + pad),
(box_of.bottom() - pad).max(box_of.y + pad),
);
if container.flow == Flow::Layer {
for child in &container.children {
self.place(ui, child, content);
}
return;
}
let cross = match container.flow {
Flow::Row => Offer::tall(content.height),
Flow::Column | Flow::Layer => Offer::wide(content.width),
};
let mut taken: Vec<i32> = Vec::with_capacity(container.children.len());
let mut weights: u32 = 0;
for child in &container.children {
let extent = match child.sizing {
Sizing::Fixed(n) => n.max(0),
Sizing::Flex(weight) => {
weights += u32::from(weight);
0
}
Sizing::Hug => self.hug(ui, child, container.flow, cross),
};
taken.push(extent);
}
let gaps = container
.gap
.saturating_mul((container.children.len().max(1) - 1) as i32);
let used: i32 = taken.iter().copied().sum::<i32>().saturating_add(gaps);
let spare = (container.flow.main(content) - used).max(0);
let mut handed = 0;
let mut remaining = weights;
for (child, extent) in container.children.iter().zip(&mut taken) {
if let Sizing::Flex(weight) = child.sizing {
let weight = u32::from(weight);
*extent = if weight == 0 || weights == 0 {
0
} else if weight == remaining {
spare - handed
} else {
let share = (i64::from(spare) * i64::from(weight) / i64::from(weights)) as i32;
handed += share;
share
};
remaining -= weight;
}
}
let mut at = match container.flow {
Flow::Row => content.x,
Flow::Column | Flow::Layer => content.y,
};
for (child, extent) in container.children.iter().zip(&taken) {
let rect = match container.flow {
Flow::Row => Rect::new(at, content.y, *extent, content.height),
Flow::Column | Flow::Layer => Rect::new(content.x, at, content.width, *extent),
};
self.place(ui, child, rect);
at = at.saturating_add(*extent).saturating_add(container.gap);
}
}
fn place<M>(&self, ui: &mut Ui<M>, child: &Child, rect: Rect) {
match child.kind {
Kind::Node(id) => ui.set_layout(id, rect),
Kind::Group(index) => {
if let Some(node) = self.groups.get(index).and_then(|c| c.node) {
ui.set_layout(node, rect);
self.lay_out(ui, index, Rect::new(0, 0, rect.width, rect.height));
} else {
self.lay_out(ui, index, rect);
}
}
}
}
fn hug<M>(&self, ui: &mut Ui<M>, child: &Child, flow: Flow, cross: Offer) -> i32 {
match child.kind {
Kind::Node(id) => {
let wanted = ui.measure(id, cross);
match flow {
Flow::Row => wanted.width,
Flow::Column | Flow::Layer => wanted.height,
}
.unwrap_or(0)
.max(0)
}
Kind::Group(index) => self.natural(ui, index, flow, cross),
}
}
fn natural<M>(&self, ui: &mut Ui<M>, index: usize, flow: Flow, cross: Offer) -> i32 {
let Some(container) = self.groups.get(index) else {
return 0;
};
let pad = container.padding.saturating_mul(2);
let inner_cross = match (container.flow, cross) {
(Flow::Row, Offer { height, .. }) => Offer {
width: None,
height: height.map(|h| (h - pad).max(0)),
},
(_, Offer { width, .. }) => Offer {
width: width.map(|w| (w - pad).max(0)),
height: None,
},
};
let mut total = 0i32;
let mut widest = 0i32;
for child in &container.children {
let extent = match child.sizing {
Sizing::Fixed(n) => n.max(0),
Sizing::Flex(_) => 0,
Sizing::Hug => self.hug(ui, child, container.flow, inner_cross),
};
total = total.saturating_add(extent);
widest = widest.max(extent);
}
let gaps = container
.gap
.saturating_mul((container.children.len().max(1) - 1) as i32);
let along = match container.flow {
Flow::Layer => widest,
_ => total.saturating_add(gaps),
};
if container.flow == flow || container.flow == Flow::Layer {
along.saturating_add(pad)
} else {
widest.saturating_add(pad)
}
}
}