bevy/examples/ui/overflow_debug.rs
Carter Anderson 015f2c69ca
Merge Style properties into Node. Use ComputedNode for computed properties. (#15975)
# Objective

Continue improving the user experience of our UI Node API in the
direction specified by [Bevy's Next Generation Scene / UI
System](https://github.com/bevyengine/bevy/discussions/14437)

## Solution

As specified in the document above, merge `Style` fields into `Node`,
and move "computed Node fields" into `ComputedNode` (I chose this name
over something like `ComputedNodeLayout` because it currently contains
more than just layout info. If we want to break this up / rename these
concepts, lets do that in a separate PR). `Style` has been removed.

This accomplishes a number of goals:

## Ergonomics wins

Specifying both `Node` and `Style` is now no longer required for
non-default styles

Before:
```rust
commands.spawn((
    Node::default(),
    Style {
        width:  Val::Px(100.),
        ..default()
    },
));
```

After:

```rust
commands.spawn(Node {
    width:  Val::Px(100.),
    ..default()
});
```

## Conceptual clarity

`Style` was never a comprehensive "style sheet". It only defined "core"
style properties that all `Nodes` shared. Any "styled property" that
couldn't fit that mold had to be in a separate component. A "real" style
system would style properties _across_ components (`Node`, `Button`,
etc). We have plans to build a true style system (see the doc linked
above).

By moving the `Style` fields to `Node`, we fully embrace `Node` as the
driving concept and remove the "style system" confusion.

## Next Steps

* Consider identifying and splitting out "style properties that aren't
core to Node". This should not happen for Bevy 0.15.

---

## Migration Guide

Move any fields set on `Style` into `Node` and replace all `Style`
component usage with `Node`.

Before:
```rust
commands.spawn((
    Node::default(),
    Style {
        width:  Val::Px(100.),
        ..default()
    },
));
```

After:

```rust
commands.spawn(Node {
    width:  Val::Px(100.),
    ..default()
});
```

For any usage of the "computed node properties" that used to live on
`Node`, use `ComputedNode` instead:

Before:
```rust
fn system(nodes: Query<&Node>) {
    for node in &nodes {
        let computed_size = node.size();
    }
}
```

After:
```rust
fn system(computed_nodes: Query<&ComputedNode>) {
    for computed_node in &computed_nodes {
        let computed_size = computed_node.size();
    }
}
```
2024-10-18 22:25:33 +00:00

286 lines
8.2 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((
UiImage::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, &T)>,
) {
for (mut transform, mut node, update_transform) in &mut containers {
update_transform.update(animation.t, &mut transform);
node.left = Val::Px(transform.translation.x);
node.top = Val::Px(transform.translation.y);
}
}
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.),
};
}
}