Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add overflow_debug example #8198

Merged
merged 5 commits into from
Apr 5, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1749,6 +1749,16 @@ description = "Illustrates how FontAtlases are populated (used to optimize text
category = "UI (User Interface)"
wasm = true

[[example]]
name = "overflow_debug"
path = "examples/ui/overflow_debug.rs"

[package.metadata.example.overflow_debug]
name = "Overflow and Clipping Debug"
description = "An example to debug overflow and clipping behavior"
category = "UI (User Interface)"
wasm = true

[[example]]
name = "relative_cursor_position"
path = "examples/ui/relative_cursor_position.rs"
Expand Down
303 changes: 303 additions & 0 deletions examples/ui/overflow_debug.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
//! Tests how different transforms behave when clipped with `Overflow::Hidden`
use bevy::prelude::*;
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)
// TODO: Remove once #8144 is fixed
.insert_resource(GizmoConfig {
enabled: false,
..default()
})
.insert_resource(AnimationState {
playing: false,
paused_at: 0.0,
paused_total: 0.0,
t: 0.0,
})
.add_systems(Startup, setup)
.add_systems(
Update,
(
toggle_overflow,
next_container_size,
update_transform::<Move>,
update_transform::<Scale>,
update_transform::<Rotate>,
update_animation,
),
)
.run();
}

#[derive(Resource)]
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 = (t * TAU - FRAC_PI_2).sin() * HALF_CONTAINER_SIZE;
transform.translation.y = -(t * TAU - FRAC_PI_2).cos() * 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 * (t * TAU).cos().max(0.0);
transform.scale.y = 1.0 + 0.5 * (t * TAU + PI).cos().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, ((t * TAU).cos() * 45.0).to_radians());
}
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Camera
commands.spawn(Camera2dBundle::default());

commands
.spawn(NodeBundle {
style: Style {
size: Size::new(Val::Percent(100.), Val::Percent(100.)),
cart marked this conversation as resolved.
Show resolved Hide resolved
flex_direction: FlexDirection::Column,
..default()
},
..default()
})
.with_children(|parent| {
parent
.spawn(NodeBundle {
style: Style {
size: Size::height(Val::Px(32.)),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
},
background_color: Color::DARK_GRAY.into(),
..default()
})
.with_children(|parent| {
parent.spawn(TextBundle::from_section(
vec![
"Toggle Overflow (O)",
"Next Container Size (S)",
"Toggle Animation (space)",
]
.join(" · "),
TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 18.0,
color: Color::WHITE,
},
));
});

parent
.spawn(NodeBundle {
style: Style {
flex_grow: 1.,
flex_direction: FlexDirection::Column,
..default()
},
..default()
})
.with_children(|parent| {
spawn_row(parent, |parent| {
spawn_image(parent, &asset_server, Move);
spawn_image(parent, &asset_server, Scale);
spawn_image(parent, &asset_server, Rotate);
});

spawn_row(parent, |parent| {
spawn_text(parent, &asset_server, Move);
spawn_text(parent, &asset_server, Scale);
spawn_text(parent, &asset_server, Rotate);
});
});
});
}

fn spawn_row(parent: &mut ChildBuilder, spawn_children: impl FnOnce(&mut ChildBuilder)) {
parent
.spawn(NodeBundle {
style: Style {
size: Size::new(Val::Percent(100.), Val::Percent(50.)),
cart marked this conversation as resolved.
Show resolved Hide resolved
align_items: AlignItems::Center,
justify_content: JustifyContent::SpaceEvenly,
..default()
},
..default()
})
.with_children(spawn_children);
}

fn spawn_image(
parent: &mut ChildBuilder,
asset_server: &Res<AssetServer>,
update_transform: impl UpdateTransform + Component,
) {
spawn_container(parent, update_transform, |parent| {
parent.spawn(ImageBundle {
image: UiImage::new(asset_server.load("branding/bevy_logo_dark_big.png")),
style: Style {
size: Size::new(Val::Auto, Val::Px(100.)),
cart marked this conversation as resolved.
Show resolved Hide resolved
position_type: PositionType::Absolute,
top: Val::Px(-50.),
left: Val::Px(-200.),
..default()
},
..default()
});
});
}

