mirror of
https://github.com/bevyengine/bevy
synced 2024-11-24 21:53:07 +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)"
|
||||
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]]
|
||||
name = "ui_texture_atlas_slice"
|
||||
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 color: LinearRgba,
|
||||
pub image_scale_mode: ImageScaleMode,
|
||||
pub flip_x: bool,
|
||||
pub flip_y: bool,
|
||||
}
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
|
@ -294,6 +296,8 @@ pub fn extract_ui_texture_slices(
|
|||
camera_entity,
|
||||
image_scale_mode: image_scale_mode.clone(),
|
||||
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 (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(),
|
||||
[
|
||||
|
@ -560,6 +564,14 @@ pub fn prepare_ui_slices(
|
|||
(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(
|
||||
image_size,
|
||||
uinode_rect.size(),
|
||||
|
@ -692,7 +704,7 @@ fn compute_texture_slices(
|
|||
) -> [[f32; 4]; 3] {
|
||||
match image_scale_mode {
|
||||
ImageScaleMode::Sliced(TextureSlicer {
|
||||
border,
|
||||
border: border_rect,
|
||||
center_scale_mode,
|
||||
sides_scale_mode,
|
||||
max_corner_scale,
|
||||
|
@ -701,31 +713,48 @@ fn compute_texture_slices(
|
|||
.min_element()
|
||||
.min(*max_corner_scale);
|
||||
|
||||
// calculate the normalized extents of the nine-patched image slices
|
||||
let slices = [
|
||||
border.left / image_size.x,
|
||||
border.top / image_size.y,
|
||||
1. - border.right / image_size.x,
|
||||
1. - border.bottom / image_size.y,
|
||||
border_rect.left / image_size.x,
|
||||
border_rect.top / image_size.y,
|
||||
1. - border_rect.right / image_size.x,
|
||||
1. - border_rect.bottom / image_size.y,
|
||||
];
|
||||
|
||||
// calculate the normalized extents of the target slices
|
||||
let border = [
|
||||
(border.left / target_size.x) * min_coeff,
|
||||
(border.top / target_size.y) * min_coeff,
|
||||
1. - (border.right / target_size.x) * min_coeff,
|
||||
1. - (border.bottom / target_size.y) * min_coeff,
|
||||
(border_rect.left / target_size.x) * min_coeff,
|
||||
(border_rect.top / target_size.y) * min_coeff,
|
||||
1. - (border_rect.right / target_size.x) * min_coeff,
|
||||
1. - (border_rect.bottom / target_size.y) * min_coeff,
|
||||
];
|
||||
|
||||
let isx = image_size.x * (1. - slices[0] - slices[2]);
|
||||
let isy = image_size.y * (1. - slices[1] - slices[3]);
|
||||
let tsx = target_size.x * (1. - border[0] - border[2]);
|
||||
let tsy = target_size.y * (1. - border[1] - border[3]);
|
||||
let image_side_width = image_size.x * (slices[2] - slices[0]);
|
||||
let image_side_height = image_size.y * (slices[2] - slices[1]);
|
||||
let target_side_height = target_size.x * (border[2] - border[0]);
|
||||
let target_side_width = target_size.y * (border[3] - border[1]);
|
||||
|
||||
let rx = compute_tiled_subaxis(isx, tsx, sides_scale_mode);
|
||||
let ry = compute_tiled_subaxis(isy, tsy, sides_scale_mode);
|
||||
let cx = compute_tiled_subaxis(isx, tsx, center_scale_mode);
|
||||
let cy = compute_tiled_subaxis(isy, tsy, center_scale_mode);
|
||||
// compute the number of times to repeat the side and center slices when tiling along each axis
|
||||
// if the returned value is `1.` the slice will be stretched to fill the axis.
|
||||
let repeat_side_x =
|
||||
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 {
|
||||
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 {
|
||||
let s = is * stretch;
|
||||
ts / s
|
||||
let s = image_extent * stretch;
|
||||
target_extent / s
|
||||
} else {
|
||||
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 {
|
||||
SliceScaleMode::Stretch => 1.,
|
||||
SliceScaleMode::Tile { stretch_value } => {
|
||||
let s = is * *stretch_value;
|
||||
ts / s
|
||||
let s = image_extent * *stretch_value;
|
||||
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 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 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
|
||||
[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.
|
||||
|
|
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