mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 12:43:34 +00:00
4e02d3cdb9
# Objective 1. UI texture slicing chops and scales an image to fit the size of a node and isn't meant to place any constraints on the size of the node itself, but because the required components changes required `ImageSize` and `ContentSize` for nodes with `UiImage`, texture sliced nodes are laid out using an `ImageMeasure`. 2. In 0.14 users could spawn a `(UiImage, NodeBundle)` which would display an image stretched to fill the UI node's bounds ignoring the image's instrinsic size. Now that `UiImage` requires `ContentSize`, there's no option to display an image without its size placing constrains on the UI layout (unless you force the `Node` to a fixed size, but that's not a solution). 3. It's desirable that the `Sprite` and `UiImage` share similar APIs. Fixes #16109 ## Solution * Remove the `Component` impl from `ImageScaleMode`. * Add a `Stretch` variant to `ImageScaleMode`. * Add a field `scale_mode: ImageScaleMode` to `Sprite`. * Add a field `mode: UiImageMode` to `UiImage`. * Add an enum `UiImageMode` similar to `ImageScaleMode` but with additional UI specific variants. * Remove the queries for `ImageScaleMode` from Sprite and UI extraction, and refer to the new fields instead. * Change `ui_layout_system` to update measure funcs on any change to `ContentSize`s to enable manual clearing without removing the component. * Don't add a measure unless `UiImageMode::Auto` is set in `update_image_content_size_system`. Mutably deref the `Mut<ContentSize>` if the `UiImage` is changed to force removal of any existing measure func. ## Testing Remove all the constraints from the ui_texture_slice example: ```rust //! This example illustrates how to create buttons with their textures sliced //! and kept in proportion instead of being stretched by the button dimensions use bevy::{ color::palettes::css::{GOLD, ORANGE}, prelude::*, winit::WinitSettings, }; fn main() { App::new() .add_plugins(DefaultPlugins) // Only run the app when there is user input. This will significantly reduce CPU/GPU use. .insert_resource(WinitSettings::desktop_app()) .add_systems(Startup, setup) .add_systems(Update, button_system) .run(); } fn button_system( mut interaction_query: Query< (&Interaction, &Children, &mut UiImage), (Changed<Interaction>, With<Button>), >, mut text_query: Query<&mut Text>, ) { for (interaction, children, mut image) in &mut interaction_query { let mut text = text_query.get_mut(children[0]).unwrap(); match *interaction { Interaction::Pressed => { **text = "Press".to_string(); image.color = GOLD.into(); } Interaction::Hovered => { **text = "Hover".to_string(); image.color = ORANGE.into(); } Interaction::None => { **text = "Button".to_string(); image.color = Color::WHITE; } } } } fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { let image = asset_server.load("textures/fantasy_ui_borders/panel-border-010.png"); let slicer = TextureSlicer { border: BorderRect::square(22.0), center_scale_mode: SliceScaleMode::Stretch, sides_scale_mode: SliceScaleMode::Stretch, max_corner_scale: 1.0, }; // ui camera commands.spawn(Camera2d); commands .spawn(Node { width: Val::Percent(100.0), height: Val::Percent(100.0), align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..default() }) .with_children(|parent| { for [w, h] in [[150.0, 150.0], [300.0, 150.0], [150.0, 300.0]] { parent .spawn(( Button, Node { // width: Val::Px(w), // height: Val::Px(h), // horizontally center child text justify_content: JustifyContent::Center, // vertically center child text align_items: AlignItems::Center, margin: UiRect::all(Val::Px(20.0)), ..default() }, UiImage::new(image.clone()), ImageScaleMode::Sliced(slicer.clone()), )) .with_children(|parent| { // parent.spawn(( // Text::new("Button"), // TextFont { // font: asset_server.load("fonts/FiraSans-Bold.ttf"), // font_size: 33.0, // ..default() // }, // TextColor(Color::srgb(0.9, 0.9, 0.9)), // )); }); } }); } ``` This should result in a blank window, since without any constraints the texture slice image nodes should be zero-sized. But in main the image nodes are given the size of the underlying unsliced source image `textures/fantasy_ui_borders/panel-border-010.png`: <img width="321" alt="slicing" src="https://github.com/user-attachments/assets/cbd74c9c-14cd-4b4d-93c6-7c0152bb05ee"> For this PR need to change the lines: ``` UiImage::new(image.clone()), ImageScaleMode::Sliced(slicer.clone()), ``` to ``` UiImage::new(image.clone()).with_mode(UiImageMode::Sliced(slicer.clone()), ``` and then nothing should be rendered, as desired. --------- Co-authored-by: Carter Anderson <mcanders1@gmail.com>
76 lines
2.8 KiB
Rust
76 lines
2.8 KiB
Rust
//! This example illustrates how to how to flip and tile images with 9-slicing in the UI.
|
|
|
|
use bevy::{
|
|
prelude::*,
|
|
render::texture::{ImageLoaderSettings, ImageSampler},
|
|
ui::widget::NodeImageMode,
|
|
winit::WinitSettings,
|
|
};
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_plugins(DefaultPlugins)
|
|
.insert_resource(UiScale(2.))
|
|
// Only run the app when there is user input. This will significantly reduce CPU/GPU use for UI-only apps.
|
|
.insert_resource(WinitSettings::desktop_app())
|
|
.add_systems(Startup, setup)
|
|
.run();
|
|
}
|
|
|
|
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|
let image = asset_server.load_with_settings(
|
|
"textures/fantasy_ui_borders/numbered_slices.png",
|
|
|settings: &mut ImageLoaderSettings| {
|
|
// Need to use nearest filtering to avoid bleeding between the slices with tiling
|
|
settings.sampler = ImageSampler::nearest();
|
|
},
|
|
);
|
|
|
|
let slicer = TextureSlicer {
|
|
// `numbered_slices.png` is 48 pixels square. `BorderRect::square(16.)` insets the slicing line from each edge by 16 pixels, resulting in nine slices that are each 16 pixels square.
|
|
border: BorderRect::square(16.),
|
|
// With `SliceScaleMode::Tile` the side and center slices are tiled to fill the side and center sections of the target.
|
|
// And with a `stretch_value` of `1.` the tiles will have the same size as the corresponding slices in the source image.
|
|
center_scale_mode: SliceScaleMode::Tile { stretch_value: 1. },
|
|
sides_scale_mode: SliceScaleMode::Tile { stretch_value: 1. },
|
|
..default()
|
|
};
|
|
|
|
// ui camera
|
|
commands.spawn(Camera2d);
|
|
|
|
commands
|
|
.spawn(Node {
|
|
width: Val::Percent(100.),
|
|
height: Val::Percent(100.),
|
|
justify_content: JustifyContent::Center,
|
|
align_content: AlignContent::Center,
|
|
flex_wrap: FlexWrap::Wrap,
|
|
column_gap: Val::Px(10.),
|
|
row_gap: Val::Px(10.),
|
|
..default()
|
|
})
|
|
.with_children(|parent| {
|
|
for ([width, height], flip_x, flip_y) in [
|
|
([160., 160.], false, false),
|
|
([320., 160.], false, true),
|
|
([320., 160.], true, false),
|
|
([160., 160.], true, true),
|
|
] {
|
|
parent.spawn((
|
|
UiImage {
|
|
image: image.clone(),
|
|
flip_x,
|
|
flip_y,
|
|
image_mode: NodeImageMode::Sliced(slicer.clone()),
|
|
..default()
|
|
},
|
|
Node {
|
|
width: Val::Px(width),
|
|
height: Val::Px(height),
|
|
..default()
|
|
},
|
|
));
|
|
}
|
|
});
|
|
}
|