#![allow(clippy::module_name_repetitions)]
use std::fmt;
use bevy::{
ecs::query::ReadOnlyWorldQuery,
prelude::{Entity, Name, Query},
};
use bevy_mod_sysfail::FailureMode;
use thiserror::Error;
use crate::{direction::Axis, direction::Size, layout::Layout};
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) enum Computed {
ChildDefined(f32, Entity),
Valid(f32),
}
impl Computed {
pub(crate) fn with_child(&self, child_size: f32) -> f32 {
match self {
Computed::ChildDefined(ratio, _) => *ratio * child_size,
Computed::Valid(size) => *size,
}
}
}
impl From<f32> for Computed {
fn from(value: f32) -> Self {
Computed::Valid(value)
}
}
impl fmt::Display for Computed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Computed::ChildDefined(_, _) => fmt::Display::fmt("<child_size>", f),
Computed::Valid(value) => fmt::Display::fmt(value, f),
}
}
}
impl From<Size<f32>> for Size<Computed> {
fn from(Size { width, height }: Size<f32>) -> Self {
Size { width: width.into(), height: height.into() }
}
}
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub enum Handle {
Unnamed(Entity),
Named(Name),
}
impl Handle {
pub(crate) fn of_entity(entity: Entity, names: &Query<&Name>) -> Self {
names
.get(entity)
.map_or(Handle::Unnamed(entity), |name| Handle::Named(name.clone()))
}
pub(crate) fn of(queries: &Layout<impl ReadOnlyWorldQuery>) -> Self {
Self::of_entity(queries.this, queries.names)
}
}
impl fmt::Display for Handle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Handle::Unnamed(entity) => write!(f, "<{entity:?}>"),
Handle::Named(name) => write!(f, "{name}"),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum RelativeAxis {
Main,
Cross,
}
impl RelativeAxis {
fn of(reference: Axis, axis: Axis) -> Self {
match reference == axis {
true => RelativeAxis::Main,
false => RelativeAxis::Cross,
}
}
}
impl fmt::Display for RelativeAxis {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Main => f.write_str("main"),
Self::Cross => f.write_str("cross"),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct Relative {
size: f32,
axis: RelativeAxis,
absolute: Axis,
}
impl Relative {
pub(crate) fn of(reference: Axis, axis: Axis, size: f32) -> Self {
Relative {
size,
axis: RelativeAxis::of(reference, axis),
absolute: reference,
}
}
}
impl fmt::Display for Relative {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.size > 0.5 {
let larger = self.size > 1.0;
write!(
f,
"- children have a total relative size on the parent's {} \
axis of {:0}% of the parent's {}.{}",
self.axis,
self.size * 100.0,
self.absolute,
if larger { " This is larger than the parent!" } else { "" },
)?;
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Error)]
pub(crate) enum Why {
#[error("Both axes of a `Root` container must be `Rule::Fixed`! {this}'s {axis} is not!")]
InvalidRoot { this: Handle, axis: Axis },
#[error(
"{0}'s `Node` is a `Container`, yet it has no children! Use `Node::Box` or `Node::Axis` \
for terminal nodes!"
)]
ChildlessContainer(Handle),
#[error(
"Cyclic rule definition detected!\n\
- {this} depends on PARENT {parent} on {axis}\n\
- {parent} depends on CHILD {this} on {axis}\n\
It's impossible to make sense of this circular dependency! \
Use different rules on {axis} for any container between {parent} and {this} \
(included) to fix this issue."
)]
CyclicRule {
this: Handle,
parent: Handle,
axis: Axis,
},
#[error(
"Node {this}'s {axis} is overflowed by its children!\n\
Notes:\n\
- {this}'s inner size (excluding margins) is {size}\n\
- There are {node_children_count} children of total {axis} {child_size}px.\n\
- The largest child is {largest_child}\n\
{child_relative_size}"
)]
ContainerOverflow {
this: Handle,
size: Size<f32>,
largest_child: Handle,
node_children_count: u32,
axis: Axis,
child_relative_size: Relative,
child_size: f32,
},
#[error(
"The margin of container {this} on axis {axis} has a negative value! ({margin}), \
cuicui_layout doesn't support negative margins."
)]
NegativeMargin {
this: Handle,
axis: Axis,
margin: f32,
},
#[error(
"The margin of container {this} on axis {axis} is of {margin} pixels, \
yet, {this} has a {axis} of {this_size} pixels! This would require \
the content of {this} to have a negative size."
)]
TooMuchMargin {
this: Handle,
axis: Axis,
margin: f32,
this_size: f32,
},
}
impl Why {
pub(crate) fn bad_rule(
axis: Axis,
parent: Entity,
queries: &Layout<impl ReadOnlyWorldQuery>,
) -> Self {
Why::CyclicRule {
this: Handle::of(queries),
parent: Handle::of_entity(parent, queries.names),
axis,
}
}
pub(crate) fn invalid_root(axis: Axis, entity: Entity, names: &Query<&Name>) -> Self {
Why::InvalidRoot { this: Handle::of_entity(entity, names), axis }
}
}
#[derive(Debug, Error)]
#[error(transparent)]
pub struct ComputeLayoutError(#[from] Why);
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub enum ErrorId {
ChildlessContainer(Handle),
CyclicRule(Handle),
ContainerOverflow(Handle),
NegativeMargin(Handle),
InvalidRoot(Handle),
TooMuchMargin(Handle),
}
impl FailureMode for ComputeLayoutError {
fn log_level(&self) -> bevy_mod_sysfail::LogLevel {
bevy_mod_sysfail::LogLevel::Error
}
type ID = ErrorId;
fn identify(&self) -> Self::ID {
match &self.0 {
Why::ChildlessContainer(this) => ErrorId::ChildlessContainer(this.clone()),
Why::CyclicRule { this, .. } => ErrorId::CyclicRule(this.clone()),
Why::ContainerOverflow { this, .. } => ErrorId::ContainerOverflow(this.clone()),
Why::NegativeMargin { this, .. } => ErrorId::NegativeMargin(this.clone()),
Why::InvalidRoot { this, .. } => ErrorId::InvalidRoot(this.clone()),
Why::TooMuchMargin { this, .. } => ErrorId::TooMuchMargin(this.clone()),
}
}
fn display(&self) -> Option<String> {
Some(self.to_string())
}
}