fn spawn_text(
parent: &mut ChildBuilder,
asset_server: &Res<AssetServer>,
update_transform: impl UpdateTransform + Component,
) {
spawn_container(parent, update_transform, |parent| {
parent.spawn(TextBundle::from_section(
"Bevy",
TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 120.0,
color: Color::WHITE,
},
));
});
}

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((
NodeBundle {
style: Style {
size: Size::new(Val::Px(CONTAINER_SIZE), Val::Px(CONTAINER_SIZE)),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
overflow: Overflow::Hidden,
..default()
},
background_color: Color::DARK_GRAY.into(),
..default()
},
Container(0),
))
.with_children(|parent| {
parent
.spawn((
NodeBundle {
style: Style {
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
top: Val::Px(transform.translation.x),
left: Val::Px(transform.translation.y),
..default()
},
transform,
..default()
},
update_transform,
))
.with_children(spawn_children);
});
}

fn update_animation(
mut animation: ResMut<AnimationState>,
time: Res<Time>,
keys: Res<Input<KeyCode>>,
) {
let time = time.elapsed_seconds();

if keys.just_pressed(KeyCode::Space) {
animation.playing = !animation.playing;

if !animation.playing {
animation.paused_at = time;
} else {
animation.paused_total += time - animation.paused_at;
}
}

if animation.playing {
animation.t = (time - animation.paused_total) % LOOP_LENGTH / LOOP_LENGTH;
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe needs some comments here because the plan has been to move away from using the Transform hierarchy for UI nodes. Also, the layout only supports rectangular axis-aligned UI nodes so modifying the Transform on nodes with children especially can just end up in a mess. So users need to be warned against naively applying arbitrary rotations and scaling like they would with sprites and expecting it to just work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, I know transform should be derived from style… but didn't find any other way to change scale/rotation. I wanted to force scale/rotation (translation it's used to update top/left) to see how it behaves.

Although, now that I think about it… maybe it doesn't make any sense to put these in the example if it's not a current feature. Or… it could be used to fix scale/rotation? Although, probably this should be done at shader/stencil/mask/whatever… level; using textures and composition and not by cutting geometry. (?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been working on #8240 which fixes some of the problems, but I'm not too sure about the API though.
It's not necessarily a problem that the example cases don't all display correctly, we could just put a WIP label or something. Users need to be aware that the clipping is very basic.

Copy link
Contributor

@ickshonpe ickshonpe Mar 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually thinking about it this will be really good to adapt to use as a test case for the UI Transform PR if you don't mind 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, I had a surgery a couple of days ago, I don't think I'll check the PRs anytime soon. Please fork it and do as you wish.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its reasonable to merge this as-is. Just make whatever changes are needed in #8240.

fn update_transform<T: UpdateTransform + Component>(
animation: Res<AnimationState>,
mut containers: Query<(&mut Transform, &mut Style, &T)>,
) {
for (mut transform, mut style, update_transform) in &mut containers {
update_transform.update(animation.t, &mut transform);

style.left = Val::Px(transform.translation.x);
style.top = Val::Px(transform.translation.y);
}
}

fn toggle_overflow(keys: Res<Input<KeyCode>>, mut containers: Query<&mut Style, With<Container>>) {
if keys.just_pressed(KeyCode::O) {
for mut style in &mut containers {
style.overflow = match style.overflow {
Overflow::Visible => Overflow::Hidden,
Overflow::Hidden => Overflow::Visible,
};
}
}
}

fn next_container_size(
keys: Res<Input<KeyCode>>,
mut containers: Query<(&mut Style, &mut Container)>,
) {
if keys.just_pressed(KeyCode::S) {
for (mut style, mut container) in &mut containers {
container.0 = (container.0 + 1) % 3;

style.size = match container.0 {
1 => Size::new(Val::Px(CONTAINER_SIZE), Val::Px(30.)),
2 => Size::new(Val::Px(30.), Val::Px(CONTAINER_SIZE)),
_ => Size::new(Val::Px(CONTAINER_SIZE), Val::Px(CONTAINER_SIZE)),
};
}
}
}