1mod utils;
4use utils::*;
5
6use bevy::prelude::*;
7use haalka::prelude::*;
8use strum::{Display, EnumIter, IntoEnumIterator};
9
10fn main() {
11 App::new()
12 .add_plugins(examples_plugin)
13 .add_systems(
14 Startup,
15 (
16 |world: &mut World| {
17 ui_root().spawn(world);
18 },
19 camera,
20 ),
21 )
22 .run();
23}
24
25#[derive(Clone, Copy, EnumIter, Display, PartialEq)]
26#[strum(crate = "strum")]
27enum RectangleAlignment {
28 TopLeft,
29 Top,
30 TopRight,
31 Right,
32 BottomRight,
33 Bottom,
34 BottomLeft,
35 Left,
36 Center,
37}
38
39impl RectangleAlignment {
40 fn to_align(self) -> Align {
41 match self {
42 Self::TopLeft => Align::new().top().left(),
43 Self::Top => Align::new().top().center_x(),
44 Self::TopRight => Align::new().top().right(),
45 Self::Right => Align::new().right().center_y(),
46 Self::BottomRight => Align::new().bottom().right(),
47 Self::Bottom => Align::new().bottom().center_x(),
48 Self::BottomLeft => Align::new().bottom().left(),
49 Self::Left => Align::new().left().center_y(),
50 Self::Center => Align::center(),
51 }
52 }
53}
54
55#[derive(Clone, Copy, PartialEq)]
56enum Alignment {
57 Self_,
58 Content,
59}
60
61static ALIGNMENT: LazyLock<Mutable<Alignment>> = LazyLock::new(|| Mutable::new(Alignment::Self_));
62static RECTANGLE_SELF_ALIGNMENT: LazyLock<Mutable<Option<RectangleAlignment>>> = LazyLock::new(default);
63static RECTANGLE_CONTENT_ALIGNMENT: LazyLock<Mutable<Option<RectangleAlignment>>> = LazyLock::new(default);
64
65fn alignment_button(alignment: Alignment) -> impl Element {
66 let hovered = Mutable::new(false);
67 El::<Node>::new()
68 .align(Align::center())
69 .cursor(CursorIcon::System(SystemCursorIcon::Pointer))
70 .with_node(|mut node| {
71 node.width = Val::Px(250.);
72 node.height = Val::Px(80.);
73 })
74 .background_color_signal(
75 signal::or(
76 hovered.signal(),
77 ALIGNMENT
78 .signal()
79 .map(move |other_alignment| alignment == other_alignment),
80 )
81 .map_bool(|| bevy::color::palettes::basic::GRAY.into(), || Color::BLACK)
82 .map(BackgroundColor),
83 )
84 .hovered_sync(hovered)
85 .align_content(Align::center())
86 .on_click(move || ALIGNMENT.set(alignment))
87 .child(
88 El::<Text>::new()
89 .text_font(TextFont::from_font_size(25.))
90 .text(Text::new(match alignment {
91 Alignment::Self_ => "align self",
92 Alignment::Content => "align content",
93 })),
94 )
95}
96
97fn ui_root() -> impl Element {
98 Column::<Node>::new()
99 .with_node(|mut node| {
100 node.width = Val::Percent(100.);
101 node.height = Val::Percent(100.);
102 node.row_gap = Val::Px(15.);
103 })
104 .align(Align::center())
105 .align_content(Align::center())
106 .cursor(CursorIcon::default())
107 .item(
108 Row::<Node>::new()
109 .with_node(|mut node| node.column_gap = Val::Px(15.))
110 .item(container("Column", Column::<Node>::new().items(rectangles())))
111 .item(container("El", El::<Node>::new().child(rectangle(1))))
112 .item(container("Grid", Grid::<Node>::new().cells(rectangles()))),
113 )
114 .item(
115 Row::<Node>::new()
116 .with_node(|mut node| node.column_gap = Val::Px(15.))
117 .item(
118 Column::<Node>::new()
119 .with_node(|mut node| node.row_gap = Val::Px(15.))
120 .item(alignment_button(Alignment::Self_))
121 .item(alignment_button(Alignment::Content)),
122 )
123 .item(
124 Stack::<Node>::new()
125 .layers(RectangleAlignment::iter().map(align_switcher))
126 .apply(container_node),
127 ),
128 )
129 .item(
130 Row::<Node>::new()
131 .with_node(|mut node| node.column_gap = Val::Px(15.))
132 .item(container("Row", Row::<Node>::new().items(rectangles())))
133 .item(container("Stack", Stack::<Node>::new().layers(rectangles()))),
134 )
135}
136
137fn container_node<E: RawElWrapper>(el: E) -> E {
138 el.update_raw_el(|raw_el| {
139 raw_el
140 .insert(BorderColor(bevy::color::palettes::basic::GRAY.into()))
141 .with_component::<Node>(|mut node| {
142 node.height = Val::Px(200.);
143 node.width = Val::Px(278.);
144 node.border = UiRect::all(Val::Px(3.));
145 })
146 })
147}
148
149fn container(name: &str, element: impl Element) -> impl Element {
150 Column::<Node>::new()
151 .item(
152 El::<Text>::new()
153 .align(Align::new().center_x())
154 .text_font(TextFont::from_font_size(25.))
155 .text(Text::new(name)),
156 )
157 .item(
158 element
159 .align_content_signal(
160 ALIGNMENT
161 .signal()
162 .map(|alignment| matches!(alignment, Alignment::Content))
163 .map_true_signal(|| {
164 RECTANGLE_CONTENT_ALIGNMENT
165 .signal_ref(|alignment| alignment.map(|alignment| alignment.to_align()))
166 })
167 .map(Option::flatten),
168 )
169 .apply(container_node),
170 )
171}
172
173fn rectangle(index: i32) -> impl Element {
174 let size = 40;
175 El::<Node>::new()
176 .with_node(move |mut node| {
177 node.width = Val::Px(size as f32);
178 node.height = Val::Px(size as f32)
179 })
180 .background_color(BackgroundColor(bevy::color::palettes::css::DARK_GREEN.into()))
181 .align_signal(
182 ALIGNMENT
183 .signal()
184 .map(|alignment| matches!(alignment, Alignment::Self_))
185 .map_true_signal(|| {
186 RECTANGLE_SELF_ALIGNMENT.signal_ref(|alignment| alignment.map(|alignment| alignment.to_align()))
187 })
188 .map(Option::flatten),
189 )
190 .child(
191 El::<Text>::new()
192 .align(Align::center())
193 .text_font(TextFont::from_font_size(11.67))
194 .text(Text(index.to_string())),
195 )
196}
197
198fn rectangles() -> Vec<impl Element> {
199 (1..=2).map(rectangle).collect()
200}
201
202fn align_switcher(rectangle_alignment: RectangleAlignment) -> impl Element {
203 let (hovered, hovered_signal) = Mutable::new_and_signal(false);
204 El::<Node>::new()
205 .align(rectangle_alignment.to_align())
206 .cursor(CursorIcon::System(SystemCursorIcon::Pointer))
207 .background_color_signal(
208 signal::or(
209 ALIGNMENT
210 .signal()
211 .map(|alignment| match alignment {
212 Alignment::Self_ => RECTANGLE_SELF_ALIGNMENT.signal(),
213 Alignment::Content => RECTANGLE_CONTENT_ALIGNMENT.signal(),
214 })
215 .flatten()
216 .map(move |selected_option| selected_option == Some(rectangle_alignment)),
217 hovered_signal,
218 )
219 .map_bool(
220 || bevy::color::palettes::basic::BLUE,
221 || bevy::color::palettes::css::MIDNIGHT_BLUE,
222 )
223 .map(Into::<Color>::into)
224 .map(BackgroundColor),
225 )
226 .with_node(|mut node| node.padding = UiRect::all(Val::Px(5.)))
227 .child(
228 El::<Text>::new()
229 .text_font(TextFont::from_font_size(11.67))
230 .text(Text(rectangle_alignment.to_string())),
231 )
232 .hovered_sync(hovered)
233 .on_click(move || {
234 match ALIGNMENT.get() {
235 Alignment::Self_ => &RECTANGLE_SELF_ALIGNMENT,
236 Alignment::Content => &RECTANGLE_CONTENT_ALIGNMENT,
237 }
238 .set(Some(rectangle_alignment));
239 })
240}
241
242fn camera(mut commands: Commands) {
243 commands.spawn(Camera2d);
244}