mirror of
https://github.com/bevyengine/bevy
synced 2024-12-25 20:43:07 +00:00
8a3a8b5cfb
# Objective We switch back and forwards between logical and physical coordinates all over the place. Systems have to query for cameras and the UiScale when they shouldn't need to. It's confusing and fragile and new scale factor bugs get found constantly. ## Solution * Use physical coordinates whereever possible in `bevy_ui`. * Store physical coords in `ComputedNode` and tear out all the unneeded scale factor calculations and queries. * Add an `inverse_scale_factor` field to `ComputedNode` and set nodes changed when their scale factor changes. ## Migration Guide `ComputedNode`'s fields and methods now use physical coordinates. `ComputedNode` has a new field `inverse_scale_factor`. Multiplying the physical coordinates by the `inverse_scale_factor` will give the logical values. --------- Co-authored-by: atlv <email@atlasdostal.com>
286 lines
8.3 KiB
Rust
286 lines
8.3 KiB
Rust
//! Tests how different transforms behave when clipped with `Overflow::Hidden`
|
|
|
|
use bevy::{input::common_conditions::input_just_pressed, prelude::*, ui::widget::TextUiWriter};
|
|
use std::f32::consts::{FRAC_PI_2, PI, TAU};
|
|
|
|
const CONTAINER_SIZE: f32 = 150.0;
|
|
const HALF_CONTAINER_SIZE: f32 = CONTAINER_SIZE / 2.0;
|
|
const LOOP_LENGTH: f32 = 4.0;
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_plugins(DefaultPlugins)
|
|
.init_resource::<AnimationState>()
|
|
.add_systems(Startup, setup)
|
|
.add_systems(
|
|
Update,
|
|
(
|
|
toggle_overflow.run_if(input_just_pressed(KeyCode::KeyO)),
|
|
next_container_size.run_if(input_just_pressed(KeyCode::KeyS)),
|
|
update_transform::<Move>,
|
|
update_transform::<Scale>,
|
|
update_transform::<Rotate>,
|
|
update_animation,
|
|
),
|
|
)
|
|
.run();
|
|
}
|
|
|
|
#[derive(Component)]
|
|
struct Instructions;
|
|
|
|
#[derive(Resource, Default)]
|
|
struct AnimationState {
|
|
playing: bool,
|
|
paused_at: f32,
|
|
paused_total: f32,
|
|
t: f32,
|
|
}
|
|
|
|
#[derive(Component)]
|
|
struct Container(u8);
|
|
|
|
trait UpdateTransform {
|
|
fn update(&self, t: f32, transform: &mut Transform);
|
|
}
|
|
|
|
#[derive(Component)]
|
|
struct Move;
|
|
|
|
impl UpdateTransform for Move {
|
|
fn update(&self, t: f32, transform: &mut Transform) {
|
|
transform.translation.x = ops::sin(t * TAU - FRAC_PI_2) * HALF_CONTAINER_SIZE;
|
|
transform.translation.y = -ops::cos(t * TAU - FRAC_PI_2) * HALF_CONTAINER_SIZE;
|
|
}
|
|
}
|
|
|
|
#[derive(Component)]
|
|
struct Scale;
|
|
|
|
impl UpdateTransform for Scale {
|
|
fn update(&self, t: f32, transform: &mut Transform) {
|
|
transform.scale.x = 1.0 + 0.5 * ops::cos(t * TAU).max(0.0);
|
|
transform.scale.y = 1.0 + 0.5 * ops::cos(t * TAU + PI).max(0.0);
|
|
}
|
|
}
|
|
|
|
#[derive(Component)]
|
|
struct Rotate;
|
|
|
|
impl UpdateTransform for Rotate {
|
|
fn update(&self, t: f32, transform: &mut Transform) {
|
|
transform.rotation =
|
|
Quat::from_axis_angle(Vec3::Z, (ops::cos(t * TAU) * 45.0).to_radians());
|
|
}
|
|
}
|
|
|
|
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|
// Camera
|
|
|
|
commands.spawn(Camera2d);
|
|
|
|
// Instructions
|
|
|
|
let text_font = TextFont::default();
|
|
|
|
commands
|
|
.spawn((
|
|
Text::new(
|
|
"Next Overflow Setting (O)\nNext Container Size (S)\nToggle Animation (space)\n\n",
|
|
),
|
|
text_font.clone(),
|
|
Node {
|
|
position_type: PositionType::Absolute,
|
|
top: Val::Px(12.0),
|
|
left: Val::Px(12.0),
|
|
..default()
|
|
},
|
|
Instructions,
|
|
))
|
|
.with_child((
|
|
TextSpan::new(format!("{:?}", Overflow::clip())),
|
|
text_font.clone(),
|
|
));
|
|
|
|
// Overflow Debug
|
|
|
|
commands
|
|
.spawn(Node {
|
|
width: Val::Percent(100.),
|
|
height: Val::Percent(100.),
|
|
justify_content: JustifyContent::Center,
|
|
align_items: AlignItems::Center,
|
|
..default()
|
|
})
|
|
.with_children(|parent| {
|
|
parent
|
|
.spawn(Node {
|
|
display: Display::Grid,
|
|
grid_template_columns: RepeatedGridTrack::px(3, CONTAINER_SIZE),
|
|
grid_template_rows: RepeatedGridTrack::px(2, CONTAINER_SIZE),
|
|
row_gap: Val::Px(80.),
|
|
column_gap: Val::Px(80.),
|
|
..default()
|
|
})
|
|
.with_children(|parent| {
|
|
spawn_image(parent, &asset_server, Move);
|
|
spawn_image(parent, &asset_server, Scale);
|
|
spawn_image(parent, &asset_server, Rotate);
|
|
|
|
spawn_text(parent, &asset_server, Move);
|
|
spawn_text(parent, &asset_server, Scale);
|
|
spawn_text(parent, &asset_server, Rotate);
|
|
});
|
|
});
|
|
}
|
|
|
|
fn spawn_image(
|
|
parent: &mut ChildBuilder,
|
|
asset_server: &Res<AssetServer>,
|
|
update_transform: impl UpdateTransform + Component,
|
|
) {
|
|
spawn_container(parent, update_transform, |parent| {
|
|
parent.spawn((
|
|
ImageNode::new(asset_server.load("branding/bevy_logo_dark_big.png")),
|
|
Node {
|
|
height: Val::Px(100.),
|
|
position_type: PositionType::Absolute,
|
|
top: Val::Px(-50.),
|
|
left: Val::Px(-200.),
|
|
..default()
|
|
},
|
|
));
|
|
});
|
|
}
|
|
|
|
fn spawn_text(
|
|
parent: &mut ChildBuilder,
|
|
asset_server: &Res<AssetServer>,
|
|
update_transform: impl UpdateTransform + Component,
|
|
) {
|
|
spawn_container(parent, update_transform, |parent| {
|
|
parent.spawn((
|
|
Text::new("Bevy"),
|
|
TextFont {
|
|
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
|
font_size: 100.0,
|
|
..default()
|
|
},
|
|
));
|
|
});
|
|
}
|
|
|
|
fn spawn_container(
|
|
parent: &mut ChildBuilder,
|
|
update_transform: impl UpdateTransform + Component,
|
|
spawn_children: impl FnOnce(&mut ChildBuilder),
|
|
) {
|
|
let mut transform = Transform::default();
|
|
|
|
update_transform.update(0.0, &mut transform);
|
|
|
|
parent
|
|
.spawn((
|
|
Node {
|
|
width: Val::Percent(100.),
|
|
height: Val::Percent(100.),
|
|
align_items: AlignItems::Center,
|
|
justify_content: JustifyContent::Center,
|
|
overflow: Overflow::clip(),
|
|
..default()
|
|
},
|
|
BackgroundColor(Color::srgb(0.25, 0.25, 0.25)),
|
|
Container(0),
|
|
))
|
|
.with_children(|parent| {
|
|
parent
|
|
.spawn((
|
|
Node {
|
|
align_items: AlignItems::Center,
|
|
justify_content: JustifyContent::Center,
|
|
top: Val::Px(transform.translation.x),
|
|
left: Val::Px(transform.translation.y),
|
|
..default()
|
|
},
|
|
transform,
|
|
update_transform,
|
|
))
|
|
.with_children(spawn_children);
|
|
});
|
|
}
|
|
|
|
fn update_animation(
|
|
mut animation: ResMut<AnimationState>,
|
|
time: Res<Time>,
|
|
keys: Res<ButtonInput<KeyCode>>,
|
|
) {
|
|
let delta = time.elapsed_secs();
|
|
|
|
if keys.just_pressed(KeyCode::Space) {
|
|
animation.playing = !animation.playing;
|
|
|
|
if !animation.playing {
|
|
animation.paused_at = delta;
|
|
} else {
|
|
animation.paused_total += delta - animation.paused_at;
|
|
}
|
|
}
|
|
|
|
if animation.playing {
|
|
animation.t = (delta - animation.paused_total) % LOOP_LENGTH / LOOP_LENGTH;
|
|
}
|
|
}
|
|
|
|
fn update_transform<T: UpdateTransform + Component>(
|
|
animation: Res<AnimationState>,
|
|
mut containers: Query<(&mut Transform, &mut Node, &ComputedNode, &T)>,
|
|
) {
|
|
for (mut transform, mut node, computed_node, update_transform) in &mut containers {
|
|
update_transform.update(animation.t, &mut transform);
|
|
|
|
node.left = Val::Px(transform.translation.x * computed_node.inverse_scale_factor());
|
|
node.top = Val::Px(transform.translation.y * computed_node.inverse_scale_factor());
|
|
}
|
|
}
|
|
|
|
fn toggle_overflow(
|
|
mut containers: Query<&mut Node, With<Container>>,
|
|
instructions: Single<Entity, With<Instructions>>,
|
|
mut writer: TextUiWriter,
|
|
) {
|
|
for mut node in &mut containers {
|
|
node.overflow = match node.overflow {
|
|
Overflow {
|
|
x: OverflowAxis::Visible,
|
|
y: OverflowAxis::Visible,
|
|
} => Overflow::clip_y(),
|
|
Overflow {
|
|
x: OverflowAxis::Visible,
|
|
y: OverflowAxis::Clip,
|
|
} => Overflow::clip_x(),
|
|
Overflow {
|
|
x: OverflowAxis::Clip,
|
|
y: OverflowAxis::Visible,
|
|
} => Overflow::clip(),
|
|
_ => Overflow::visible(),
|
|
};
|
|
|
|
let entity = *instructions;
|
|
*writer.text(entity, 1) = format!("{:?}", node.overflow);
|
|
}
|
|
}
|
|
|
|
fn next_container_size(mut containers: Query<(&mut Node, &mut Container)>) {
|
|
for (mut node, mut container) in &mut containers {
|
|
container.0 = (container.0 + 1) % 3;
|
|
|
|
node.width = match container.0 {
|
|
2 => Val::Percent(30.),
|
|
_ => Val::Percent(100.),
|
|
};
|
|
node.height = match container.0 {
|
|
1 => Val::Percent(30.),
|
|
_ => Val::Percent(100.),
|
|
};
|
|
}
|
|
}
|