use std::{num::ParseFloatError, str::FromStr};
#[cfg(feature = "reflect")]
use bevy::prelude::{Reflect, ReflectComponent};
use bevy::{
ecs::query::ReadOnlyWorldQuery,
prelude::{trace, Children, Component, Entity, Name, Query},
utils::FloatOrd,
};
const WIDTH: Flow = Flow::Horizontal;
const HEIGHT: Flow = Flow::Vertical;
use crate::{
alignment::{Alignment, CrossAlign, Distribution},
direction::{Flow, Oriented, Size},
error::{self, Computed, Handle, Relative},
PosRect,
};
impl<T> Size<Result<T, Entity>> {
fn transpose(self, queries: &Layout<impl ReadOnlyWorldQuery>) -> Result<Size<T>, error::Why> {
let err = |flow, e: Entity| error::Why::bad_rule(flow, e, queries);
let width = self.width.map_err(|e| err(WIDTH, e))?;
let height = self.height.map_err(|e| err(HEIGHT, e))?;
Ok(Size { width, height })
}
}
impl Oriented<Computed> {
fn with_children(self, Oriented { main, cross }: Oriented<f32>) -> Oriented<f32> {
Oriented {
main: self.main.with_child(main),
cross: self.cross.with_child(cross),
}
}
}
impl Size<Computed> {
pub(crate) fn set_margin(
&mut self,
margin: Size<f32>,
queries: &Layout<impl ReadOnlyWorldQuery>,
) -> Result<(), error::Why> {
if let Computed::Valid(width) = &mut self.width {
if *width < 2. * margin.width {
return Err(error::Why::TooMuchMargin {
this: Handle::of(queries),
axis: WIDTH,
margin: margin.width,
this_size: *width,
});
}
if margin.width.is_sign_negative() {
return Err(error::Why::NegativeMargin {
this: Handle::of(queries),
axis: WIDTH,
margin: margin.width,
});
}
*width -= 2. * margin.width;
}
if let Computed::Valid(height) = &mut self.height {
if *height < 2. * margin.height {
return Err(error::Why::TooMuchMargin {
this: Handle::of(queries),
axis: HEIGHT,
margin: margin.height,
this_size: *height,
});
}
if margin.height.is_sign_negative() {
return Err(error::Why::NegativeMargin {
this: Handle::of(queries),
axis: HEIGHT,
margin: margin.height,
});
}
*height -= 2. * margin.height;
}
Ok(())
}
fn container_size(
self,
Container { rules, margin, .. }: &Container,
queries: &Layout<impl ReadOnlyWorldQuery>,
) -> Result<Self, error::Why> {
let bounds = Size {
width: rules.width.inside(self.width, queries.this),
height: rules.height.inside(self.height, queries.this),
};
let mut bounds = bounds.transpose(queries)?;
bounds.set_margin(*margin, queries)?;
Ok(bounds)
}
fn leaf_size(self, Size { width, height }: Size<LeafRule>) -> Size<Result<f32, Entity>> {
Size {
width: width.inside(self.width),
height: height.inside(self.height),
}
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
pub struct Container {
pub flow: Flow,
pub align: Alignment,
pub distrib: Distribution,
pub rules: Size<Rule>,
pub margin: Size<f32>,
}
impl Default for Container {
fn default() -> Self {
Container {
flow: Flow::Horizontal,
align: Alignment::Center,
distrib: Distribution::FillMain,
margin: Size::ZERO,
rules: Size::all(Rule::Parent(1.)),
}
}
}
impl Container {
#[must_use]
pub const fn new(flow: Flow, align: Alignment, distrib: Distribution) -> Self {
let main = match distrib {
Distribution::FillMain | Distribution::End => Rule::Parent(1.),
Distribution::Start => Rule::Children(1.),
};
let rules = flow.absolute(Oriented::new(main, Rule::Children(1.)));
let margin = Size::ZERO;
Self { flow, align, distrib, rules, margin }
}
#[must_use]
pub const fn stretch(flow: Flow) -> Self {
Self::new(flow, Alignment::Center, Distribution::FillMain)
}
#[must_use]
pub const fn compact(flow: Flow) -> Self {
Self::new(flow, Alignment::Start, Distribution::Start)
}
}
#[derive(Component, Default)]
#[cfg_attr(feature = "reflect", derive(Reflect), reflect(Component))]
pub struct Root(pub(crate) Container);
impl Root {
#[must_use]
pub const fn get(&self) -> &Container {
&self.0
}
#[must_use]
pub fn size_mut(&mut self) -> Size<&mut f32> {
use Rule::Fixed;
let Size { width: Fixed(width), height: Fixed(height) } = &mut self.0.rules else {
unreachable!("Can't construct a `Root` with non-fixed size");
};
Size { width, height }
}
#[must_use]
pub const fn size(&self) -> Size<f32> {
use Rule::Fixed;
let Size { width: Fixed(width), height: Fixed(height) } = self.0.rules else {
panic!("A Root container had an unfixed axis");
};
Size { width, height }
}
pub(crate) fn get_size(
&self,
entity: Entity,
names: &Query<&Name>,
) -> Result<Size<f32>, error::Why> {
use Rule::Fixed;
let Size { width: Fixed(width), height: Fixed(height) } = self.0.rules else {
let width_fix = matches!(self.0.rules.width, Fixed(_));
let axis = if width_fix { HEIGHT } else { WIDTH };
return Err(error::Why::invalid_root(axis, entity, names));
};
Ok(Size { width, height })
}
#[must_use]
pub const fn new(
Size { width, height }: Size<f32>,
flow: Flow,
align: Alignment,
distrib: Distribution,
margin: Size<f32>,
) -> Self {
use Rule::Fixed;
let rules = Size::new(Fixed(width), Fixed(height));
Root(Container { flow, align, distrib, rules, margin })
}
}
#[derive(Component, Clone, Copy, Debug)]
#[cfg_attr(feature = "reflect", derive(Reflect), reflect(Component))]
pub enum Node {
Container(Container),
Axis(Oriented<LeafRule>),
Box(Size<LeafRule>),
}
impl Default for Node {
fn default() -> Self {
Node::Box(Size::all(LeafRule::Parent(1.)))
}
}
impl Node {
#[must_use]
pub(crate) const fn content_sized(&self) -> bool {
use LeafRule::Fixed;
matches!(
self,
Node::Box(Size { width: Fixed(_, true), .. } | Size { height: Fixed(_, true), .. })
)
}
#[must_use]
pub fn spacer_percent(value: f32) -> Option<Self> {
Self::spacer_ratio(value / 100.)
}
#[must_use]
pub fn spacer_ratio(value: f32) -> Option<Self> {
let spacer = Node::Axis(Oriented {
main: LeafRule::Parent(value),
cross: LeafRule::Fixed(0., false),
});
(value <= 1. && value >= 0.).then_some(spacer)
}
#[must_use]
pub fn fixed(size: Size<f32>) -> Self {
Node::Box(size.map(|f| LeafRule::Fixed(f, false)))
}
const fn parent_rule(&self, flow: Flow, axis: Flow) -> Option<f32> {
match self {
Node::Container(Container { rules, .. }) => {
axis.relative(rules.as_ref()).main.parent_rule()
}
Node::Axis(oriented) => {
let rules = flow.absolute(oriented.as_ref());
axis.relative(rules).main.parent_rule()
}
Node::Box(rules) => axis.relative(rules.as_ref()).main.parent_rule(),
}
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
pub enum LeafRule {
Parent(f32),
Fixed(f32, bool),
}
impl Default for LeafRule {
fn default() -> Self {
LeafRule::Parent(1.)
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
pub enum Rule {
Children(f32),
Parent(f32),
Fixed(f32),
}
impl Default for Rule {
fn default() -> Self {
Rule::Children(1.)
}
}
impl FromStr for Rule {
type Err = Option<ParseFloatError>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some(pixels) = s.strip_suffix("px") {
let pixels = pixels.parse()?;
if pixels < 0. {
return Err(None);
}
Ok(Self::Fixed(pixels))
} else if let Some(percents) = s.strip_suffix('%') {
let percents: f32 = percents.parse()?;
if percents > 100. || percents < 0. {
return Err(None);
}
Ok(Self::Parent(percents / 100.))
} else if let Some(child_ratio) = s.strip_suffix('*') {
let ratio: f32 = child_ratio.parse()?;
if ratio > 1. || ratio < 0. {
return Err(None);
}
Ok(Self::Children(ratio))
} else {
Err(None)
}
}
}
impl LeafRule {
pub(crate) const fn from_rule(rule: Option<Rule>) -> Self {
match rule {
Some(Rule::Children(_)) | None => Self::Fixed(0., true),
Some(Rule::Fixed(v)) => Self::Fixed(v, false),
Some(Rule::Parent(v)) => Self::Parent(v),
}
}
fn inside(self, parent_size: Computed) -> Result<f32, Entity> {
match (self, parent_size) {
(LeafRule::Parent(ratio), Computed::Valid(value)) => Ok(value * ratio),
(LeafRule::Parent(_), Computed::ChildDefined(_, parent)) => Err(parent),
(LeafRule::Fixed(fixed, _), _) => Ok(fixed),
}
}
const fn parent_rule(self) -> Option<f32> {
match self {
LeafRule::Parent(ratio) => Some(ratio),
LeafRule::Fixed(_, _) => None,
}
}
}
impl Rule {
const fn parent_rule(self) -> Option<f32> {
match self {
Rule::Parent(ratio) => Some(ratio),
Rule::Children(_) | Rule::Fixed(_) => None,
}
}
fn inside(self, parent_size: Computed, this: Entity) -> Result<Computed, Entity> {
use Computed::{ChildDefined, Valid};
match (self, parent_size) {
(Rule::Parent(ratio), Valid(value)) => Ok(Valid(value * ratio)),
(Rule::Parent(_), ChildDefined(_, parent)) => Err(parent),
(Rule::Fixed(fixed), _) => Ok(Valid(fixed)),
(Rule::Children(ratio), ChildDefined(_, parent)) => Ok(ChildDefined(ratio, parent)),
(Rule::Children(ratio), _) => Ok(ChildDefined(ratio, this)),
}
}
}
pub type NodeQuery = (Entity, &'static Node, Option<&'static Children>);
pub struct Layout<'a, 'w, 's, F: ReadOnlyWorldQuery> {
pub(crate) this: Entity,
pub(crate) to_update: &'a mut Query<'w, 's, &'static mut PosRect, F>,
pub(crate) nodes: &'a Query<'w, 's, NodeQuery, F>,
pub(crate) names: &'a Query<'w, 's, &'static Name>,
}
impl<'a, 'w, 's, F: ReadOnlyWorldQuery> Layout<'a, 'w, 's, F> {
pub(crate) fn new(
this: Entity,
to_update: &'a mut Query<'w, 's, &'static mut PosRect, F>,
nodes: &'a Query<'w, 's, NodeQuery, F>,
names: &'a Query<'w, 's, &'static Name>,
) -> Self {
Self { this, to_update, nodes, names }
}
#[allow(clippy::cast_precision_loss)] pub(crate) fn container(
&mut self,
Container { flow, distrib, align, margin, .. }: Container,
children: &Children,
computed_size: Size<Computed>,
) -> Result<Size<f32>, error::Why> {
let mut child_size = Oriented { main: 0., cross: 0. };
let mut children_count: u32 = 0;
let this_entity = self.this;
for (this, node, children) in self.nodes.iter_many(children) {
self.this = this;
let Oriented { main, cross } = self.leaf(node, children, flow, computed_size)?;
child_size.main += main;
child_size.cross = child_size.cross.max(cross);
children_count += 1;
}
self.this = this_entity;
let size = flow.relative(computed_size).with_children(child_size);
self.validate_size(children, flow, child_size, size)?;
let single_child = children_count == 1;
let count = children_count.saturating_sub(1).max(1) as f32;
let (main_offset, space_between) = match distrib {
Distribution::FillMain if single_child => ((size.main - child_size.main) / 2., 0.),
Distribution::FillMain => (0., (size.main - child_size.main) / count),
Distribution::Start => (0., 0.),
Distribution::End => (size.main - child_size.main, 0.),
};
let margin = flow.relative(margin);
let mut offset = Oriented::new(main_offset + margin.main, 0.);
trace!("Setting offsets of children of {}", Handle::of(self));
let cross_align = CrossAlign::new(size, align);
let mut iter = self.to_update.iter_many_mut(children);
while let Some(mut space) = iter.fetch_next() {
let child_size = flow.relative(space.size);
offset.cross = cross_align.offset(child_size.cross) + margin.cross;
space.pos = flow.absolute(offset);
offset.main += child_size.main + space_between;
}
Ok(flow.absolute(size))
}
fn leaf(
&mut self,
node: &Node,
children: Option<&Children>,
flow: Flow,
parent: Size<Computed>,
) -> Result<Oriented<f32>, error::Why> {
let size = match *node {
Node::Container(container) => match children {
Some(children) => {
let computed_size = parent.container_size(&container, self);
let mut inner_size = self.container(container, children, computed_size?)?;
inner_size.width += container.margin.width * 2.;
inner_size.height += container.margin.height * 2.;
inner_size
}
None => return Err(error::Why::ChildlessContainer(Handle::of(self))),
},
Node::Axis(oriented) => parent.leaf_size(flow.absolute(oriented)).transpose(self)?,
Node::Box(size) => parent.leaf_size(size).transpose(self)?,
};
trace!("Setting size of {}", Handle::of(self));
if let Ok(mut to_update) = self.to_update.get_mut(self.this) {
to_update.size = size;
}
Ok(flow.relative(size))
}
fn validate_size(
&self,
children: &Children,
flow: Flow,
oriented_child_size: Oriented<f32>,
oriented_size: Oriented<f32>,
) -> Result<(), error::Why> {
let child_size = flow.absolute(oriented_child_size);
let size = flow.absolute(oriented_size);
if child_size.width <= size.width && child_size.height <= size.height {
return Ok(());
}
let width_too_large = child_size.width > size.width;
let axis = if width_too_large { WIDTH } else { HEIGHT };
let largest_child = children.iter().max_by_key(|e| {
let Ok(PosRect { size, .. }) = self.to_update.get(**e) else { return FloatOrd(0.); };
FloatOrd(if width_too_large { size.width } else { size.height })
});
let relative_size = children.iter().filter_map(|e| {
let node = self.nodes.get(*e).ok()?;
node.1.parent_rule(flow, axis)
});
let relative_size = relative_size.sum();
let largest_child = *largest_child.unwrap();
Err(error::Why::ContainerOverflow {
this: Handle::of(self),
size,
axis,
node_children_count: u32::try_from(self.nodes.iter_many(children).count()).unwrap(),
child_size: axis.relative(child_size).main,
largest_child: Handle::of_entity(largest_child, self.names),
child_relative_size: Relative::of(axis, flow, relative_size),
})
}
}