Skip to content

Image Node Rotation #19340

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3418,6 +3418,17 @@ description = "An example for CSS Grid layout"
category = "UI (User Interface)"
wasm = true

[[example]]
name = "image_node_rotation"
path = "examples/ui/image_node_rotation.rs"
doc-scrape-examples = true

[package.metadata.example.image_node_rotation]
name = "Image Node Rotation"
description = "Show the use of `ImageNode::rotation`"
category = "UI (User Interface)"
wasm = true

[[example]]
name = "gradients"
path = "examples/ui/gradients.rs"
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ui/src/render/debug_overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ pub fn extract_debug_overlay(
item: ExtractedUiItem::Node {
atlas_scaling: None,
transform: transform.compute_matrix(),
rotation: 0.,
flip_x: false,
flip_y: false,
border: BorderRect::all(debug_options.line_width / uinode.inverse_scale_factor()),
border_radius: uinode.border_radius(),
node_type: NodeType::Border,
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ui/src/render/gradient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -411,8 +411,8 @@ pub fn extract_gradients(
extracted_camera_entity,
item: ExtractedUiItem::Node {
atlas_scaling: None,
rotation: 0.,
flip_x: false,
flip_y: false,
border_radius: uinode.border_radius,
border: uinode.border,
node_type,
Expand Down
31 changes: 14 additions & 17 deletions crates/bevy_ui/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,8 @@ pub enum NodeType {
pub enum ExtractedUiItem {
Node {
atlas_scaling: Option<Vec2>,
rotation: f32,
flip_x: bool,
flip_y: bool,
/// Border radius of the UI node.
/// Ordering: top left, top right, bottom right, bottom left.
border_radius: ResolvedBorderRadius,
Expand Down Expand Up @@ -385,8 +385,8 @@ pub fn extract_uinode_background_colors(
item: ExtractedUiItem::Node {
atlas_scaling: None,
transform: transform.compute_matrix(),
rotation: 0.,
flip_x: false,
flip_y: false,
border: uinode.border(),
border_radius: uinode.border_radius(),
node_type: NodeType::Rect,
Expand Down Expand Up @@ -469,8 +469,8 @@ pub fn extract_uinode_images(
item: ExtractedUiItem::Node {
atlas_scaling,
transform: transform.compute_matrix(),
rotation: image.rotation,
flip_x: image.flip_x,
flip_y: image.flip_y,
border: uinode.border,
border_radius: uinode.border_radius,
node_type: NodeType::Rect,
Expand Down Expand Up @@ -537,8 +537,8 @@ pub fn extract_uinode_borders(
item: ExtractedUiItem::Node {
atlas_scaling: None,
transform: global_transform.compute_matrix(),
rotation: 0.,
flip_x: false,
flip_y: false,
border: computed_node.border(),
border_radius: computed_node.border_radius(),
node_type: NodeType::Border,
Expand Down Expand Up @@ -570,8 +570,8 @@ pub fn extract_uinode_borders(
item: ExtractedUiItem::Node {
transform: global_transform.compute_matrix(),
atlas_scaling: None,
rotation: 0.,
flip_x: false,
flip_y: false,
border: BorderRect::all(computed_node.outline_width()),
border_radius: computed_node.outline_radius(),
node_type: NodeType::Border,
Expand Down Expand Up @@ -760,8 +760,8 @@ pub fn extract_viewport_nodes(
item: ExtractedUiItem::Node {
atlas_scaling: None,
transform: transform.compute_matrix(),
rotation: 0.,
flip_x: false,
flip_y: false,
border: uinode.border(),
border_radius: uinode.border_radius(),
node_type: NodeType::Rect,
Expand Down Expand Up @@ -1010,8 +1010,8 @@ pub fn extract_text_background_colors(
item: ExtractedUiItem::Node {
atlas_scaling: None,
transform: transform * Mat4::from_translation(rect.center().extend(0.)),
rotation: 0.,
flip_x: false,
flip_y: false,
border: uinode.border(),
border_radius: uinode.border_radius(),
node_type: NodeType::Rect,
Expand Down Expand Up @@ -1269,8 +1269,8 @@ pub fn prepare_uinodes(
match &extracted_uinode.item {
ExtractedUiItem::Node {
atlas_scaling,
rotation,
flip_x,
flip_y,
border_radius,
border,
node_type,
Expand All @@ -1287,8 +1287,12 @@ pub fn prepare_uinodes(
let rect_size = uinode_rect.size().extend(1.0);

// Specify the corners of the node
let positions = QUAD_VERTEX_POSITIONS
.map(|pos| (*transform * (pos * rect_size).extend(1.)).xyz());
let positions = QUAD_VERTEX_POSITIONS.map(|pos| {
(*transform
* Mat4::from_rotation_z(*rotation)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the rotation be flipped in flip_y is true?

* (pos * rect_size).extend(1.))
.xyz()
});
let points = QUAD_VERTEX_POSITIONS.map(|pos| pos.xy() * rect_size.xy());

// Calculate the effect of clipping
Expand Down Expand Up @@ -1365,13 +1369,6 @@ pub fn prepare_uinodes(
positions_diff[2].x *= -1.;
positions_diff[3].x *= -1.;
}
if *flip_y {
core::mem::swap(&mut uinode_rect.max.y, &mut uinode_rect.min.y);
positions_diff[0].y *= -1.;
positions_diff[1].y *= -1.;
positions_diff[2].y *= -1.;
positions_diff[3].y *= -1.;
}
[
Vec2::new(
uinode_rect.min.x + positions_diff[0].x,
Expand Down
16 changes: 8 additions & 8 deletions crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,8 @@ pub struct ExtractedUiTextureSlice {
pub extracted_camera_entity: Entity,
pub color: LinearRgba,
pub image_scale_mode: SpriteImageMode,
pub rotation: f32,
pub flip_x: bool,
pub flip_y: bool,
pub inverse_scale_factor: f32,
pub main_entity: MainEntity,
pub render_entity: Entity,
Expand Down Expand Up @@ -323,8 +323,8 @@ pub fn extract_ui_texture_slices(
extracted_camera_entity,
image_scale_mode,
atlas_rect,
rotation: image.rotation,
flip_x: image.flip_x,
flip_y: image.flip_y,
inverse_scale_factor: uinode.inverse_scale_factor,
main_entity: entity.into(),
});
Expand Down Expand Up @@ -506,8 +506,12 @@ pub fn prepare_ui_slices(
let rect_size = uinode_rect.size().extend(1.0);

// Specify the corners of the node
let positions = QUAD_VERTEX_POSITIONS
.map(|pos| (texture_slices.transform * (pos * rect_size).extend(1.)).xyz());
let positions = QUAD_VERTEX_POSITIONS.map(|pos| {
(texture_slices.transform
* Mat4::from_rotation_z(texture_slices.rotation)
* (pos * rect_size).extend(1.))
.xyz()
});

// Calculate the effect of clipping
// Note: this won't work with rotation/scaling, but that's much more complex (may need more that 2 quads)
Expand Down Expand Up @@ -609,10 +613,6 @@ pub fn prepare_ui_slices(
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() * texture_slices.inverse_scale_factor,
Expand Down
22 changes: 12 additions & 10 deletions crates/bevy_ui/src/widget/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ pub struct ImageNode {
pub image: Handle<Image>,
/// The (optional) texture atlas used to render the image.
pub texture_atlas: Option<TextureAtlas>,
/// Clockwise rotation of the image in radians.
/// Rotations done through this field do not update the node's size and might cause clipping or overflow,
/// to rotate updating the node's size use [`Transform`](bevy_transform::prelude::Transform).
pub rotation: f32,
/// Whether the image should be flipped along its x-axis.
pub flip_x: bool,
/// Whether the image should be flipped along its y-axis.
pub flip_y: bool,
/// An optional rectangle representing the region of the image to render, instead of rendering
/// the full image. This is an easy one-off alternative to using a [`TextureAtlas`].
///
Expand All @@ -55,8 +57,8 @@ impl Default for ImageNode {
texture_atlas: None,
// This texture needs to be transparent by default, to avoid covering the background color
image: TRANSPARENT_IMAGE_HANDLE,
rotation: 0.,
flip_x: false,
flip_y: false,
rect: None,
image_mode: NodeImageMode::Auto,
}
Expand All @@ -80,8 +82,8 @@ impl ImageNode {
Self {
image: Handle::default(),
color,
rotation: 0.,
flip_x: false,
flip_y: false,
texture_atlas: None,
rect: None,
image_mode: NodeImageMode::Auto,
Expand All @@ -104,17 +106,17 @@ impl ImageNode {
self
}

/// Flip the image along its x-axis
/// Rotates the image
#[must_use]
pub const fn with_flip_x(mut self) -> Self {
self.flip_x = true;
pub const fn with_rotation(mut self, rotation: f32) -> Self {
self.rotation = rotation;
self
}

/// Flip the image along its y-axis
/// Flip the image along its x-axis
#[must_use]
pub const fn with_flip_y(mut self) -> Self {
self.flip_y = true;
pub const fn with_flip_x(mut self) -> Self {
self.flip_x = true;
self
}

Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,7 @@ Example | Description
[Font Atlas Debug](../examples/ui/font_atlas_debug.rs) | Illustrates how FontAtlases are populated (used to optimize text rendering internally)
[Ghost Nodes](../examples/ui/ghost_nodes.rs) | Demonstrates the use of Ghost Nodes to skip entities in the UI layout hierarchy
[Gradients](../examples/ui/gradients.rs) | An example demonstrating gradients
[Image Node Rotation](../examples/ui/image_node_rotation.rs) | Show the use of `ImageNode::rotation`
[Overflow](../examples/ui/overflow.rs) | Simple example demonstrating overflow behavior
[Overflow Clip Margin](../examples/ui/overflow_clip_margin.rs) | Simple example demonstrating the OverflowClipMargin style property
[Overflow and Clipping Debug](../examples/ui/overflow_debug.rs) | An example to debug overflow and clipping behavior
Expand Down
17 changes: 12 additions & 5 deletions examples/testbed/full_ui.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! This example illustrates the various features of Bevy UI.

use std::f32::consts::PI;
use std::f32::consts::{FRAC_PI_2, PI};

use accesskit::{Node as Accessible, Role};
use bevy::{
Expand Down Expand Up @@ -375,14 +375,21 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
})
.insert(Pickable::IGNORE)
.with_children(|parent| {
for (flip_x, flip_y) in
[(false, false), (false, true), (true, true), (true, false)]
{
for (rotation, flip_x) in [
(0., false),
(FRAC_PI_2, false),
(PI, false),
(FRAC_PI_2 + PI, false),
(0., true),
(FRAC_PI_2, true),
(PI, true),
(FRAC_PI_2 + PI, true),
] {
parent.spawn((
ImageNode {
image: asset_server.load("branding/icon.png"),
rotation,
flip_x,
flip_y,
..default()
},
Node {
Expand Down
Loading