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 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#[derive(Debug, Error)]
205#[error(transparent)]
206pub struct ComputeLayoutError(#[from] Why);
207
208#[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}