bevy/examples/ui/overflow_debug.rs
ickshonpe 8a3a8b5cfb
Only use physical coords internally in bevy_ui (#16375)
# 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>
2024-11-22 00:45:07 +00:00

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.),
};
}
}