mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
UI texture slice texture flipping reimplementation (#15034)
# Objective Fixes #15032 ## Solution Reimplement support for the `flip_x` and `flip_y` fields. This doesn't flip the border geometry, I'm not really sure whether that is desirable or not. Also fixes a bug that was causing the side and center slices to tile incorrectly. ### Testing ``` cargo run --example ui_texture_slice_flip_and_tile ``` ## Showcase <img width="787" alt="nearest" src="https://github.com/user-attachments/assets/bc044bae-1748-42ba-92b5-0500c87264f6"> With tiling need to use nearest filtering to avoid bleeding between the slices. --------- Co-authored-by: Jan Hohenheim <jan@hohenheim.ch> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
parent
739007f148
commit
8ac745ab10
5 changed files with 144 additions and 25 deletions
11
Cargo.toml
11
Cargo.toml
|
@ -2917,6 +2917,17 @@ description = "Illustrates how to use 9 Slicing in UI"
|
||||||
category = "UI (User Interface)"
|
category = "UI (User Interface)"
|
||||||
wasm = true
|
wasm = true
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "ui_texture_slice_flip_and_tile"
|
||||||
|
path = "examples/ui/ui_texture_slice_flip_and_tile.rs"
|
||||||
|
doc-scrape-examples = true
|
||||||
|
|
||||||
|
[package.metadata.example.ui_texture_slice_flip_and_tile]
|
||||||
|
name = "UI Texture Slice Flipping and Tiling"
|
||||||
|
description = "Illustrates how to flip and tile images with 9 Slicing in UI"
|
||||||
|
category = "UI (User Interface)"
|
||||||
|
wasm = true
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "ui_texture_atlas_slice"
|
name = "ui_texture_atlas_slice"
|
||||||
path = "examples/ui/ui_texture_atlas_slice.rs"
|
path = "examples/ui/ui_texture_atlas_slice.rs"
|
||||||
|
|
BIN
assets/textures/fantasy_ui_borders/numbered_slices.png
Normal file
BIN
assets/textures/fantasy_ui_borders/numbered_slices.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 620 B |
|
@ -232,6 +232,8 @@ pub struct ExtractedUiTextureSlice {
|
||||||
pub camera_entity: Entity,
|
pub camera_entity: Entity,
|
||||||
pub color: LinearRgba,
|
pub color: LinearRgba,
|
||||||
pub image_scale_mode: ImageScaleMode,
|
pub image_scale_mode: ImageScaleMode,
|
||||||
|
pub flip_x: bool,
|
||||||
|
pub flip_y: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Resource, Default)]
|
#[derive(Resource, Default)]
|
||||||
|
@ -294,6 +296,8 @@ pub fn extract_ui_texture_slices(
|
||||||
camera_entity,
|
camera_entity,
|
||||||
image_scale_mode: image_scale_mode.clone(),
|
image_scale_mode: image_scale_mode.clone(),
|
||||||
atlas_rect,
|
atlas_rect,
|
||||||
|
flip_x: image.flip_x,
|
||||||
|
flip_y: image.flip_y,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -546,7 +550,7 @@ pub fn prepare_ui_slices(
|
||||||
|
|
||||||
let color = texture_slices.color.to_f32_array();
|
let color = texture_slices.color.to_f32_array();
|
||||||
|
|
||||||
let (image_size, atlas) = if let Some(atlas) = texture_slices.atlas_rect {
|
let (image_size, mut atlas) = if let Some(atlas) = texture_slices.atlas_rect {
|
||||||
(
|
(
|
||||||
atlas.size(),
|
atlas.size(),
|
||||||
[
|
[
|
||||||
|
@ -560,6 +564,14 @@ pub fn prepare_ui_slices(
|
||||||
(batch_image_size, [0., 0., 1., 1.])
|
(batch_image_size, [0., 0., 1., 1.])
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if texture_slices.flip_x {
|
||||||
|
atlas.swap(0, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if texture_slices.flip_y {
|
||||||
|
atlas.swap(1, 3);
|
||||||
|
}
|
||||||
|
|
||||||
let [slices, border, repeat] = compute_texture_slices(
|
let [slices, border, repeat] = compute_texture_slices(
|
||||||
image_size,
|
image_size,
|
||||||
uinode_rect.size(),
|
uinode_rect.size(),
|
||||||
|
@ -692,7 +704,7 @@ fn compute_texture_slices(
|
||||||
) -> [[f32; 4]; 3] {
|
) -> [[f32; 4]; 3] {
|
||||||
match image_scale_mode {
|
match image_scale_mode {
|
||||||
ImageScaleMode::Sliced(TextureSlicer {
|
ImageScaleMode::Sliced(TextureSlicer {
|
||||||
border,
|
border: border_rect,
|
||||||
center_scale_mode,
|
center_scale_mode,
|
||||||
sides_scale_mode,
|
sides_scale_mode,
|
||||||
max_corner_scale,
|
max_corner_scale,
|
||||||
|
@ -701,31 +713,48 @@ fn compute_texture_slices(
|
||||||
.min_element()
|
.min_element()
|
||||||
.min(*max_corner_scale);
|
.min(*max_corner_scale);
|
||||||
|
|
||||||
|
// calculate the normalized extents of the nine-patched image slices
|
||||||
let slices = [
|
let slices = [
|
||||||
border.left / image_size.x,
|
border_rect.left / image_size.x,
|
||||||
border.top / image_size.y,
|
border_rect.top / image_size.y,
|
||||||
1. - border.right / image_size.x,
|
1. - border_rect.right / image_size.x,
|
||||||
1. - border.bottom / image_size.y,
|
1. - border_rect.bottom / image_size.y,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// calculate the normalized extents of the target slices
|
||||||
let border = [
|
let border = [
|
||||||
(border.left / target_size.x) * min_coeff,
|
(border_rect.left / target_size.x) * min_coeff,
|
||||||
(border.top / target_size.y) * min_coeff,
|
(border_rect.top / target_size.y) * min_coeff,
|
||||||
1. - (border.right / target_size.x) * min_coeff,
|
1. - (border_rect.right / target_size.x) * min_coeff,
|
||||||
1. - (border.bottom / target_size.y) * min_coeff,
|
1. - (border_rect.bottom / target_size.y) * min_coeff,
|
||||||
];
|
];
|
||||||
|
|
||||||
let isx = image_size.x * (1. - slices[0] - slices[2]);
|
let image_side_width = image_size.x * (slices[2] - slices[0]);
|
||||||
let isy = image_size.y * (1. - slices[1] - slices[3]);
|
let image_side_height = image_size.y * (slices[2] - slices[1]);
|
||||||
let tsx = target_size.x * (1. - border[0] - border[2]);
|
let target_side_height = target_size.x * (border[2] - border[0]);
|
||||||
let tsy = target_size.y * (1. - border[1] - border[3]);
|
let target_side_width = target_size.y * (border[3] - border[1]);
|
||||||
|
|
||||||
let rx = compute_tiled_subaxis(isx, tsx, sides_scale_mode);
|
// compute the number of times to repeat the side and center slices when tiling along each axis
|
||||||
let ry = compute_tiled_subaxis(isy, tsy, sides_scale_mode);
|
// if the returned value is `1.` the slice will be stretched to fill the axis.
|
||||||
let cx = compute_tiled_subaxis(isx, tsx, center_scale_mode);
|
let repeat_side_x =
|
||||||
let cy = compute_tiled_subaxis(isy, tsy, center_scale_mode);
|
compute_tiled_subaxis(image_side_width, target_side_height, sides_scale_mode);
|
||||||
|
let repeat_side_y =
|
||||||
|
compute_tiled_subaxis(image_side_height, target_side_width, sides_scale_mode);
|
||||||
|
let repeat_center_x =
|
||||||
|
compute_tiled_subaxis(image_side_width, target_side_height, center_scale_mode);
|
||||||
|
let repeat_center_y =
|
||||||
|
compute_tiled_subaxis(image_side_height, target_side_width, center_scale_mode);
|
||||||
|
|
||||||
[slices, border, [rx, ry, cx, cy]]
|
[
|
||||||
|
slices,
|
||||||
|
border,
|
||||||
|
[
|
||||||
|
repeat_side_x,
|
||||||
|
repeat_side_y,
|
||||||
|
repeat_center_x,
|
||||||
|
repeat_center_y,
|
||||||
|
],
|
||||||
|
]
|
||||||
}
|
}
|
||||||
ImageScaleMode::Tiled {
|
ImageScaleMode::Tiled {
|
||||||
tile_x,
|
tile_x,
|
||||||
|
@ -739,21 +768,21 @@ fn compute_texture_slices(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_tiled_axis(tile: bool, is: f32, ts: f32, stretch: f32) -> f32 {
|
fn compute_tiled_axis(tile: bool, image_extent: f32, target_extent: f32, stretch: f32) -> f32 {
|
||||||
if tile {
|
if tile {
|
||||||
let s = is * stretch;
|
let s = image_extent * stretch;
|
||||||
ts / s
|
target_extent / s
|
||||||
} else {
|
} else {
|
||||||
1.
|
1.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_tiled_subaxis(is: f32, ts: f32, mode: &SliceScaleMode) -> f32 {
|
fn compute_tiled_subaxis(image_extent: f32, target_extent: f32, mode: &SliceScaleMode) -> f32 {
|
||||||
match mode {
|
match mode {
|
||||||
SliceScaleMode::Stretch => 1.,
|
SliceScaleMode::Stretch => 1.,
|
||||||
SliceScaleMode::Tile { stretch_value } => {
|
SliceScaleMode::Tile { stretch_value } => {
|
||||||
let s = is * *stretch_value;
|
let s = image_extent * *stretch_value;
|
||||||
ts / s
|
target_extent / s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -491,6 +491,7 @@ Example | Description
|
||||||
[UI Texture Atlas](../examples/ui/ui_texture_atlas.rs) | Illustrates how to use TextureAtlases in UI
|
[UI Texture Atlas](../examples/ui/ui_texture_atlas.rs) | Illustrates how to use TextureAtlases in UI
|
||||||
[UI Texture Atlas Slice](../examples/ui/ui_texture_atlas_slice.rs) | Illustrates how to use 9 Slicing for TextureAtlases in UI
|
[UI Texture Atlas Slice](../examples/ui/ui_texture_atlas_slice.rs) | Illustrates how to use 9 Slicing for TextureAtlases in UI
|
||||||
[UI Texture Slice](../examples/ui/ui_texture_slice.rs) | Illustrates how to use 9 Slicing in UI
|
[UI Texture Slice](../examples/ui/ui_texture_slice.rs) | Illustrates how to use 9 Slicing in UI
|
||||||
|
[UI Texture Slice Flipping and Tiling](../examples/ui/ui_texture_slice_flip_and_tile.rs) | Illustrates how to flip and tile images with 9 Slicing in UI
|
||||||
[UI Z-Index](../examples/ui/z_index.rs) | Demonstrates how to control the relative depth (z-position) of UI elements
|
[UI Z-Index](../examples/ui/z_index.rs) | Demonstrates how to control the relative depth (z-position) of UI elements
|
||||||
[Viewport Debug](../examples/ui/viewport_debug.rs) | An example for debugging viewport coordinates
|
[Viewport Debug](../examples/ui/viewport_debug.rs) | An example for debugging viewport coordinates
|
||||||
[Window Fallthrough](../examples/ui/window_fallthrough.rs) | Illustrates how to access `winit::window::Window`'s `hittest` functionality.
|
[Window Fallthrough](../examples/ui/window_fallthrough.rs) | Illustrates how to access `winit::window::Window`'s `hittest` functionality.
|
||||||
|
|
78
examples/ui/ui_texture_slice_flip_and_tile.rs
Normal file
78
examples/ui/ui_texture_slice_flip_and_tile.rs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
//! This example illustrates how to how to flip and tile images with 9-slicing in the UI.
|
||||||
|
|
||||||
|
use bevy::{prelude::*, winit::WinitSettings};
|
||||||
|
use bevy_render::texture::{ImageLoaderSettings, ImageSampler};
|
||||||
|
|
||||||
|
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 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(Camera2dBundle::default());
|
||||||
|
|
||||||
|
commands
|
||||||
|
.spawn(NodeBundle {
|
||||||
|
style: Style {
|
||||||
|
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()
|
||||||
|
},
|
||||||
|
..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((
|
||||||
|
NodeBundle {
|
||||||
|
style: Style {
|
||||||
|
width: Val::Px(width),
|
||||||
|
height: Val::Px(height),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
UiImage {
|
||||||
|
texture: image.clone(),
|
||||||
|
flip_x,
|
||||||
|
flip_y,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
ImageScaleMode::Sliced(slicer.clone()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue