mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 15:14:50 +00:00
many_buttons
enhancements (#9712)
# Objective `many_buttons` enhancements: * use `argh` to manage the commandline arguments like the other stress tests * add an option to set the number of buttons * add a grid layout option * centre the grid properly * use viewport coords for the layout's style constraints * replace use of absolute positioning includes the changes from #9636 Displaying an image isn't actually about stress testing image rendering. Without a second texture (the first is used by the text) the entire grid will be drawn in a single batch. The extra texture used by the image forces the renderer to break up the batches at every button displaying an image, where it has to switch between the font atlas texture and the image texture. ## Solution <img width="401" alt="many_buttons_new" src="https://github.com/bevyengine/bevy/assets/27962798/82140c6d-d72c-4e4f-b9b6-dd204176e51d"> --- ## Changelog `many_buttons` stress test example enhancements: * uses `argh` to the manage the commandline arguments. * New commandline args: - `--help` display info & list all commandline options - `--buttons` set the number of buttons. - `--image-freq` set the frequency of buttons displaying images - `--grid` use a grid layout * style constraints are specified in viewport coords insead of percentage values * margins and nested bundles are used to construct the layout, instead of absolute positioning * the button grid centered in the window, the empty gap along the bottom and right is removed * an image is drawn as the background to every Nth button where N is set using the `--image-freq` commandline option. --------- Co-authored-by: Rob Parrett <robparrett@gmail.com>
This commit is contained in:
parent
c8f61c3963
commit
73447b6d72
1 changed files with 155 additions and 58 deletions
|
@ -1,29 +1,48 @@
|
|||
//! This example shows what happens when there is a lot of buttons on screen.
|
||||
//!
|
||||
//! To start the demo without text run
|
||||
//! `cargo run --example many_buttons --release no-text`
|
||||
//!
|
||||
//! //! To start the demo without borders run
|
||||
//! `cargo run --example many_buttons --release no-borders`
|
||||
//!
|
||||
//| To do a full layout update each frame run
|
||||
//! `cargo run --example many_buttons --release recompute-layout`
|
||||
//!
|
||||
//! To recompute all text each frame run
|
||||
//! `cargo run --example many_buttons --release recompute-text`
|
||||
|
||||
/// General UI benchmark that stress tests layouting, text, interaction and rendering
|
||||
use argh::FromArgs;
|
||||
use bevy::{
|
||||
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||
prelude::*,
|
||||
window::{PresentMode, WindowPlugin},
|
||||
};
|
||||
|
||||
// For a total of 110 * 110 = 12100 buttons with text
|
||||
const ROW_COLUMN_COUNT: usize = 110;
|
||||
const FONT_SIZE: f32 = 7.0;
|
||||
|
||||
#[derive(FromArgs, Resource)]
|
||||
/// `many_buttons` general UI benchmark that stress tests layouting, text, interaction and rendering
|
||||
struct Args {
|
||||
/// whether to add text to each button
|
||||
#[argh(switch)]
|
||||
no_text: bool,
|
||||
|
||||
/// whether to add borders to each button
|
||||
#[argh(switch)]
|
||||
no_borders: bool,
|
||||
|
||||
/// whether to perform a full relayout each frame
|
||||
#[argh(switch)]
|
||||
relayout: bool,
|
||||
|
||||
/// whether to recompute all text each frame
|
||||
#[argh(switch)]
|
||||
recompute_text: bool,
|
||||
|
||||
/// how many buttons per row and column of the grid.
|
||||
#[argh(option, default = "110")]
|
||||
buttons: usize,
|
||||
|
||||
/// give every nth button an image
|
||||
#[argh(option, default = "4")]
|
||||
image_freq: usize,
|
||||
|
||||
/// use the grid layout model
|
||||
#[argh(switch)]
|
||||
grid: bool,
|
||||
}
|
||||
|
||||
/// This example shows what happens when there is a lot of buttons on screen.
|
||||
fn main() {
|
||||
let args: Args = argh::from_env();
|
||||
let mut app = App::new();
|
||||
|
||||
app.add_plugins((
|
||||
|
@ -37,22 +56,27 @@ fn main() {
|
|||
FrameTimeDiagnosticsPlugin,
|
||||
LogDiagnosticsPlugin::default(),
|
||||
))
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, button_system);
|
||||
|
||||
if std::env::args().any(|arg| arg == "recompute-layout") {
|
||||
app.add_systems(Update, |mut ui_scale: ResMut<UiScale>| {
|
||||
ui_scale.set_changed();
|
||||
if args.grid {
|
||||
app.add_systems(Startup, setup_grid);
|
||||
} else {
|
||||
app.add_systems(Startup, setup_flex);
|
||||
}
|
||||
|
||||
if args.relayout {
|
||||
app.add_systems(Update, |mut style_query: Query<&mut Style>| {
|
||||
style_query.for_each_mut(|mut style| style.set_changed());
|
||||
});
|
||||
}
|
||||
|
||||
if std::env::args().any(|arg| arg == "recompute-text") {
|
||||
if args.recompute_text {
|
||||
app.add_systems(Update, |mut text_query: Query<&mut Text>| {
|
||||
text_query.for_each_mut(|mut text| text.set_changed());
|
||||
});
|
||||
}
|
||||
|
||||
app.run();
|
||||
app.insert_resource(args).run();
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
|
@ -64,50 +88,117 @@ fn button_system(
|
|||
Changed<Interaction>,
|
||||
>,
|
||||
) {
|
||||
for (interaction, mut material, IdleColor(idle_color)) in interaction_query.iter_mut() {
|
||||
if matches!(interaction, Interaction::Hovered) {
|
||||
*material = Color::ORANGE_RED.into();
|
||||
} else {
|
||||
*material = *idle_color;
|
||||
}
|
||||
for (interaction, mut button_color, IdleColor(idle_color)) in interaction_query.iter_mut() {
|
||||
*button_color = match interaction {
|
||||
Interaction::Hovered => Color::ORANGE_RED.into(),
|
||||
_ => *idle_color,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands) {
|
||||
fn setup_flex(mut commands: Commands, asset_server: Res<AssetServer>, args: Res<Args>) {
|
||||
warn!(include_str!("warning_string.txt"));
|
||||
let image = if 0 < args.image_freq {
|
||||
Some(asset_server.load("branding/icon.png"))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let count = ROW_COLUMN_COUNT;
|
||||
let count_f = count as f32;
|
||||
let as_rainbow = |i: usize| Color::hsl((i as f32 / count_f) * 360.0, 0.9, 0.8);
|
||||
let buttons_f = args.buttons as f32;
|
||||
let border = if args.no_borders {
|
||||
UiRect::ZERO
|
||||
} else {
|
||||
UiRect::all(Val::VMin(0.05 * 90. / buttons_f))
|
||||
};
|
||||
|
||||
let as_rainbow = |i: usize| Color::hsl((i as f32 / buttons_f) * 360.0, 0.9, 0.8);
|
||||
commands.spawn(Camera2dBundle::default());
|
||||
commands
|
||||
.spawn(NodeBundle {
|
||||
style: Style {
|
||||
flex_direction: FlexDirection::Column,
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
width: Val::Percent(100.),
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
})
|
||||
.with_children(|commands| {
|
||||
let spawn_text = std::env::args().all(|arg| arg != "no-text");
|
||||
let border = if std::env::args().all(|arg| arg != "no-borders") {
|
||||
UiRect::all(Val::Percent(10. / count_f))
|
||||
} else {
|
||||
UiRect::DEFAULT
|
||||
};
|
||||
for i in 0..count {
|
||||
for j in 0..count {
|
||||
let color = as_rainbow(j % i.max(1)).into();
|
||||
let border_color = as_rainbow(i % j.max(1)).into();
|
||||
for column in 0..args.buttons {
|
||||
commands
|
||||
.spawn(NodeBundle::default())
|
||||
.with_children(|commands| {
|
||||
for row in 0..args.buttons {
|
||||
let color = as_rainbow(row % column.max(1)).into();
|
||||
let border_color = Color::WHITE.with_a(0.5).into();
|
||||
spawn_button(
|
||||
commands,
|
||||
color,
|
||||
count_f,
|
||||
i,
|
||||
j,
|
||||
spawn_text,
|
||||
buttons_f,
|
||||
column,
|
||||
row,
|
||||
!args.no_text,
|
||||
border,
|
||||
border_color,
|
||||
image
|
||||
.as_ref()
|
||||
.filter(|_| (column + row) % args.image_freq == 0)
|
||||
.cloned(),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn setup_grid(mut commands: Commands, asset_server: Res<AssetServer>, args: Res<Args>) {
|
||||
warn!(include_str!("warning_string.txt"));
|
||||
let image = if 0 < args.image_freq {
|
||||
Some(asset_server.load("branding/icon.png"))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let buttons_f = args.buttons as f32;
|
||||
let border = if args.no_borders {
|
||||
UiRect::ZERO
|
||||
} else {
|
||||
UiRect::all(Val::VMin(0.05 * 90. / buttons_f))
|
||||
};
|
||||
|
||||
let as_rainbow = |i: usize| Color::hsl((i as f32 / buttons_f) * 360.0, 0.9, 0.8);
|
||||
commands.spawn(Camera2dBundle::default());
|
||||
commands
|
||||
.spawn(NodeBundle {
|
||||
style: Style {
|
||||
display: Display::Grid,
|
||||
width: Val::Percent(100.),
|
||||
height: Val::Percent(100.0),
|
||||
grid_template_columns: RepeatedGridTrack::flex(args.buttons as u16, 1.0),
|
||||
grid_template_rows: RepeatedGridTrack::flex(args.buttons as u16, 1.0),
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
})
|
||||
.with_children(|commands| {
|
||||
for column in 0..args.buttons {
|
||||
for row in 0..args.buttons {
|
||||
let color = as_rainbow(row % column.max(1)).into();
|
||||
let border_color = Color::WHITE.with_a(0.5).into();
|
||||
spawn_button(
|
||||
commands,
|
||||
color,
|
||||
buttons_f,
|
||||
column,
|
||||
row,
|
||||
!args.no_text,
|
||||
border,
|
||||
border_color,
|
||||
image
|
||||
.as_ref()
|
||||
.filter(|_| (column + row) % args.image_freq == 0)
|
||||
.cloned(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -118,23 +209,25 @@ fn setup(mut commands: Commands) {
|
|||
fn spawn_button(
|
||||
commands: &mut ChildBuilder,
|
||||
background_color: BackgroundColor,
|
||||
total: f32,
|
||||
i: usize,
|
||||
j: usize,
|
||||
buttons: f32,
|
||||
column: usize,
|
||||
row: usize,
|
||||
spawn_text: bool,
|
||||
border: UiRect,
|
||||
border_color: BorderColor,
|
||||
image: Option<Handle<Image>>,
|
||||
) {
|
||||
let width = 90.0 / total;
|
||||
let width = Val::Vw(90.0 / buttons);
|
||||
let height = Val::Vh(90.0 / buttons);
|
||||
let margin = UiRect::axes(width * 0.05, height * 0.05);
|
||||
let mut builder = commands.spawn((
|
||||
ButtonBundle {
|
||||
style: Style {
|
||||
width: Val::Percent(width),
|
||||
height: Val::Percent(width),
|
||||
bottom: Val::Percent(100.0 / total * i as f32),
|
||||
left: Val::Percent(100.0 / total * j as f32),
|
||||
width,
|
||||
height,
|
||||
margin,
|
||||
align_items: AlignItems::Center,
|
||||
position_type: PositionType::Absolute,
|
||||
justify_content: JustifyContent::Center,
|
||||
border,
|
||||
..default()
|
||||
},
|
||||
|
@ -145,10 +238,14 @@ fn spawn_button(
|
|||
IdleColor(background_color),
|
||||
));
|
||||
|
||||
if let Some(image) = image {
|
||||
builder.insert(UiImage::new(image));
|
||||
}
|
||||
|
||||
if spawn_text {
|
||||
builder.with_children(|commands| {
|
||||
commands.spawn(TextBundle::from_section(
|
||||
format!("{i}, {j}"),
|
||||
builder.with_children(|parent| {
|
||||
parent.spawn(TextBundle::from_section(
|
||||
format!("{column}, {row}"),
|
||||
TextStyle {
|
||||
font_size: FONT_SIZE,
|
||||
color: Color::rgb(0.2, 0.2, 0.2),
|
||||
|
|
Loading…
Reference in a new issue