|
| 1 | +//! Tests how different transforms behave when clipped with `Overflow::Hidden` |
| 2 | +use bevy::prelude::*; |
| 3 | +use std::f32::consts::{FRAC_PI_2, PI, TAU}; |
| 4 | + |
| 5 | +const CONTAINER_SIZE: f32 = 150.0; |
| 6 | +const HALF_CONTAINER_SIZE: f32 = CONTAINER_SIZE / 2.0; |
| 7 | +const LOOP_LENGTH: f32 = 4.0; |
| 8 | + |
| 9 | +fn main() { |
| 10 | + App::new() |
| 11 | + .add_plugins(DefaultPlugins) |
| 12 | + // TODO: Remove once #8144 is fixed |
| 13 | + .insert_resource(GizmoConfig { |
| 14 | + enabled: false, |
| 15 | + ..default() |
| 16 | + }) |
| 17 | + .insert_resource(AnimationState { |
| 18 | + playing: false, |
| 19 | + paused_at: 0.0, |
| 20 | + paused_total: 0.0, |
| 21 | + t: 0.0, |
| 22 | + }) |
| 23 | + .add_systems(Startup, setup) |
| 24 | + .add_systems( |
| 25 | + Update, |
| 26 | + ( |
| 27 | + toggle_overflow, |
| 28 | + next_container_size, |
| 29 | + update_transform::<Move>, |
| 30 | + update_transform::<Scale>, |
| 31 | + update_transform::<Rotate>, |
| 32 | + update_animation, |
| 33 | + ), |
| 34 | + ) |
| 35 | + .run(); |
| 36 | +} |
| 37 | + |
| 38 | +#[derive(Resource)] |
| 39 | +struct AnimationState { |
| 40 | + playing: bool, |
| 41 | + paused_at: f32, |
| 42 | + paused_total: f32, |
| 43 | + t: f32, |
| 44 | +} |
| 45 | + |
| 46 | +#[derive(Component)] |
| 47 | +struct Container(u8); |
| 48 | + |
| 49 | +trait UpdateTransform { |
| 50 | + fn update(&self, t: f32, transform: &mut Transform); |
| 51 | +} |
| 52 | + |
| 53 | +#[derive(Component)] |
| 54 | +struct Move; |
| 55 | + |
| 56 | +impl UpdateTransform for Move { |
| 57 | + fn update(&self, t: f32, transform: &mut Transform) { |
| 58 | + transform.translation.x = (t * TAU - FRAC_PI_2).sin() * HALF_CONTAINER_SIZE; |
| 59 | + transform.translation.y = -(t * TAU - FRAC_PI_2).cos() * HALF_CONTAINER_SIZE; |
| 60 | + } |
| 61 | +} |
| 62 | + |
| 63 | +#[derive(Component)] |
| 64 | +struct Scale; |
| 65 | + |
| 66 | +impl UpdateTransform for Scale { |
| 67 | + fn update(&self, t: f32, transform: &mut Transform) { |
| 68 | + transform.scale.x = 1.0 + 0.5 * (t * TAU).cos().max(0.0); |
| 69 | + transform.scale.y = 1.0 + 0.5 * (t * TAU + PI).cos().max(0.0); |
| 70 | + } |
| 71 | +} |
| 72 | + |
| 73 | +#[derive(Component)] |
| 74 | +struct Rotate; |
| 75 | + |
| 76 | +impl UpdateTransform for Rotate { |
| 77 | + fn update(&self, t: f32, transform: &mut Transform) { |
| 78 | + transform.rotation = Quat::from_axis_angle(Vec3::Z, ((t * TAU).cos() * 45.0).to_radians()); |
| 79 | + } |
| 80 | +} |
| 81 | + |
| 82 | +fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { |
| 83 | + // Camera |
| 84 | + commands.spawn(Camera2dBundle::default()); |
| 85 | + |
| 86 | + commands |
| 87 | + .spawn(NodeBundle { |
| 88 | + style: Style { |
| 89 | + size: Size::width(Val::Percent(100.)), |
| 90 | + flex_direction: FlexDirection::Column, |
| 91 | + ..default() |
| 92 | + }, |
| 93 | + ..default() |
| 94 | + }) |
| 95 | + .with_children(|parent| { |
| 96 | + parent |
| 97 | + .spawn(NodeBundle { |
| 98 | + style: Style { |
| 99 | + size: Size::height(Val::Px(32.)), |
| 100 | + align_items: AlignItems::Center, |
| 101 | + justify_content: JustifyContent::Center, |
| 102 | + ..default() |
| 103 | + }, |
| 104 | + background_color: Color::DARK_GRAY.into(), |
| 105 | + ..default() |
| 106 | + }) |
| 107 | + .with_children(|parent| { |
| 108 | + parent.spawn(TextBundle::from_section( |
| 109 | + vec![ |
| 110 | + "Toggle Overflow (O)", |
| 111 | + "Next Container Size (S)", |
| 112 | + "Toggle Animation (space)", |
| 113 | + ] |
| 114 | + .join(" · "), |
| 115 | + TextStyle { |
| 116 | + font: asset_server.load("fonts/FiraSans-Bold.ttf"), |
| 117 | + font_size: 18.0, |
| 118 | + color: Color::WHITE, |
| 119 | + }, |
| 120 | + )); |
| 121 | + }); |
| 122 | + |
| 123 | + parent |
| 124 | + .spawn(NodeBundle { |
| 125 | + style: Style { |
| 126 | + flex_grow: 1., |
| 127 | + flex_direction: FlexDirection::Column, |
| 128 | + ..default() |
| 129 | + }, |
| 130 | + ..default() |
| 131 | + }) |
| 132 | + .with_children(|parent| { |
| 133 | + spawn_row(parent, |parent| { |
| 134 | + spawn_image(parent, &asset_server, Move); |
| 135 | + spawn_image(parent, &asset_server, Scale); |
| 136 | + spawn_image(parent, &asset_server, Rotate); |
| 137 | + }); |
| 138 | + |
| 139 | + spawn_row(parent, |parent| { |
| 140 | + spawn_text(parent, &asset_server, Move); |
| 141 | + spawn_text(parent, &asset_server, Scale); |
| 142 | + spawn_text(parent, &asset_server, Rotate); |
| 143 | + }); |
| 144 | + }); |
| 145 | + }); |
| 146 | +} |
| 147 | + |
| 148 | +fn spawn_row(parent: &mut ChildBuilder, spawn_children: impl FnOnce(&mut ChildBuilder)) { |
| 149 | + parent |
| 150 | + .spawn(NodeBundle { |
| 151 | + style: Style { |
| 152 | + size: Size::width(Val::Percent(50.)), |
| 153 | + align_items: AlignItems::Center, |
| 154 | + justify_content: JustifyContent::SpaceEvenly, |
| 155 | + ..default() |
| 156 | + }, |
| 157 | + ..default() |
| 158 | + }) |
| 159 | + .with_children(spawn_children); |
| 160 | +} |
| 161 | + |
| 162 | +fn spawn_image( |
| 163 | + parent: &mut ChildBuilder, |
| 164 | + asset_server: &Res<AssetServer>, |
| 165 | + update_transform: impl UpdateTransform + Component, |
| 166 | +) { |
| 167 | + spawn_container(parent, update_transform, |parent| { |
| 168 | + parent.spawn(ImageBundle { |
| 169 | + image: UiImage::new(asset_server.load("branding/bevy_logo_dark_big.png")), |
| 170 | + style: Style { |
| 171 | + size: Size::height(Val::Px(100.)), |
| 172 | + position_type: PositionType::Absolute, |
| 173 | + top: Val::Px(-50.), |
| 174 | + left: Val::Px(-200.), |
| 175 | + ..default() |
| 176 | + }, |
| 177 | + ..default() |
| 178 | + }); |
| 179 | + }); |
| 180 | +} |
| 181 | + |
| 182 | +fn spawn_text( |
| 183 | + parent: &mut ChildBuilder, |
| 184 | + asset_server: &Res<AssetServer>, |
| 185 | + update_transform: impl UpdateTransform + Component, |
| 186 | +) { |
| 187 | + spawn_container(parent, update_transform, |parent| { |
| 188 | + parent.spawn(TextBundle::from_section( |
| 189 | + "Bevy", |
| 190 | + TextStyle { |
| 191 | + font: asset_server.load("fonts/FiraSans-Bold.ttf"), |
| 192 | + font_size: 120.0, |
| 193 | + color: Color::WHITE, |
| 194 | + }, |
| 195 | + )); |
| 196 | + }); |
| 197 | +} |
| 198 | + |
| 199 | +fn spawn_container( |
| 200 | + parent: &mut ChildBuilder, |
| 201 | + update_transform: impl UpdateTransform + Component, |
| 202 | + spawn_children: impl FnOnce(&mut ChildBuilder), |
| 203 | +) { |
| 204 | + let mut transform = Transform::default(); |
| 205 | + |
| 206 | + update_transform.update(0.0, &mut transform); |
| 207 | + |
| 208 | + parent |
| 209 | + .spawn(( |
| 210 | + NodeBundle { |
| 211 | + style: Style { |
| 212 | + size: Size::new(Val::Px(CONTAINER_SIZE), Val::Px(CONTAINER_SIZE)), |
| 213 | + align_items: AlignItems::Center, |
| 214 | + justify_content: JustifyContent::Center, |
| 215 | + overflow: Overflow::Hidden, |
| 216 | + ..default() |
| 217 | + }, |
| 218 | + background_color: Color::DARK_GRAY.into(), |
| 219 | + ..default() |
| 220 | + }, |
| 221 | + Container(0), |
| 222 | + )) |
| 223 | + .with_children(|parent| { |
| 224 | + parent |
| 225 | + .spawn(( |
| 226 | + NodeBundle { |
| 227 | + style: Style { |
| 228 | + align_items: AlignItems::Center, |
| 229 | + justify_content: JustifyContent::Center, |
| 230 | + top: Val::Px(transform.translation.x), |
| 231 | + left: Val::Px(transform.translation.y), |
| 232 | + ..default() |
| 233 | + }, |
| 234 | + transform, |
| 235 | + ..default() |
| 236 | + }, |
| 237 | + update_transform, |
| 238 | + )) |
| 239 | + .with_children(spawn_children); |
| 240 | + }); |
| 241 | +} |
| 242 | + |
| 243 | +fn update_animation( |
| 244 | + mut animation: ResMut<AnimationState>, |
| 245 | + time: Res<Time>, |
| 246 | + keys: Res<Input<KeyCode>>, |
| 247 | +) { |
| 248 | + let time = time.elapsed_seconds(); |
| 249 | + |
| 250 | + if keys.just_pressed(KeyCode::Space) { |
| 251 | + animation.playing = !animation.playing; |
| 252 | + |
| 253 | + if !animation.playing { |
| 254 | + animation.paused_at = time; |
| 255 | + } else { |
| 256 | + animation.paused_total += time - animation.paused_at; |
| 257 | + } |
| 258 | + } |
| 259 | + |
| 260 | + if animation.playing { |
| 261 | + animation.t = (time - animation.paused_total) % LOOP_LENGTH / LOOP_LENGTH; |
| 262 | + } |
| 263 | +} |
| 264 | + |
| 265 | +fn update_transform<T: UpdateTransform + Component>( |
| 266 | + animation: Res<AnimationState>, |
| 267 | + mut containers: Query<(&mut Transform, &mut Style, &T)>, |
| 268 | +) { |
| 269 | + for (mut transform, mut style, update_transform) in &mut containers { |
| 270 | + update_transform.update(animation.t, &mut transform); |
| 271 | + |
| 272 | + style.left = Val::Px(transform.translation.x); |
| 273 | + style.top = Val::Px(transform.translation.y); |
| 274 | + } |
| 275 | +} |
| 276 | + |
| 277 | +fn toggle_overflow(keys: Res<Input<KeyCode>>, mut containers: Query<&mut Style, With<Container>>) { |
| 278 | + if keys.just_pressed(KeyCode::O) { |
| 279 | + for mut style in &mut containers { |
| 280 | + style.overflow = match style.overflow { |
| 281 | + Overflow::Visible => Overflow::Hidden, |
| 282 | + Overflow::Hidden => Overflow::Visible, |
| 283 | + }; |
| 284 | + } |
| 285 | + } |
| 286 | +} |
| 287 | + |
| 288 | +fn next_container_size( |
| 289 | + keys: Res<Input<KeyCode>>, |
| 290 | + mut containers: Query<(&mut Style, &mut Container)>, |
| 291 | +) { |
| 292 | + if keys.just_pressed(KeyCode::S) { |
| 293 | + for (mut style, mut container) in &mut containers { |
| 294 | + container.0 = (container.0 + 1) % 3; |
| 295 | + |
| 296 | + style.size = match container.0 { |
| 297 | + 1 => Size::new(Val::Px(CONTAINER_SIZE), Val::Px(30.)), |
| 298 | + 2 => Size::new(Val::Px(30.), Val::Px(CONTAINER_SIZE)), |
| 299 | + _ => Size::new(Val::Px(CONTAINER_SIZE), Val::Px(CONTAINER_SIZE)), |
| 300 | + }; |
| 301 | + } |
| 302 | + } |
| 303 | +} |
0 commit comments