1
- //! This example shows what happens when there is a lot of buttons on screen.
2
- //!
3
- //! To start the demo without text run
4
- //! `cargo run --example many_buttons --release no-text`
5
- //!
6
- //! //! To start the demo without borders run
7
- //! `cargo run --example many_buttons --release no-borders`
8
- //!
9
- //| To do a full layout update each frame run
10
- //! `cargo run --example many_buttons --release recompute-layout`
11
- //!
12
- //! To recompute all text each frame run
13
- //! `cargo run --example many_buttons --release recompute-text`
14
-
1
+ /// General UI benchmark that stress tests layouting, text, interaction and rendering
2
+ use argh:: FromArgs ;
15
3
use bevy:: {
16
4
diagnostic:: { FrameTimeDiagnosticsPlugin , LogDiagnosticsPlugin } ,
17
5
prelude:: * ,
18
6
window:: { PresentMode , WindowPlugin } ,
19
7
} ;
20
8
21
- // For a total of 110 * 110 = 12100 buttons with text
22
- const ROW_COLUMN_COUNT : usize = 110 ;
23
9
const FONT_SIZE : f32 = 7.0 ;
24
10
11
+ #[ derive( FromArgs , Resource ) ]
12
+ /// `many_buttons` general UI benchmark that stress tests layouting, text, interaction and rendering
13
+ struct Args {
14
+ /// whether to add text to each button
15
+ #[ argh( switch) ]
16
+ no_text : bool ,
17
+
18
+ /// whether to add borders to each button
19
+ #[ argh( switch) ]
20
+ no_borders : bool ,
21
+
22
+ /// whether to perform a full relayout each frame
23
+ #[ argh( switch) ]
24
+ relayout : bool ,
25
+
26
+ /// whether to recompute all text each frame
27
+ #[ argh( switch) ]
28
+ recompute_text : bool ,
29
+
30
+ /// how many buttons per row and column of the grid.
31
+ #[ argh( option, default = "110" ) ]
32
+ buttons : usize ,
33
+
34
+ /// give every nth button an image
35
+ #[ argh( option, default = "4" ) ]
36
+ image_freq : usize ,
37
+
38
+ /// use the grid layout model
39
+ #[ argh( switch) ]
40
+ grid : bool ,
41
+ }
42
+
25
43
/// This example shows what happens when there is a lot of buttons on screen.
26
44
fn main ( ) {
45
+ let args: Args = argh:: from_env ( ) ;
27
46
let mut app = App :: new ( ) ;
28
47
29
48
app. add_plugins ( (
@@ -37,22 +56,27 @@ fn main() {
37
56
FrameTimeDiagnosticsPlugin ,
38
57
LogDiagnosticsPlugin :: default ( ) ,
39
58
) )
40
- . add_systems ( Startup , setup)
41
59
. add_systems ( Update , button_system) ;
42
60
43
- if std:: env:: args ( ) . any ( |arg| arg == "recompute-layout" ) {
44
- app. add_systems ( Update , |mut ui_scale : ResMut < UiScale > | {
45
- ui_scale. set_changed ( ) ;
61
+ if args. grid {
62
+ app. add_systems ( Startup , setup_grid) ;
63
+ } else {
64
+ app. add_systems ( Startup , setup_flex) ;
65
+ }
66
+
67
+ if args. relayout {
68
+ app. add_systems ( Update , |mut style_query : Query < & mut Style > | {
69
+ style_query. for_each_mut ( |mut style| style. set_changed ( ) ) ;
46
70
} ) ;
47
71
}
48
72
49
- if std :: env :: args ( ) . any ( |arg| arg == "recompute-text" ) {
73
+ if args. recompute_text {
50
74
app. add_systems ( Update , |mut text_query : Query < & mut Text > | {
51
75
text_query. for_each_mut ( |mut text| text. set_changed ( ) ) ;
52
76
} ) ;
53
77
}
54
78
55
- app. run ( ) ;
79
+ app. insert_resource ( args ) . run ( ) ;
56
80
}
57
81
58
82
#[ derive( Component ) ]
@@ -64,50 +88,117 @@ fn button_system(
64
88
Changed < Interaction > ,
65
89
> ,
66
90
) {
67
- for ( interaction, mut material, IdleColor ( idle_color) ) in interaction_query. iter_mut ( ) {
68
- if matches ! ( interaction, Interaction :: Hovered ) {
69
- * material = Color :: ORANGE_RED . into ( ) ;
70
- } else {
71
- * material = * idle_color;
72
- }
91
+ for ( interaction, mut button_color, IdleColor ( idle_color) ) in interaction_query. iter_mut ( ) {
92
+ * button_color = match interaction {
93
+ Interaction :: Hovered => Color :: ORANGE_RED . into ( ) ,
94
+ _ => * idle_color,
95
+ } ;
73
96
}
74
97
}
75
98
76
- fn setup ( mut commands : Commands ) {
99
+ fn setup_flex ( mut commands : Commands , asset_server : Res < AssetServer > , args : Res < Args > ) {
100
+ warn ! ( include_str!( "warning_string.txt" ) ) ;
101
+ let image = if 0 < args. image_freq {
102
+ Some ( asset_server. load ( "branding/icon.png" ) )
103
+ } else {
104
+ None
105
+ } ;
106
+
107
+ let buttons_f = args. buttons as f32 ;
108
+ let border = if args. no_borders {
109
+ UiRect :: ZERO
110
+ } else {
111
+ UiRect :: all ( Val :: VMin ( 0.05 * 90. / buttons_f) )
112
+ } ;
113
+
114
+ let as_rainbow = |i : usize | Color :: hsl ( ( i as f32 / buttons_f) * 360.0 , 0.9 , 0.8 ) ;
115
+ commands. spawn ( Camera2dBundle :: default ( ) ) ;
116
+ commands
117
+ . spawn ( NodeBundle {
118
+ style : Style {
119
+ flex_direction : FlexDirection :: Column ,
120
+ justify_content : JustifyContent :: Center ,
121
+ align_items : AlignItems :: Center ,
122
+ width : Val :: Percent ( 100. ) ,
123
+ ..default ( )
124
+ } ,
125
+ ..default ( )
126
+ } )
127
+ . with_children ( |commands| {
128
+ for column in 0 ..args. buttons {
129
+ commands
130
+ . spawn ( NodeBundle :: default ( ) )
131
+ . with_children ( |commands| {
132
+ for row in 0 ..args. buttons {
133
+ let color = as_rainbow ( row % column. max ( 1 ) ) . into ( ) ;
134
+ let border_color = Color :: WHITE . with_a ( 0.5 ) . into ( ) ;
135
+ spawn_button (
136
+ commands,
137
+ color,
138
+ buttons_f,
139
+ column,
140
+ row,
141
+ !args. no_text ,
142
+ border,
143
+ border_color,
144
+ image
145
+ . as_ref ( )
146
+ . filter ( |_| ( column + row) % args. image_freq == 0 )
147
+ . cloned ( ) ,
148
+ ) ;
149
+ }
150
+ } ) ;
151
+ }
152
+ } ) ;
153
+ }
154
+
155
+ fn setup_grid ( mut commands : Commands , asset_server : Res < AssetServer > , args : Res < Args > ) {
77
156
warn ! ( include_str!( "warning_string.txt" ) ) ;
157
+ let image = if 0 < args. image_freq {
158
+ Some ( asset_server. load ( "branding/icon.png" ) )
159
+ } else {
160
+ None
161
+ } ;
162
+
163
+ let buttons_f = args. buttons as f32 ;
164
+ let border = if args. no_borders {
165
+ UiRect :: ZERO
166
+ } else {
167
+ UiRect :: all ( Val :: VMin ( 0.05 * 90. / buttons_f) )
168
+ } ;
78
169
79
- let count = ROW_COLUMN_COUNT ;
80
- let count_f = count as f32 ;
81
- let as_rainbow = |i : usize | Color :: hsl ( ( i as f32 / count_f) * 360.0 , 0.9 , 0.8 ) ;
170
+ let as_rainbow = |i : usize | Color :: hsl ( ( i as f32 / buttons_f) * 360.0 , 0.9 , 0.8 ) ;
82
171
commands. spawn ( Camera2dBundle :: default ( ) ) ;
83
172
commands
84
173
. spawn ( NodeBundle {
85
174
style : Style {
175
+ display : Display :: Grid ,
86
176
width : Val :: Percent ( 100. ) ,
177
+ height : Val :: Percent ( 100.0 ) ,
178
+ grid_template_columns : RepeatedGridTrack :: flex ( args. buttons as u16 , 1.0 ) ,
179
+ grid_template_rows : RepeatedGridTrack :: flex ( args. buttons as u16 , 1.0 ) ,
87
180
..default ( )
88
181
} ,
89
182
..default ( )
90
183
} )
91
184
. with_children ( |commands| {
92
- let spawn_text = std:: env:: args ( ) . all ( |arg| arg != "no-text" ) ;
93
- let border = if std:: env:: args ( ) . all ( |arg| arg != "no-borders" ) {
94
- UiRect :: all ( Val :: Percent ( 10. / count_f) )
95
- } else {
96
- UiRect :: DEFAULT
97
- } ;
98
- for i in 0 ..count {
99
- for j in 0 ..count {
100
- let color = as_rainbow ( j % i. max ( 1 ) ) . into ( ) ;
101
- let border_color = as_rainbow ( i % j. max ( 1 ) ) . into ( ) ;
185
+ for column in 0 ..args. buttons {
186
+ for row in 0 ..args. buttons {
187
+ let color = as_rainbow ( row % column. max ( 1 ) ) . into ( ) ;
188
+ let border_color = Color :: WHITE . with_a ( 0.5 ) . into ( ) ;
102
189
spawn_button (
103
190
commands,
104
191
color,
105
- count_f ,
106
- i ,
107
- j ,
108
- spawn_text ,
192
+ buttons_f ,
193
+ column ,
194
+ row ,
195
+ !args . no_text ,
109
196
border,
110
197
border_color,
198
+ image
199
+ . as_ref ( )
200
+ . filter ( |_| ( column + row) % args. image_freq == 0 )
201
+ . cloned ( ) ,
111
202
) ;
112
203
}
113
204
}
@@ -118,23 +209,25 @@ fn setup(mut commands: Commands) {
118
209
fn spawn_button (
119
210
commands : & mut ChildBuilder ,
120
211
background_color : BackgroundColor ,
121
- total : f32 ,
122
- i : usize ,
123
- j : usize ,
212
+ buttons : f32 ,
213
+ column : usize ,
214
+ row : usize ,
124
215
spawn_text : bool ,
125
216
border : UiRect ,
126
217
border_color : BorderColor ,
218
+ image : Option < Handle < Image > > ,
127
219
) {
128
- let width = 90.0 / total;
220
+ let width = Val :: Vw ( 90.0 / buttons) ;
221
+ let height = Val :: Vh ( 90.0 / buttons) ;
222
+ let margin = UiRect :: axes ( width * 0.05 , height * 0.05 ) ;
129
223
let mut builder = commands. spawn ( (
130
224
ButtonBundle {
131
225
style : Style {
132
- width : Val :: Percent ( width) ,
133
- height : Val :: Percent ( width) ,
134
- bottom : Val :: Percent ( 100.0 / total * i as f32 ) ,
135
- left : Val :: Percent ( 100.0 / total * j as f32 ) ,
226
+ width,
227
+ height,
228
+ margin,
136
229
align_items : AlignItems :: Center ,
137
- position_type : PositionType :: Absolute ,
230
+ justify_content : JustifyContent :: Center ,
138
231
border,
139
232
..default ( )
140
233
} ,
@@ -145,10 +238,14 @@ fn spawn_button(
145
238
IdleColor ( background_color) ,
146
239
) ) ;
147
240
241
+ if let Some ( image) = image {
242
+ builder. insert ( UiImage :: new ( image) ) ;
243
+ }
244
+
148
245
if spawn_text {
149
- builder. with_children ( |commands | {
150
- commands . spawn ( TextBundle :: from_section (
151
- format ! ( "{i }, {j }" ) ,
246
+ builder. with_children ( |parent | {
247
+ parent . spawn ( TextBundle :: from_section (
248
+ format ! ( "{column }, {row }" ) ,
152
249
TextStyle {
153
250
font_size : FONT_SIZE ,
154
251
color : Color :: rgb ( 0.2 , 0.2 , 0.2 ) ,
0 commit comments