cuicui_layout/
error.rs

1use std::fmt;
2
3use bevy::ecs::query::ReadOnlyWorldQuery;
4use bevy::prelude::{Entity, Name, Query};
5use bevy_mod_sysfail::FailureMode;
6use thiserror::Error;
7
8use crate::{direction::Axis, direction::Size, layout::Layout};
9
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub(crate) enum Computed {
12    ChildDefined(f32, Entity),
13    Valid(f32),
14}
15impl Computed {
16    pub(crate) fn with_child(&self, child_size: f32) -> f32 {
17        match self {
18            // TODO: margin
19            Self::ChildDefined(ratio, _) => *ratio * child_size,
20            Self::Valid(size) => *size,
21        }
22    }
23}
24impl From<f32> for Computed {
25    fn from(value: f32) -> Self {
26        Self::Valid(value)
27    }
28}
29impl fmt::Display for Computed {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        match self {
32            Self::ChildDefined(_, _) => fmt::Display::fmt("<child_size>", f),
33            Self::Valid(value) => fmt::Display::fmt(value, f),
34        }
35    }
36}
37
38impl From<Size<f32>> for Size<Computed> {
39    fn from(Size { width, height }: Size<f32>) -> Self {
40        Self { width: width.into(), height: height.into() }
41    }
42}
43
44#[derive(Clone, Debug, Hash, Eq, PartialEq)]
45pub enum Handle {
46    Unnamed(Entity),
47    Named(Name),
48}
49impl Handle {
50    pub(crate) fn of_entity(entity: Entity, names: &Query<&Name>) -> Self {
51        names
52            .get(entity)
53            .map_or(Self::Unnamed(entity), |name| Self::Named(name.clone()))
54    }
55    pub(crate) fn of(queries: &Layout<impl ReadOnlyWorldQuery>) -> Self {
56        Self::of_entity(queries.this, queries.names)
57    }
58}
59impl fmt::Display for Handle {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        match self {
62            Self::Unnamed(entity) => write!(f, "<{entity:?}>"),
63            Self::Named(name) => write!(f, "{name}"),
64        }
65    }
66}
67
68#[derive(Clone, Copy, Debug, PartialEq)]
69enum RelativeAxis {
70    Main,
71    Cross,
72}
73
74impl RelativeAxis {
75    fn of(reference: Axis, axis: Axis) -> Self {
76        match reference == axis {
77            true => Self::Main,
78            false => Self::Cross,
79        }
80    }
81}
82
83impl fmt::Display for RelativeAxis {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        match self {
86            Self::Main => f.write_str("main"),
87            Self::Cross => f.write_str("cross"),
88        }
89    }
90}
91
92#[derive(Clone, Copy, Debug, PartialEq)]
93pub(crate) struct Relative {
94    size: f32,
95    axis: RelativeAxis,
96    absolute: Axis,
97}
98impl Relative {
99    pub(crate) fn of(reference: Axis, axis: Axis, size: f32) -> Self {
100        Self {
101            size,
102            axis: RelativeAxis::of(reference, axis),
103            absolute: reference,
104        }
105    }
106}
107
108impl fmt::Display for Relative {
109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110        if self.size > 0.5 {
111            let larger = self.size > 1.0;
112            write!(
113                f,
114                "- children have a total relative size on the parent's {} \
115                axis of {:0}% of the parent's {}.{}",
116                self.axis,
117                self.size * 100.0,
118                self.absolute,
119                if larger { " This is larger than the parent!" } else { "" },
120            )?;
121        }
122        Ok(())
123    }
124}
125
126#[derive(Clone, Debug, PartialEq, Error)]
127pub(crate) enum Why {
128    #[error("Both axes of a `Root` container must be `Rule::Fixed`! {this}'s {axis} is not!")]
129    InvalidRoot { this: Handle, axis: Axis },
130    #[error(
131        "{0}'s `Node` is a `Container`, yet it has no children! Use `Node::Box` or `Node::Axis` \
132        for terminal nodes!"
133    )]
134    ChildlessContainer(Handle),
135    #[error(
136        "Cyclic rule definition detected!\n\
137        - {this} depends on PARENT {parent} on {axis}\n\
138        - {parent} depends on CHILD {this} on {axis}\n\
139        It's impossible to make sense of this circular dependency!   \
140        Use different rules on {axis} for {parent} or {this} to fix this issue."
141    )]
142    CyclicRule {
143        this: Handle,
144        parent: Handle,
145        axis: Axis,
146    },
147    #[error(
148        "Node {this}'s {axis} is overflowed by its children!\n\
149        Notes:\n\
150        - {this}'s inner size (excluding margins) is {size}\n\
151        - There are {node_children_count} children of total {axis} {child_size}px.\n\
152        - The largest child is {largest_child}\n\
153        {child_relative_size}"
154    )]
155    ContainerOverflow {
156        this: Handle,
157        size: Size<f32>,
158        largest_child: Handle,
159        node_children_count: u32,
160        axis: Axis,
161        child_relative_size: Relative,
162        child_size: f32,
163    },
164    #[error(
165        "The margin of container {this} on axis {axis} has a negative value! ({margin}), \
166        cuicui_layout doesn't support negative margins."
167    )]
168    NegativeMargin {
169        this: Handle,
170        axis: Axis,
171        margin: f32,
172    },
173    #[error(
174        "The margin of container {this} on axis {axis} is of {margin} pixels, \
175        yet, {this} has a {axis} of {this_size} pixels! This would require \
176        the content of {this} to have a negative size."
177    )]
178    TooMuchMargin {
179        this: Handle,
180        axis: Axis,
181        margin: f32,
182        this_size: f32,
183    },
184}
185
186impl Why {
187    pub(crate) fn bad_rule(
188        axis: Axis,
189        parent: Entity,
190        queries: &Layout<impl ReadOnlyWorldQuery>,
191    ) -> Self {
192        Self::CyclicRule {
193            this: Handle::of(queries),
194            parent: Handle::of_entity(parent, queries.names),
195            axis,
196        }
197    }
198
199    pub(crate) fn invalid_root(axis: Axis, entity: Entity, names: &Query<&Name>) -> Self {
200        Self::InvalidRoot { this: Handle::of_entity(entity, names), axis }
201    }
202}
203/// An error caused by a bad layout.
204#[derive(Debug, Error)]
205#[error(transparent)]
206pub struct ComputeLayoutError(#[from] Why);
207
208/// Uniquely identifies an error
209#[derive(Clone, Debug, Hash, Eq, PartialEq)]
210pub enum ErrorId {
211    ChildlessContainer(Handle),
212    CyclicRule(Handle),
213    ContainerOverflow(Handle),
214    NegativeMargin(Handle),
215    InvalidRoot(Handle),
216    TooMuchMargin(Handle),
217}
218
219impl FailureMode for ComputeLayoutError {
220    type ID = ErrorId;
221
222    fn identify(&self) -> Self::ID {
223        match &self.0 {
224            Why::ChildlessContainer(this) => ErrorId::ChildlessContainer(this.clone()),
225            Why::CyclicRule { this, .. } => ErrorId::CyclicRule(this.clone()),
226            Why::ContainerOverflow { this, .. } => ErrorId::ContainerOverflow(this.clone()),
227            Why::NegativeMargin { this, .. } => ErrorId::NegativeMargin(this.clone()),
228            Why::InvalidRoot { this, .. } => ErrorId::InvalidRoot(this.clone()),
229            Why::TooMuchMargin { this, .. } => ErrorId::TooMuchMargin(this.clone()),
230        }
231    }
232}