align/
align.rs

1//! Alignment API demo, port of <https://github.com/MoonZoon/MoonZoon/tree/main/examples/align> and <https://github.com/MoonZoon/MoonZoon/tree/main/examples/align_content>.
2
3mod 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}