1use bevy::{input::common_conditions::input_just_pressed, prelude::*, ui::widget::TextUiWriter};
4use std::f32::consts::{FRAC_PI_2, PI, TAU};
5
6const CONTAINER_SIZE: f32 = 150.0;
7const HALF_CONTAINER_SIZE: f32 = CONTAINER_SIZE / 2.0;
8const LOOP_LENGTH: f32 = 4.0;
9
10fn main() {
11 App::new()
12 .add_plugins(DefaultPlugins)
13 .init_resource::<AnimationState>()
14 .add_systems(Startup, setup)
15 .add_systems(
16 Update,
17 (
18 toggle_overflow.run_if(input_just_pressed(KeyCode::KeyO)),
19 next_container_size.run_if(input_just_pressed(KeyCode::KeyS)),
20 update_transform::<Move>,
21 update_transform::<Scale>,
22 update_transform::<Rotate>,
23 update_animation,
24 ),
25 )
26 .run();
27}
28
29#[derive(Component)]
30struct Instructions;
31
32#[derive(Resource, Default)]
33struct AnimationState {
34 playing: bool,
35 paused_at: f32,
36 paused_total: f32,
37 t: f32,
38}
39
40#[derive(Component)]
41struct Container(u8);
42
43trait UpdateTransform {
44 fn update(&self, t: f32, transform: &mut Transform);
45}
46
47#[derive(Component)]
48struct Move;
49
50impl UpdateTransform for Move {
51 fn update(&self, t: f32, transform: &mut Transform) {
52 transform.translation.x = ops::sin(t * TAU - FRAC_PI_2) * HALF_CONTAINER_SIZE;
53 transform.translation.y = -ops::cos(t * TAU - FRAC_PI_2) * HALF_CONTAINER_SIZE;
54 }
55}
56
57#[derive(Component)]
58struct Scale;
59
60impl UpdateTransform for Scale {
61 fn update(&self, t: f32, transform: &mut Transform) {
62 transform.scale.x = 1.0 + 0.5 * ops::cos(t * TAU).max(0.0);
63 transform.scale.y = 1.0 + 0.5 * ops::cos(t * TAU + PI).max(0.0);
64 }
65}
66
67#[derive(Component)]
68struct Rotate;
69
70impl UpdateTransform for Rotate {
71 fn update(&self, t: f32, transform: &mut Transform) {
72 transform.rotation =
73 Quat::from_axis_angle(Vec3::Z, (ops::cos(t * TAU) * 45.0).to_radians());
74 }
75}
76
77fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
78 commands.spawn(Camera2d);
81
82 let text_font = TextFont::default();
85
86 commands
87 .spawn((
88 Text::new(
89 "Next Overflow Setting (O)\nNext Container Size (S)\nToggle Animation (space)\n\n",
90 ),
91 text_font.clone(),
92 Node {
93 position_type: PositionType::Absolute,
94 top: Val::Px(12.0),
95 left: Val::Px(12.0),
96 ..default()
97 },
98 Instructions,
99 ))
100 .with_child((
101 TextSpan::new(format!("{:?}", Overflow::clip())),
102 text_font.clone(),
103 ));
104
105 commands
108 .spawn(Node {
109 width: Val::Percent(100.),
110 height: Val::Percent(100.),
111 justify_content: JustifyContent::Center,
112 align_items: AlignItems::Center,
113 ..default()
114 })
115 .with_children(|parent| {
116 parent
117 .spawn(Node {
118 display: Display::Grid,
119 grid_template_columns: RepeatedGridTrack::px(3, CONTAINER_SIZE),
120 grid_template_rows: RepeatedGridTrack::px(2, CONTAINER_SIZE),
121 row_gap: Val::Px(80.),
122 column_gap: Val::Px(80.),
123 ..default()
124 })
125 .with_children(|parent| {
126 spawn_image(parent, &asset_server, Move);
127 spawn_image(parent, &asset_server, Scale);
128 spawn_image(parent, &asset_server, Rotate);
129
130 spawn_text(parent, &asset_server, Move);
131 spawn_text(parent, &asset_server, Scale);
132 spawn_text(parent, &asset_server, Rotate);
133 });
134 });
135}
136
137fn spawn_image(
138 parent: &mut ChildSpawnerCommands,
139 asset_server: &Res<AssetServer>,
140 update_transform: impl UpdateTransform + Component,
141) {
142 spawn_container(parent, update_transform, |parent| {
143 parent.spawn((
144 ImageNode::new(asset_server.load("branding/bevy_logo_dark_big.png")),
145 Node {
146 height: Val::Px(100.),
147 position_type: PositionType::Absolute,
148 top: Val::Px(-50.),
149 left: Val::Px(-200.),
150 ..default()
151 },
152 ));
153 });
154}
155
156fn spawn_text(
157 parent: &mut ChildSpawnerCommands,
158 asset_server: &Res<AssetServer>,
159 update_transform: impl UpdateTransform + Component,
160) {
161 spawn_container(parent, update_transform, |parent| {
162 parent.spawn((
163 Text::new("Bevy"),
164 TextFont {
165 font: asset_server.load("fonts/FiraSans-Bold.ttf"),
166 font_size: 100.0,
167 ..default()
168 },
169 ));
170 });
171}
172
173fn spawn_container(
174 parent: &mut ChildSpawnerCommands,
175 update_transform: impl UpdateTransform + Component,
176 spawn_children: impl FnOnce(&mut ChildSpawnerCommands),
177) {
178 let mut transform = Transform::default();
179
180 update_transform.update(0.0, &mut transform);
181
182 parent
183 .spawn((
184 Node {
185 width: Val::Percent(100.),
186 height: Val::Percent(100.),
187 align_items: AlignItems::Center,
188 justify_content: JustifyContent::Center,
189 overflow: Overflow::clip(),
190 ..default()
191 },
192 BackgroundColor(Color::srgb(0.25, 0.25, 0.25)),
193 Container(0),
194 ))
195 .with_children(|parent| {
196 parent
197 .spawn((
198 Node {
199 align_items: AlignItems::Center,
200 justify_content: JustifyContent::Center,
201 top: Val::Px(transform.translation.x),
202 left: Val::Px(transform.translation.y),
203 ..default()
204 },
205 transform,
206 update_transform,
207 ))
208 .with_children(spawn_children);
209 });
210}
211
212fn update_animation(
213 mut animation: ResMut<AnimationState>,
214 time: Res<Time>,
215 keys: Res<ButtonInput<KeyCode>>,
216) {
217 let delta = time.elapsed_secs();
218
219 if keys.just_pressed(KeyCode::Space) {
220 animation.playing = !animation.playing;
221
222 if !animation.playing {
223 animation.paused_at = delta;
224 } else {
225 animation.paused_total += delta - animation.paused_at;
226 }
227 }
228
229 if animation.playing {
230 animation.t = (delta - animation.paused_total) % LOOP_LENGTH / LOOP_LENGTH;
231 }
232}
233
234fn update_transform<T: UpdateTransform + Component>(
235 animation: Res<AnimationState>,
236 mut containers: Query<(&mut Transform, &mut Node, &ComputedNode, &T)>,
237) {
238 for (mut transform, mut node, computed_node, update_transform) in &mut containers {
239 update_transform.update(animation.t, &mut transform);
240
241 node.left = Val::Px(transform.translation.x * computed_node.inverse_scale_factor());
242 node.top = Val::Px(transform.translation.y * computed_node.inverse_scale_factor());
243 }
244}
245
246fn toggle_overflow(
247 mut containers: Query<&mut Node, With<Container>>,
248 instructions: Single<Entity, With<Instructions>>,
249 mut writer: TextUiWriter,
250) {
251 for mut node in &mut containers {
252 node.overflow = match node.overflow {
253 Overflow {
254 x: OverflowAxis::Visible,
255 y: OverflowAxis::Visible,
256 } => Overflow::clip_y(),
257 Overflow {
258 x: OverflowAxis::Visible,
259 y: OverflowAxis::Clip,
260 } => Overflow::clip_x(),
261 Overflow {
262 x: OverflowAxis::Clip,
263 y: OverflowAxis::Visible,
264 } => Overflow::clip(),
265 _ => Overflow::visible(),
266 };
267
268 let entity = *instructions;
269 *writer.text(entity, 1) = format!("{:?}", node.overflow);
270 }
271}
272
273fn next_container_size(mut containers: Query<(&mut Node, &mut Container)>) {
274 for (mut node, mut container) in &mut containers {
275 container.0 = (container.0 + 1) % 3;
276
277 node.width = match container.0 {
278 2 => Val::Percent(30.),
279 _ => Val::Percent(100.),
280 };
281 node.height = match container.0 {
282 1 => Val::Percent(30.),
283 _ => Val::Percent(100.),
284 };
285 }
286}