1use bevy::{input::common_conditions::input_just_pressed, prelude::*, ui::widget::TextUiWriter};
4
5use std::f32::consts::{FRAC_PI_2, PI, TAU};
6
7const CONTAINER_SIZE: f32 = 150.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 UiTransform);
45}
46
47#[derive(Component)]
48struct Move;
49
50impl UpdateTransform for Move {
51 fn update(&self, t: f32, transform: &mut UiTransform) {
52 transform.translation.x = percent(ops::sin(t * TAU - FRAC_PI_2) * 50.);
53 transform.translation.y = percent(-ops::cos(t * TAU - FRAC_PI_2) * 50.);
54 }
55}
56
57#[derive(Component)]
58struct Scale;
59
60impl UpdateTransform for Scale {
61 fn update(&self, t: f32, transform: &mut UiTransform) {
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 UiTransform) {
72 transform.rotation = Rot2::radians(ops::cos(t * TAU) * 45.0);
73 }
74}
75
76fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
77 commands.spawn(Camera2d);
80
81 let text_font = TextFont::default();
84
85 commands
86 .spawn((
87 Text::new(
88 "Next Overflow Setting (O)\nNext Container Size (S)\nToggle Animation (space)\n\n",
89 ),
90 text_font.clone(),
91 Node {
92 position_type: PositionType::Absolute,
93 top: px(12),
94 left: px(12),
95 ..default()
96 },
97 Instructions,
98 ))
99 .with_child((
100 TextSpan::new(format!("{:?}", Overflow::clip())),
101 text_font.clone(),
102 ));
103
104 commands
107 .spawn(Node {
108 width: percent(100),
109 height: percent(100),
110 justify_content: JustifyContent::Center,
111 align_items: AlignItems::Center,
112 ..default()
113 })
114 .with_children(|parent| {
115 parent
116 .spawn(Node {
117 display: Display::Grid,
118 grid_template_columns: RepeatedGridTrack::px(3, CONTAINER_SIZE),
119 grid_template_rows: RepeatedGridTrack::px(2, CONTAINER_SIZE),
120 row_gap: px(80),
121 column_gap: px(80),
122 ..default()
123 })
124 .with_children(|parent| {
125 spawn_image(parent, &asset_server, Move);
126 spawn_image(parent, &asset_server, Scale);
127 spawn_image(parent, &asset_server, Rotate);
128
129 spawn_text(parent, &asset_server, Move);
130 spawn_text(parent, &asset_server, Scale);
131 spawn_text(parent, &asset_server, Rotate);
132 });
133 });
134}
135
136fn spawn_image(
137 parent: &mut ChildSpawnerCommands,
138 asset_server: &Res<AssetServer>,
139 update_transform: impl UpdateTransform + Component,
140) {
141 spawn_container(parent, update_transform, |parent| {
142 parent.spawn((
143 ImageNode::new(asset_server.load("branding/bevy_logo_dark_big.png")),
144 Node {
145 height: px(100),
146 position_type: PositionType::Absolute,
147 top: px(-50),
148 left: px(-200),
149 ..default()
150 },
151 ));
152 });
153}
154
155fn spawn_text(
156 parent: &mut ChildSpawnerCommands,
157 asset_server: &Res<AssetServer>,
158 update_transform: impl UpdateTransform + Component,
159) {
160 spawn_container(parent, update_transform, |parent| {
161 parent.spawn((
162 Text::new("Bevy"),
163 TextFont {
164 font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
165 font_size: FontSize::Px(100.0),
166 ..default()
167 },
168 ));
169 });
170}
171
172fn spawn_container(
173 parent: &mut ChildSpawnerCommands,
174 update_transform: impl UpdateTransform + Component,
175 spawn_children: impl FnOnce(&mut ChildSpawnerCommands),
176) {
177 parent
178 .spawn((
179 Node {
180 width: percent(100),
181 height: percent(100),
182 align_items: AlignItems::Center,
183 justify_content: JustifyContent::Center,
184 overflow: Overflow::clip(),
185 ..default()
186 },
187 BackgroundColor(Color::srgb(0.25, 0.25, 0.25)),
188 Container(0),
189 ))
190 .with_children(|parent| {
191 parent
192 .spawn((
193 Node {
194 align_items: AlignItems::Center,
195 justify_content: JustifyContent::Center,
196 ..default()
197 },
198 update_transform,
199 ))
200 .with_children(spawn_children);
201 });
202}
203
204fn update_animation(
205 mut animation: ResMut<AnimationState>,
206 time: Res<Time>,
207 keys: Res<ButtonInput<KeyCode>>,
208) {
209 let delta = time.elapsed_secs();
210
211 if keys.just_pressed(KeyCode::Space) {
212 animation.playing = !animation.playing;
213
214 if !animation.playing {
215 animation.paused_at = delta;
216 } else {
217 animation.paused_total += delta - animation.paused_at;
218 }
219 }
220
221 if animation.playing {
222 animation.t = (delta - animation.paused_total) % LOOP_LENGTH / LOOP_LENGTH;
223 }
224}
225
226fn update_transform<T: UpdateTransform + Component>(
227 animation: Res<AnimationState>,
228 mut containers: Query<(&mut UiTransform, &T)>,
229) {
230 for (mut transform, update_transform) in &mut containers {
231 update_transform.update(animation.t, &mut transform);
232 }
233}
234
235fn toggle_overflow(
236 mut containers: Query<&mut Node, With<Container>>,
237 instructions: Single<Entity, With<Instructions>>,
238 mut writer: TextUiWriter,
239) {
240 for mut node in &mut containers {
241 node.overflow = match node.overflow {
242 Overflow {
243 x: OverflowAxis::Visible,
244 y: OverflowAxis::Visible,
245 } => Overflow::clip_y(),
246 Overflow {
247 x: OverflowAxis::Visible,
248 y: OverflowAxis::Clip,
249 } => Overflow::clip_x(),
250 Overflow {
251 x: OverflowAxis::Clip,
252 y: OverflowAxis::Visible,
253 } => Overflow::clip(),
254 _ => Overflow::visible(),
255 };
256
257 let entity = *instructions;
258 *writer.text(entity, 1) = format!("{:?}", node.overflow);
259 }
260}
261
262fn next_container_size(mut containers: Query<(&mut Node, &mut Container)>) {
263 for (mut node, mut container) in &mut containers {
264 container.0 = (container.0 + 1) % 3;
265
266 node.width = match container.0 {
267 2 => percent(30),
268 _ => percent(100),
269 };
270 node.height = match container.0 {
271 1 => percent(30),
272 _ => percent(100),
273 };
274 }
275}