bevy/crates
ickshonpe 4e02d3cdb9
Improved UiImage and Sprite scaling and slicing APIs (#16088)
# 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>
2024-11-04 15:14:03 +00:00
..
bevy_a11y Add core and alloc over std Lints (#15281) 2024-09-27 00:59:59 +00:00
bevy_animation Undeprecate is_playing_animation (#16121) 2024-10-27 22:38:07 +00:00
bevy_app Improve SubApp documentation example (#16160) 2024-10-30 22:12:25 +00:00
bevy_asset Update notify-debouncer-full requirement from 0.3.1 to 0.4.0 (#16133) 2024-10-28 22:23:03 +00:00
bevy_audio Fix audio not playing (#15638) 2024-10-04 01:07:09 +00:00
bevy_color Make some associated functions of Color const (#16091) 2024-10-28 03:26:35 +00:00
bevy_core Add core and alloc over std Lints (#15281) 2024-09-27 00:59:59 +00:00
bevy_core_pipeline Adding alpha_threshold to OrderIndependentTransparencySettings for user-level optimization (#16090) 2024-10-27 19:08:34 +00:00
bevy_derive move ANDROID_APP to bevy_window (#15585) 2024-10-02 03:01:06 +00:00
bevy_dev_tools fix bevy_dev_tools build (#16099) 2024-10-25 20:14:39 +00:00
bevy_diagnostic Use en-us locale for typos (#16037) 2024-10-20 18:55:17 +00:00
bevy_dylib Generate links to definition in source code pages on docs.rs and dev-docs.bevyengine.org (#12965) 2024-07-29 23:10:16 +00:00
bevy_ecs Adding ScheduleGraph::contains_set (#16206) 2024-11-03 16:16:24 +00:00
bevy_encase_derive Update `glam to 0.29, encase` to 0.10. (#15249) 2024-09-23 19:44:02 +00:00
bevy_gilrs Remove thiserror from bevy_gilrs (#15773) 2024-10-09 14:21:25 +00:00
bevy_gizmos Fix gizmos (#15836) 2024-10-10 22:04:04 +00:00
bevy_gltf Use en-us locale for typos (#16037) 2024-10-20 18:55:17 +00:00
bevy_hierarchy fix: add reflect to SceneInstanceReady and other observers/events (#16018) 2024-10-20 13:51:41 +00:00
bevy_image Use en-us locale for typos (#16037) 2024-10-20 18:55:17 +00:00
bevy_input Use en-us locale for typos (#16037) 2024-10-20 18:55:17 +00:00
bevy_internal Fix bevy_picking plugin suffixes (#16082) 2024-10-25 20:11:51 +00:00
bevy_log Use en-us locale for typos (#16037) 2024-10-20 18:55:17 +00:00
bevy_macro_utils Modify derive_label to support no_std environments (#15465) 2024-09-27 20:23:26 +00:00
bevy_math Implement Measured2d for Arc2d-based primitives. (#16213) 2024-11-03 16:12:31 +00:00
bevy_mesh Mesh::merge: count_vertices instead of initializing positions (#16024) 2024-10-31 17:06:32 +00:00
bevy_mikktspace Use en-us locale for typos (#16037) 2024-10-20 18:55:17 +00:00
bevy_pbr Adding alpha_threshold to OrderIndependentTransparencySettings for user-level optimization (#16090) 2024-10-27 19:08:34 +00:00
bevy_picking Add button_just_down and button_just_up methods to PointerInput (#16176) 2024-10-31 14:44:34 +00:00
bevy_ptr Reduce compile time of bevy_ptr::OwnedPtr::make function (#15644) 2024-10-28 21:15:00 +00:00
bevy_reflect Use en-us locale for typos (#16037) 2024-10-20 18:55:17 +00:00
bevy_remote remove reference to missing file in bevy_remote cargo.toml (#16057) 2024-10-22 20:21:19 +00:00
bevy_render Fix and improve MSAA documentation (#16196) 2024-10-31 21:34:04 +00:00
bevy_scene fix: add reflect to SceneInstanceReady and other observers/events (#16018) 2024-10-20 13:51:41 +00:00
bevy_sprite Improved UiImage and Sprite scaling and slicing APIs (#16088) 2024-11-04 15:14:03 +00:00
bevy_state Fix typo in bevy_ecs (#16195) 2024-10-31 19:20:01 +00:00
bevy_tasks Resolve unused_qualifications warnings (#16001) 2024-10-19 16:59:58 +00:00
bevy_text Use CosmicFontSystem in public bevy_text APIs and remove cosmic_text re-export (#16063) 2024-10-23 20:05:28 +00:00
bevy_time Use en-us locale for typos (#16037) 2024-10-20 18:55:17 +00:00
bevy_transform Remove thiserror from bevy_transform (#15761) 2024-10-09 14:27:30 +00:00
bevy_ui Improved UiImage and Sprite scaling and slicing APIs (#16088) 2024-11-04 15:14:03 +00:00
bevy_utils More #[doc(fake_variadic)] goodness (#16108) 2024-10-27 19:01:50 +00:00
bevy_window Support prefers_home_indicator_hidden (#16005) 2024-10-31 16:09:30 +00:00
bevy_winit Correctly feature gate custom_cursor (#16093) 2024-11-02 01:47:32 +00:00