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