ui_transform/
ui_transform.rs

1//! An example demonstrating how to translate, rotate and scale UI elements.
2use bevy::color::palettes::css::DARK_GRAY;
3use bevy::color::palettes::css::RED;
4use bevy::color::palettes::css::YELLOW;
5use bevy::prelude::*;
6use core::f32::consts::FRAC_PI_8;
7
8fn main() {
9    App::new()
10        .add_plugins(DefaultPlugins)
11        .add_systems(Startup, setup)
12        .add_systems(Update, button_system)
13        .add_systems(Update, translation_system)
14        .run();
15}
16
17const NORMAL_BUTTON: Color = Color::WHITE;
18const HOVERED_BUTTON: Color = Color::Srgba(YELLOW);
19const PRESSED_BUTTON: Color = Color::Srgba(RED);
20
21/// A button that rotates the target node
22#[derive(Component)]
23pub struct RotateButton(pub Rot2);
24
25/// A button that scales the target node
26#[derive(Component)]
27pub struct ScaleButton(pub f32);
28
29/// Marker component so the systems know which entities to translate, rotate and scale
30#[derive(Component)]
31pub struct TargetNode;
32
33/// Handles button interactions
34fn button_system(
35    mut interaction_query: Query<
36        (
37            &Interaction,
38            &mut BackgroundColor,
39            Option<&RotateButton>,
40            Option<&ScaleButton>,
41        ),
42        (Changed<Interaction>, With<Button>),
43    >,
44    mut rotator_query: Query<&mut UiTransform, With<TargetNode>>,
45) {
46    for (interaction, mut color, maybe_rotate, maybe_scale) in &mut interaction_query {
47        match *interaction {
48            Interaction::Pressed => {
49                *color = PRESSED_BUTTON.into();
50                if let Some(step) = maybe_rotate {
51                    for mut transform in rotator_query.iter_mut() {
52                        transform.rotation *= step.0;
53                    }
54                }
55                if let Some(step) = maybe_scale {
56                    for mut transform in rotator_query.iter_mut() {
57                        transform.scale += step.0;
58                        transform.scale =
59                            transform.scale.clamp(Vec2::splat(0.25), Vec2::splat(3.0));
60                    }
61                }
62            }
63            Interaction::Hovered => {
64                *color = HOVERED_BUTTON.into();
65            }
66            Interaction::None => {
67                *color = NORMAL_BUTTON.into();
68            }
69        }
70    }
71}
72
73// move the rotating panel when the arrow keys are pressed
74fn translation_system(
75    time: Res<Time>,
76    input: Res<ButtonInput<KeyCode>>,
77    mut translation_query: Query<&mut UiTransform, With<TargetNode>>,
78) {
79    let controls = [
80        (KeyCode::ArrowLeft, -Vec2::X),
81        (KeyCode::ArrowRight, Vec2::X),
82        (KeyCode::ArrowUp, -Vec2::Y),
83        (KeyCode::ArrowDown, Vec2::Y),
84    ];
85    for &(key_code, direction) in &controls {
86        if input.pressed(key_code) {
87            for mut transform in translation_query.iter_mut() {
88                let d = direction * 50.0 * time.delta_secs();
89                let (Val::Px(x), Val::Px(y)) = (transform.translation.x, transform.translation.y)
90                else {
91                    continue;
92                };
93                let x = (x + d.x).clamp(-150., 150.);
94                let y = (y + d.y).clamp(-150., 150.);
95
96                transform.translation = Val2::px(x, y);
97            }
98        }
99    }
100}
101
102fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
103    // UI camera
104    commands.spawn(Camera2d);
105
106    // Root node filling the whole screen
107    commands.spawn((
108        Node {
109            width: percent(100),
110            height: percent(100),
111            align_items: AlignItems::Center,
112            justify_content: JustifyContent::Center,
113            ..default()
114        },
115        BackgroundColor(Color::BLACK),
116        children![(
117            Node {
118                align_items: AlignItems::Center,
119                justify_content: JustifyContent::SpaceEvenly,
120                column_gap: px(25),
121                row_gap: px(25),
122                ..default()
123            },
124            BackgroundColor(Color::BLACK),
125            children![
126                (
127                    Node {
128                        flex_direction: FlexDirection::Column,
129                        justify_content: JustifyContent::Center,
130                        row_gap: px(10),
131                        column_gap: px(10),
132                        padding: UiRect::all(px(10)),
133                        ..default()
134                    },
135                    BackgroundColor(Color::BLACK),
136                    GlobalZIndex(1),
137                    children![
138                        (
139                            Button,
140                            Node {
141                                height: px(50),
142                                width: px(50),
143                                align_items: AlignItems::Center,
144                                justify_content: JustifyContent::Center,
145                                ..default()
146                            },
147                            BackgroundColor(Color::WHITE),
148                            RotateButton(Rot2::radians(-FRAC_PI_8)),
149                            children![(Text::new("<--"), TextColor(Color::BLACK),)]
150                        ),
151                        (
152                            Button,
153                            Node {
154                                height: px(50),
155                                width: px(50),
156                                align_items: AlignItems::Center,
157                                justify_content: JustifyContent::Center,
158                                ..default()
159                            },
160                            BackgroundColor(Color::WHITE),
161                            ScaleButton(-0.25),
162                            children![(Text::new("-"), TextColor(Color::BLACK),)]
163                        ),
164                    ]
165                ),
166                // Target node with its own set of buttons
167                (
168                    Node {
169                        flex_direction: FlexDirection::Column,
170                        justify_content: JustifyContent::SpaceBetween,
171                        align_items: AlignItems::Center,
172                        width: px(300),
173                        height: px(300),
174                        ..default()
175                    },
176                    BackgroundColor(DARK_GRAY.into()),
177                    TargetNode,
178                    children![
179                        (
180                            Button,
181                            Node {
182                                width: px(80),
183                                height: px(80),
184                                align_items: AlignItems::Center,
185                                justify_content: JustifyContent::Center,
186                                ..default()
187                            },
188                            BackgroundColor(Color::WHITE),
189                            children![(Text::new("Top"), TextColor(Color::BLACK))]
190                        ),
191                        (
192                            Node {
193                                align_self: AlignSelf::Stretch,
194                                justify_content: JustifyContent::SpaceBetween,
195                                align_items: AlignItems::Center,
196                                ..default()
197                            },
198                            children![
199                                (
200                                    Button,
201                                    Node {
202                                        width: px(80),
203                                        height: px(80),
204                                        align_items: AlignItems::Center,
205                                        justify_content: JustifyContent::Center,
206                                        ..default()
207                                    },
208                                    BackgroundColor(Color::WHITE),
209                                    UiTransform::from_rotation(Rot2::radians(
210                                        -std::f32::consts::FRAC_PI_2
211                                    )),
212                                    children![(Text::new("Left"), TextColor(Color::BLACK),)]
213                                ),
214                                (
215                                    Node {
216                                        width: px(100),
217                                        height: px(100),
218                                        ..Default::default()
219                                    },
220                                    ImageNode {
221                                        image: asset_server.load("branding/icon.png"),
222                                        image_mode: NodeImageMode::Stretch,
223                                        ..default()
224                                    }
225                                ),
226                                (
227                                    Button,
228                                    Node {
229                                        width: px(80),
230                                        height: px(80),
231                                        align_items: AlignItems::Center,
232                                        justify_content: JustifyContent::Center,
233                                        ..default()
234                                    },
235                                    UiTransform::from_rotation(Rot2::radians(
236                                        core::f32::consts::FRAC_PI_2
237                                    )),
238                                    BackgroundColor(Color::WHITE),
239                                    children![(Text::new("Right"), TextColor(Color::BLACK))]
240                                ),
241                            ]
242                        ),
243                        (
244                            Button,
245                            Node {
246                                width: px(80),
247                                height: px(80),
248                                align_items: AlignItems::Center,
249                                justify_content: JustifyContent::Center,
250                                ..default()
251                            },
252                            BackgroundColor(Color::WHITE),
253                            UiTransform::from_rotation(Rot2::radians(std::f32::consts::PI)),
254                            children![(Text::new("Bottom"), TextColor(Color::BLACK),)]
255                        ),
256                    ]
257                ),
258                // Right column of controls
259                (
260                    Node {
261                        flex_direction: FlexDirection::Column,
262                        justify_content: JustifyContent::Center,
263                        row_gap: px(10),
264                        column_gap: px(10),
265                        padding: UiRect::all(px(10)),
266                        ..default()
267                    },
268                    BackgroundColor(Color::BLACK),
269                    GlobalZIndex(1),
270                    children![
271                        (
272                            Button,
273                            Node {
274                                height: px(50),
275                                width: px(50),
276                                align_items: AlignItems::Center,
277                                justify_content: JustifyContent::Center,
278                                ..default()
279                            },
280                            BackgroundColor(Color::WHITE),
281                            RotateButton(Rot2::radians(FRAC_PI_8)),
282                            children![(Text::new("-->"), TextColor(Color::BLACK),)]
283                        ),
284                        (
285                            Button,
286                            Node {
287                                height: px(50),
288                                width: px(50),
289                                align_items: AlignItems::Center,
290                                justify_content: JustifyContent::Center,
291                                ..default()
292                            },
293                            BackgroundColor(Color::WHITE),
294                            ScaleButton(0.25),
295                            children![(Text::new("+"), TextColor(Color::BLACK),)]
296                        ),
297                    ]
298                )
299            ]
300        )],
301    ));
302}