cuicui_layout/alignment.rs
1//! enums for main axis and cross axis alignment.
2
3use std::{mem::replace, str::FromStr};
4
5#[cfg(feature = "reflect")]
6use bevy::prelude::Reflect;
7
8use crate::Oriented;
9
10/// The cross axis alignment. Aka alignment.
11///
12/// Note that you can use any alignments with any kind of constraint.
13///
14/// ```text
15/// Direction::Vertical
16///
17/// Start | Center | End |
18/// ▕██ ⁞ ▏ | ▕ ██ ▏ | ▕ ⁞ ██▏ |
19/// ▕███████ ▏ | ▕ ███████ ▏ | ▕ ███████▏ |
20/// ▕ ⁞ ▏ | ▕ ⁞ ▏ | ▕ ⁞ ▏ |
21/// ▕███ ⁞ ▏ | ▕ ███ ▏ | ▕ ⁞ ███▏ |
22/// ▕█ ⁞ ▏ | ▕ █ ▏ | ▕ ⁞ █▏ |
23/// ▕███ ⁞ ▏ | ▕ ███ ▏ | ▕ ⁞ ███▏ |
24/// ▕ ⁞ ▏ | ▕ ⁞ ▏ | ▕ ⁞ ▏ |
25/// ▕█ ⁞ ▏ | ▕ █ ▏ | ▕ ⁞ █▏ |
26/// ```
27///
28/// ```text
29/// Direction::Horizontal
30///
31/// | Start | Center | End |
32/// |▁▁▁▁▁▁▁▁▁▁▁|▁▁▁▁▁▁▁▁▁▁▁|▁▁▁▁▁▁▁▁▁▁▁|
33/// |██ ███ █ ██| | |
34/// |██ █ █ ██| █ | |
35/// | █ ███ ██|██ ███ ██| █ |
36/// |⋯█⋯⋯⋯⋯⋯⋯⋯⋯⋯|██⋯█⋯█⋯█⋯██|⋯█⋯⋯⋯⋯⋯⋯⋯⋯⋯|
37/// | █ | █ ███ ██| █ ███ ██|
38/// | | █ |██ █ █ ██|
39/// | | |██ ███ █ ██|
40/// |▔▔▔▔▔▔▔▔▔▔▔|▔▔▔▔▔▔▔▔▔▔▔|▔▔▔▔▔▔▔▔▔▔▔|
41/// ```
42#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
43#[cfg_attr(feature = "reflect", derive(Reflect))]
44pub enum Alignment {
45 /// The items within the container are all aligned to the top or left.
46 ///
47 /// If the container's axis is `Direction::Vertical`, a start alignment
48 /// will align all items to the left.
49 #[default]
50 Start,
51
52 /// The items within the container are all centered on the container's axis.
53 Center,
54
55 /// The items within the container are all aligned to the bottom or right.
56 ///
57 /// If the container's axis is `Direction::Vertical`, an end alignment
58 /// will align all items to the right.
59 End,
60}
61
62/// The main axis alignment. Aka distribution.
63///
64/// The following suposes an [`Alignment::Start`].
65///
66/// ```text
67/// Direction::Vertical
68///
69/// Start | FillMain | End |
70/// ▕██ ⁞ ▏ | ▕██ ⁞ ▏ | ▕ ⁞ ▏ |
71/// ▕███████ ▏ | ▕ ⁞ ▏ | ▕ ⁞ ▏ |
72/// ▕███ ⁞ ▏ | ▕███████ ▏ | ▕ ⁞ ▏ |
73/// ▕█ ⁞ ▏ | ▕ ⁞ ▏ | ▕██ ⁞ ▏ |
74/// ▕ ⁞ ▏ | ▕███ ⁞ ▏ | ▕███████ ▏ |
75/// ▕ ⁞ ▏ | ▕ ⁞ ▏ | ▕███ ⁞ ▏ |
76/// ▕ ⁞ ▏ | ▕█ ⁞ ▏ | ▕█ ⁞ ▏ |
77/// ```
78#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
79#[cfg_attr(feature = "reflect", derive(Reflect))]
80#[doc(alias = "justification")]
81pub enum Distribution {
82 /// Items are clumped together at the left/top.
83 #[default]
84 Start,
85
86 /// Items are distributed evenly, with no space left on the sides of the container.
87 FillMain,
88
89 /// Items are clumped together at the right/bottom.
90 End,
91
92 /// Items overlap at the left/top.
93 OverlapStart,
94
95 /// Items overlap centered on the middle of the container.
96 OverlapCenter,
97
98 /// Items overlap at the right/bottom.
99 OverlapEnd,
100}
101
102/// Manage cross alignment.
103#[derive(Clone, Copy, PartialEq, Debug)]
104pub(crate) struct CrossAlign {
105 cross_parent_size: f32,
106 align: Alignment,
107}
108
109impl Alignment {
110 pub(crate) const fn compute(self, parent_size: Oriented<f32>) -> CrossAlign {
111 CrossAlign { cross_parent_size: parent_size.cross, align: self }
112 }
113}
114impl CrossAlign {
115 pub fn offset(self, cross_child_size: f32) -> f32 {
116 match self.align {
117 Alignment::Start => 0.0,
118 Alignment::Center => (self.cross_parent_size - cross_child_size) / 2.0,
119 Alignment::End => self.cross_parent_size - cross_child_size,
120 }
121 }
122}
123
124/// Manage main alignment based on [`Distribution`].
125#[derive(Clone, PartialEq, Debug)]
126pub(crate) struct MainAlign {
127 offset: f32,
128 gap: f32,
129 distrib: Distribution,
130}
131
132impl Distribution {
133 /// Whether children are meant to overlap.
134 #[must_use]
135 pub const fn overlaps(self) -> bool {
136 use Distribution::{OverlapCenter, OverlapEnd, OverlapStart};
137 matches!(self, OverlapStart | OverlapCenter | OverlapEnd)
138 }
139 pub(crate) fn compute(
140 self,
141 main_size: f32,
142 child_main_size: f32,
143 single_child: bool,
144 count: f32,
145 ) -> MainAlign {
146 let (offset, gap) = match self {
147 Self::FillMain if single_child => ((main_size - child_main_size) / 2., 0.),
148 Self::OverlapCenter => (0., main_size / 2.),
149 Self::FillMain => (0., (main_size - child_main_size) / count),
150 Self::Start | Self::OverlapStart => (0., 0.),
151 Self::End => (main_size - child_main_size, 0.),
152 Self::OverlapEnd => (0., main_size),
153 };
154 MainAlign { offset, gap, distrib: self }
155 }
156}
157impl MainAlign {
158 pub fn offset(&mut self, child_size: f32) -> f32 {
159 use Distribution::{End, FillMain, Start};
160 match self.distrib {
161 Start | FillMain | End => {
162 let new_offset = self.offset + child_size + self.gap;
163 replace(&mut self.offset, new_offset)
164 }
165 Distribution::OverlapStart => 0.,
166 Distribution::OverlapCenter => self.gap - child_size / 2.,
167 Distribution::OverlapEnd => self.gap - child_size,
168 }
169 }
170}
171
172impl FromStr for Distribution {
173 type Err = ();
174
175 fn from_str(s: &str) -> Result<Self, Self::Err> {
176 match s {
177 "dS" => Ok(Self::Start),
178 "dE" => Ok(Self::End),
179 "dC" => Ok(Self::FillMain),
180 "oS" => Ok(Self::OverlapStart),
181 "oE" => Ok(Self::OverlapEnd),
182 "oC" => Ok(Self::OverlapCenter),
183 _ => Err(()),
184 }
185 }
186}
187
188impl FromStr for Alignment {
189 type Err = ();
190
191 fn from_str(s: &str) -> Result<Self, Self::Err> {
192 match s {
193 "aS" => Ok(Self::Start),
194 "aE" => Ok(Self::End),
195 "aC" => Ok(Self::Center),
196 _ => Err(()),
197 }
198 }
199}