Built-in 'Contain' or 'Fit' scaling for `ImageNode` to preserve aspect ratio
- Dominant language
- Rust
- Stars
- 48.2k
- Forks
- 4.8k
- Avg merge
- 3d 22h
- Merged PRs (30d)
- 161
Description
## What problem does this solve or what need does it fill?
Currently, a `ImageNode` will stretch to fill the exact dimensions of its parent `Node` by default.This distorts the image if the parent node's aspect ratio does not match the image's intrinsic aspect ratio.
A very common UI requirement is to display an image as large as possible within a container **while preserving its original aspect ratio**, effectively "fitting" it inside the container. This is analogous to the `object-fit: contain` or `background-size: contain` properties in CSS.
The current workaround involves writing a custom system that manually queries the parent's final computed size and the image asset's dimensions to calculate and set the image node's width and height every frame. This has several disadvantages:
- It's boilerplate code that needs to be rewritten for many projects.
- It's not easily discoverable for new users, especially those coming from a web development background where this is a standard feature.
- It requires manual implementation of a very common and fundamental UI behavior.
There is no built-in, declarative way to achieve this "contain" scaling behavior.
## What solution would you like?
I suggest adding new type to 'NodeImageMode' to indicate how the final size of an image should be calculated relative to the available space of its node.
The enum could have variants mirroring the CSS `object-fit` property:
```rust
pub enum NodeImageMode {
...
/// Stretch the image to fill the node completely (the current default behavior).
Fill,
/// Scale the image to be as large as possible while fully visible within the node, preserving its aspect ratio.
Contain,
/// Scale the image to cover the entire node, preserving aspect ratio. Parts of the image may be clipped.
Cover,
/// Like `Contain`, but only if the image is larger than the node. Does not scale up.
ScaleDown,
}
```
The desired API would look something like this:
```rust
commands.spawn((
Node {
width: Val::Percent(100.0),
..Default::default()
},
ImageNode::new(asset_server.load("yi.png")).with_mode(NodeImageMode::Contain),
));
```
This would provide a simple, declarative, and efficient way to handle a core UI imaging requirement.
## What alternative(s) have you considered?
**Custom System:** This is the current solution. As detailed above, it's verbose, requires boilerplate, and is not user-friendly. It forces every developer to re-solve a standard problem.
## Additional context
Here is an example of the boilerplate system that is currently required to implement the `Contain` behavior. Integrating this logic directly into Bevy would greatly simplify UI code for many users.
This implementation does not have a parent container.
```rust
// A marker component.
#[derive(Component)]
struct BG;
// The system that performs the calculation every frame.
fn update_bg_size(mut bg: Query<&mut Node, With>, mut event: MessageReader) {
let Ok(mut bg_rect) = bg.single_mut() else {
return;
};
for window in event.read() {
// Assume the original aspect ratio of the image is 16:9.
let image_ratio = 16.0 / 9.0;
let window_ratio = window.width / window.height;
if window_ratio > image_ratio {
bg_rect.width = Val::Auto;
bg_rect.height = Val::Percent(100.0);
} else {
bg_rect.width = Val::Percent(100.0);
bg_rect.height = Val::Auto;
}
}
}
```
This feature would significantly improve the developer experience for building UIs in Bevy and align it more closely with the capabilities of other established UI frameworks.
Contributor guide
Assessment
This issue has not been assessed yet.