Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/ui/images/image_node_resizing.rs
9331 views
1
//! This example demonstrates the behavior of `NodeImageMode::Auto` and `NodeImageMode::Stretch` by allowing keyboard input to resize an `ImageGroup` container.
2
//! It visually shows how images are sized automatically versus stretched to fit their container.
3
4
use bevy::{color::palettes::tailwind, prelude::*};
5
6
const MIN_RESIZE_VAL: f32 = 1.0;
7
const IMAGE_GROUP_BOX_MIN_WIDTH: f32 = 50.0;
8
const IMAGE_GROUP_BOX_MAX_WIDTH: f32 = 100.0;
9
const IMAGE_GROUP_BOX_MIN_HEIGHT: f32 = 10.0;
10
const IMAGE_GROUP_BOX_MAX_HEIGHT: f32 = 50.0;
11
const IMAGE_GROUP_BOX_INIT_WIDTH: f32 =
12
(IMAGE_GROUP_BOX_MIN_WIDTH + IMAGE_GROUP_BOX_MAX_WIDTH) / 2.;
13
const IMAGE_GROUP_BOX_INIT_HEIGHT: f32 =
14
(IMAGE_GROUP_BOX_MIN_HEIGHT + IMAGE_GROUP_BOX_MAX_HEIGHT) / 2.;
15
const TEXT_PREFIX: &str = "Compare NodeImageMode(Auto, Stretch) press `Up`/`Down` to resize height, press `Left`/`Right` to resize width\n";
16
17
fn main() {
18
App::new()
19
.add_plugins(DefaultPlugins)
20
// Enable for image outline
21
.insert_resource(UiDebugOptions {
22
enabled: true,
23
..default()
24
})
25
.add_systems(Startup, setup)
26
.add_systems(Update, update)
27
.add_observer(on_trigger_image_group)
28
.run();
29
}
30
31
#[derive(Debug, Component)]
32
struct ImageGroup;
33
34
#[derive(Debug, Event)]
35
enum ImageGroupResize {
36
HeightGrow,
37
HeightShrink,
38
WidthGrow,
39
WidthShrink,
40
}
41
42
// Text data for easy modification
43
#[derive(Debug, Component)]
44
struct TextData {
45
height: f32,
46
width: f32,
47
}
48
49
#[derive(Debug)]
50
enum Direction {
51
Height,
52
Width,
53
}
54
55
#[derive(Debug, EntityEvent)]
56
struct TextUpdate {
57
entity: Entity,
58
direction: Direction,
59
change: f32,
60
}
61
62
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
63
let image_handle = asset_server.load("branding/icon.png");
64
let full_text = format!(
65
"{}height : {}%, width : {}%",
66
TEXT_PREFIX, IMAGE_GROUP_BOX_INIT_HEIGHT, IMAGE_GROUP_BOX_INIT_WIDTH,
67
);
68
69
commands.spawn(Camera2d);
70
71
let container = commands
72
.spawn((
73
Node {
74
display: Display::Grid,
75
width: percent(100),
76
height: percent(100),
77
grid_template_rows: vec![GridTrack::min_content(), GridTrack::flex(1.0)],
78
..default()
79
},
80
BackgroundColor(Color::WHITE),
81
))
82
.id();
83
84
// Keyboard Text
85
commands
86
.spawn((
87
TextData {
88
height: IMAGE_GROUP_BOX_INIT_HEIGHT,
89
width: IMAGE_GROUP_BOX_INIT_WIDTH,
90
},
91
Text::new(full_text),
92
TextColor::BLACK,
93
Node {
94
grid_row: GridPlacement::span(1),
95
padding: px(6).all(),
96
..default()
97
},
98
UiDebugOptions {
99
enabled: false,
100
..default()
101
},
102
ChildOf(container),
103
))
104
.observe(update_text);
105
106
commands
107
.spawn((
108
Node {
109
display: Display::Flex,
110
grid_row: GridPlacement::span(1),
111
flex_direction: FlexDirection::Column,
112
justify_content: JustifyContent::SpaceAround,
113
padding: px(10.).all(),
114
..default()
115
},
116
BackgroundColor(Color::BLACK),
117
ChildOf(container),
118
))
119
.with_children(|builder| {
120
// `NodeImageMode::Auto` will resize the image automatically by taking the size of the source image and applying any layout constraints.
121
builder
122
.spawn((
123
ImageGroup,
124
Node {
125
display: Display::Flex,
126
justify_content: JustifyContent::Start,
127
width: percent(IMAGE_GROUP_BOX_INIT_WIDTH),
128
height: percent(IMAGE_GROUP_BOX_INIT_HEIGHT),
129
..default()
130
},
131
BackgroundColor(Color::from(tailwind::BLUE_100)),
132
))
133
.with_children(|parent| {
134
for _ in 0..4 {
135
// child node will apply Flex layout
136
parent.spawn((
137
Node::default(),
138
ImageNode {
139
image: image_handle.clone(),
140
image_mode: NodeImageMode::Auto,
141
..default()
142
},
143
));
144
}
145
});
146
// `NodeImageMode::Stretch` will resize the image to match the size of the `Node` component
147
builder
148
.spawn((
149
ImageGroup,
150
Node {
151
display: Display::Flex,
152
justify_content: JustifyContent::Start,
153
width: percent(IMAGE_GROUP_BOX_INIT_WIDTH),
154
height: percent(IMAGE_GROUP_BOX_INIT_HEIGHT),
155
..default()
156
},
157
BackgroundColor(Color::from(tailwind::BLUE_100)),
158
))
159
.with_children(|parent| {
160
for width in [10., 20., 30., 40.] {
161
parent.spawn((
162
Node {
163
height: percent(100),
164
width: percent(width),
165
..default()
166
},
167
ImageNode {
168
image: image_handle.clone(),
169
image_mode: NodeImageMode::Stretch,
170
..default()
171
},
172
));
173
}
174
});
175
});
176
}
177
178
// Trigger event
179
fn update(
180
keycode: Res<ButtonInput<KeyCode>>,
181
mut commands: Commands,
182
query: Query<Entity, With<TextData>>,
183
) {
184
let entity = query.single().unwrap();
185
if keycode.pressed(KeyCode::ArrowUp) {
186
commands.trigger(ImageGroupResize::HeightGrow);
187
commands.trigger(TextUpdate {
188
entity,
189
direction: Direction::Height,
190
change: MIN_RESIZE_VAL,
191
});
192
}
193
if keycode.pressed(KeyCode::ArrowDown) {
194
commands.trigger(ImageGroupResize::HeightShrink);
195
commands.trigger(TextUpdate {
196
entity,
197
direction: Direction::Height,
198
change: -MIN_RESIZE_VAL,
199
});
200
}
201
if keycode.pressed(KeyCode::ArrowLeft) {
202
commands.trigger(ImageGroupResize::WidthShrink);
203
commands.trigger(TextUpdate {
204
entity,
205
direction: Direction::Width,
206
change: -MIN_RESIZE_VAL,
207
});
208
}
209
if keycode.pressed(KeyCode::ArrowRight) {
210
commands.trigger(ImageGroupResize::WidthGrow);
211
commands.trigger(TextUpdate {
212
entity,
213
direction: Direction::Width,
214
change: MIN_RESIZE_VAL,
215
});
216
}
217
}
218
219
fn update_text(
220
event: On<TextUpdate>,
221
mut textmeta: Single<&mut TextData>,
222
mut text: Single<&mut Text>,
223
) {
224
let mut new_text = Text::new(TEXT_PREFIX);
225
match event.direction {
226
Direction::Height => {
227
textmeta.height = (textmeta.height + event.change)
228
.clamp(IMAGE_GROUP_BOX_MIN_HEIGHT, IMAGE_GROUP_BOX_MAX_HEIGHT);
229
new_text.push_str(&format!(
230
"height : {}%, width : {}%",
231
textmeta.height, textmeta.width
232
));
233
}
234
Direction::Width => {
235
textmeta.width = (textmeta.width + event.change)
236
.clamp(IMAGE_GROUP_BOX_MIN_WIDTH, IMAGE_GROUP_BOX_MAX_WIDTH);
237
new_text.push_str(&format!(
238
"height : {}%, width : {}%",
239
textmeta.height, textmeta.width
240
));
241
}
242
}
243
text.0 = new_text.0;
244
}
245
246
fn on_trigger_image_group(event: On<ImageGroupResize>, query: Query<&mut Node, With<ImageGroup>>) {
247
for mut node in query {
248
match event.event() {
249
ImageGroupResize::HeightGrow => {
250
if let Val::Percent(val) = &mut node.height {
251
*val = (*val + MIN_RESIZE_VAL).min(IMAGE_GROUP_BOX_MAX_HEIGHT);
252
}
253
}
254
ImageGroupResize::HeightShrink => {
255
if let Val::Percent(val) = &mut node.height {
256
*val = (*val - MIN_RESIZE_VAL).max(IMAGE_GROUP_BOX_MIN_HEIGHT);
257
}
258
}
259
ImageGroupResize::WidthGrow => {
260
if let Val::Percent(val) = &mut node.width {
261
*val = (*val + MIN_RESIZE_VAL).min(IMAGE_GROUP_BOX_MAX_WIDTH);
262
}
263
}
264
ImageGroupResize::WidthShrink => {
265
if let Val::Percent(val) = &mut node.width {
266
*val = (*val - MIN_RESIZE_VAL).max(IMAGE_GROUP_BOX_MIN_WIDTH);
267
}
268
}
269
}
270
}
271
}
272
273