Newtype Anchor (#18439)

# Objective

The `Anchor` component doesn't need to be a enum. The variants are just
mapped to `Vec2`s so it could be changed to a newtype with associated
const values, saving the space needed for the discriminator by the enum.

Also there was no benefit I think in hiding the underlying `Vec2`
representation of `Anchor`s.

Suggested by @atlv24.

Fixes #18459
Fixes #18460

## Solution

Change `Anchor` to a struct newtyping a `Vec2`, and its variants into
associated constants.

## Migration Guide

The anchor component has been changed from an enum to a struct newtyping
a `Vec2`. The `Custom` variant has been removed, instead to construct a
custom `Anchor` use its tuple constructor:
```rust
Sprite {
     anchor: Anchor(Vec2::new(0.25, 0.4)),
     ..default()
}
```
The other enum variants have been replaced with corresponding constants:
* `Anchor::BottomLeft` to `Anchor::BOTTOM_LEFT`
* `Anchor::Center` to `Anchor::CENTER`
* `Anchor::TopRight` to `Anchor::TOP_RIGHT`
* .. and so on for the remaining variants
This commit is contained in:
ickshonpe 2025-03-21 22:27:11 +00:00 committed by GitHub
parent 9ae7aa4399
commit 84b09b9398
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 59 additions and 63 deletions

View File

@ -331,7 +331,7 @@ mod test {
.world_mut() .world_mut()
.spawn(Sprite { .spawn(Sprite {
rect: Some(Rect::new(0., 0., 0.5, 1.)), rect: Some(Rect::new(0., 0., 0.5, 1.)),
anchor: Anchor::TopRight, anchor: Anchor::TOP_RIGHT,
image: image_handle, image: image_handle,
..default() ..default()
}) })

View File

@ -1,5 +1,6 @@
use bevy_asset::{Assets, Handle}; use bevy_asset::{Assets, Handle};
use bevy_color::Color; use bevy_color::Color;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{component::Component, reflect::ReflectComponent}; use bevy_ecs::{component::Component, reflect::ReflectComponent};
use bevy_image::{Image, TextureAtlas, TextureAtlasLayout}; use bevy_image::{Image, TextureAtlas, TextureAtlasLayout};
use bevy_math::{Rect, UVec2, Vec2}; use bevy_math::{Rect, UVec2, Vec2};
@ -240,41 +241,37 @@ pub enum ScalingMode {
FitEnd, FitEnd,
} }
/// How a sprite is positioned relative to its [`Transform`]. /// Normalized (relative to its size) offset of a 2d renderable entity from its [`Transform`].
/// It defaults to `Anchor::Center`. #[derive(Component, Debug, Clone, Copy, PartialEq, Deref, DerefMut, Reflect)]
#[derive(Component, Debug, Clone, Copy, PartialEq, Default, Reflect)]
#[reflect(Component, Default, Debug, PartialEq, Clone)] #[reflect(Component, Default, Debug, PartialEq, Clone)]
#[doc(alias = "pivot")] #[doc(alias = "pivot")]
pub enum Anchor { pub struct Anchor(pub Vec2);
#[default]
Center,
BottomLeft,
BottomCenter,
BottomRight,
CenterLeft,
CenterRight,
TopLeft,
TopCenter,
TopRight,
/// Custom anchor point. Top left is `(-0.5, 0.5)`, center is `(0.0, 0.0)`. The value will
/// be scaled with the sprite size.
Custom(Vec2),
}
impl Anchor { impl Anchor {
pub const BOTTOM_LEFT: Self = Self(Vec2::new(-0.5, -0.5));
pub const BOTTOM_CENTER: Self = Self(Vec2::new(0.0, -0.5));
pub const BOTTOM_RIGHT: Self = Self(Vec2::new(0.5, -0.5));
pub const CENTER_LEFT: Self = Self(Vec2::new(-0.5, 0.0));
pub const CENTER: Self = Self(Vec2::ZERO);
pub const CENTER_RIGHT: Self = Self(Vec2::new(0.5, 0.0));
pub const TOP_LEFT: Self = Self(Vec2::new(-0.5, 0.5));
pub const TOP_CENTER: Self = Self(Vec2::new(0.0, 0.5));
pub const TOP_RIGHT: Self = Self(Vec2::new(0.5, 0.5));
pub fn as_vec(&self) -> Vec2 { pub fn as_vec(&self) -> Vec2 {
match self { self.0
Anchor::Center => Vec2::ZERO, }
Anchor::BottomLeft => Vec2::new(-0.5, -0.5), }
Anchor::BottomCenter => Vec2::new(0.0, -0.5),
Anchor::BottomRight => Vec2::new(0.5, -0.5), impl Default for Anchor {
Anchor::CenterLeft => Vec2::new(-0.5, 0.0), fn default() -> Self {
Anchor::CenterRight => Vec2::new(0.5, 0.0), Self::CENTER
Anchor::TopLeft => Vec2::new(-0.5, 0.5), }
Anchor::TopCenter => Vec2::new(0.0, 0.5), }
Anchor::TopRight => Vec2::new(0.5, 0.5),
Anchor::Custom(point) => *point, impl From<Vec2> for Anchor {
} fn from(value: Vec2) -> Self {
Self(value)
} }
} }
@ -358,7 +355,7 @@ mod tests {
let sprite = Sprite { let sprite = Sprite {
image, image,
anchor: Anchor::BottomLeft, anchor: Anchor::BOTTOM_LEFT,
..Default::default() ..Default::default()
}; };
@ -380,7 +377,7 @@ mod tests {
let sprite = Sprite { let sprite = Sprite {
image, image,
anchor: Anchor::TopRight, anchor: Anchor::TOP_RIGHT,
..Default::default() ..Default::default()
}; };
@ -402,7 +399,7 @@ mod tests {
let sprite = Sprite { let sprite = Sprite {
image, image,
anchor: Anchor::BottomLeft, anchor: Anchor::BOTTOM_LEFT,
flip_x: true, flip_x: true,
..Default::default() ..Default::default()
}; };
@ -425,7 +422,7 @@ mod tests {
let sprite = Sprite { let sprite = Sprite {
image, image,
anchor: Anchor::TopRight, anchor: Anchor::TOP_RIGHT,
flip_y: true, flip_y: true,
..Default::default() ..Default::default()
}; };
@ -449,7 +446,7 @@ mod tests {
let sprite = Sprite { let sprite = Sprite {
image, image,
rect: Some(Rect::new(1.5, 3.0, 3.0, 9.5)), rect: Some(Rect::new(1.5, 3.0, 3.0, 9.5)),
anchor: Anchor::BottomLeft, anchor: Anchor::BOTTOM_LEFT,
..Default::default() ..Default::default()
}; };
@ -473,7 +470,7 @@ mod tests {
let sprite = Sprite { let sprite = Sprite {
image, image,
anchor: Anchor::BottomLeft, anchor: Anchor::BOTTOM_LEFT,
texture_atlas: Some(TextureAtlas { texture_atlas: Some(TextureAtlas {
layout: texture_atlas, layout: texture_atlas,
index: 0, index: 0,
@ -501,7 +498,7 @@ mod tests {
let sprite = Sprite { let sprite = Sprite {
image, image,
anchor: Anchor::BottomLeft, anchor: Anchor::BOTTOM_LEFT,
texture_atlas: Some(TextureAtlas { texture_atlas: Some(TextureAtlas {
layout: texture_atlas, layout: texture_atlas,
index: 0, index: 0,

View File

@ -213,7 +213,7 @@ pub fn extract_text2d_sprite(
image_handle_id: atlas_info.texture.id(), image_handle_id: atlas_info.texture.id(),
flip_x: false, flip_x: false,
flip_y: false, flip_y: false,
anchor: Anchor::Center.as_vec(), anchor: Anchor::CENTER.as_vec(),
original_entity, original_entity,
scaling_mode: None, scaling_mode: None,
}); });

View File

@ -132,7 +132,7 @@ fn setup_sprites(mut commands: Commands, asset_server: Res<AssetServer>) {
TextLayout::new_with_justify(JustifyText::Center), TextLayout::new_with_justify(JustifyText::Center),
TextFont::from_font_size(15.), TextFont::from_font_size(15.),
Transform::from_xyz(0., -0.5 * rect.size.y - 10., 0.), Transform::from_xyz(0., -0.5 * rect.size.y - 10., 0.),
bevy::sprite::Anchor::TopCenter, bevy::sprite::Anchor::TOP_CENTER,
)); ));
}); });
} }
@ -278,7 +278,7 @@ fn setup_texture_atlas(
TextLayout::new_with_justify(JustifyText::Center), TextLayout::new_with_justify(JustifyText::Center),
TextFont::from_font_size(15.), TextFont::from_font_size(15.),
Transform::from_xyz(0., -0.5 * sprite_sheet.size.y - 10., 0.), Transform::from_xyz(0., -0.5 * sprite_sheet.size.y - 10., 0.),
bevy::sprite::Anchor::TopCenter, bevy::sprite::Anchor::TOP_CENTER,
)); ));
}); });
} }

View File

@ -96,7 +96,7 @@ fn spawn_sprites(
text_style, text_style,
TextLayout::new_with_justify(JustifyText::Center), TextLayout::new_with_justify(JustifyText::Center),
Transform::from_xyz(0., -0.5 * size.y - 10., 0.0), Transform::from_xyz(0., -0.5 * size.y - 10., 0.0),
bevy::sprite::Anchor::TopCenter, bevy::sprite::Anchor::TOP_CENTER,
)], )],
)); ));
position.x += 0.5 * size.x + gap; position.x += 0.5 * size.x + gap;

View File

@ -129,10 +129,10 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
)) ))
.with_children(|commands| { .with_children(|commands| {
for (text_anchor, color) in [ for (text_anchor, color) in [
(Anchor::TopLeft, Color::Srgba(LIGHT_SALMON)), (Anchor::TOP_LEFT, Color::Srgba(LIGHT_SALMON)),
(Anchor::TopRight, Color::Srgba(LIGHT_GREEN)), (Anchor::TOP_RIGHT, Color::Srgba(LIGHT_GREEN)),
(Anchor::BottomRight, Color::Srgba(LIGHT_BLUE)), (Anchor::BOTTOM_RIGHT, Color::Srgba(LIGHT_BLUE)),
(Anchor::BottomLeft, Color::Srgba(LIGHT_YELLOW)), (Anchor::BOTTOM_LEFT, Color::Srgba(LIGHT_YELLOW)),
] { ] {
commands commands
.spawn(( .spawn((

View File

@ -38,16 +38,15 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
.spawn((Transform::default(), Visibility::default())) .spawn((Transform::default(), Visibility::default()))
.with_children(|commands| { .with_children(|commands| {
for (anchor_index, anchor) in [ for (anchor_index, anchor) in [
Anchor::TopLeft, Anchor::TOP_LEFT,
Anchor::TopCenter, Anchor::TOP_CENTER,
Anchor::TopRight, Anchor::TOP_RIGHT,
Anchor::CenterLeft, Anchor::CENTER_LEFT,
Anchor::Center, Anchor::CENTER,
Anchor::CenterRight, Anchor::CENTER_RIGHT,
Anchor::BottomLeft, Anchor::BOTTOM_LEFT,
Anchor::BottomCenter, Anchor::BOTTOM_CENTER,
Anchor::BottomRight, Anchor::BOTTOM_RIGHT,
Anchor::Custom(Vec2::new(0.5, 0.5)),
] ]
.iter() .iter()
.enumerate() .enumerate()

View File

@ -101,7 +101,7 @@ fn setup(mut commands: Commands, args: Res<Args>) {
Text2d::new(text_string), Text2d::new(text_string),
text_font.clone(), text_font.clone(),
TextColor(RED.into()), TextColor(RED.into()),
bevy::sprite::Anchor::Center, bevy::sprite::Anchor::CENTER,
TextBounds::new_horizontal(1000.), TextBounds::new_horizontal(1000.),
text_block, text_block,
)); ));

View File

@ -215,10 +215,10 @@ mod text {
)); ));
for anchor in [ for anchor in [
Anchor::TopLeft, Anchor::TOP_LEFT,
Anchor::TopRight, Anchor::TOP_RIGHT,
Anchor::BottomRight, Anchor::BOTTOM_RIGHT,
Anchor::BottomLeft, Anchor::BOTTOM_LEFT,
] { ] {
let mut text = commands.spawn(( let mut text = commands.spawn((
Text2d::new("L R\n"), Text2d::new("L R\n"),
@ -229,7 +229,7 @@ mod text {
)); ));
text.with_children(|parent| { text.with_children(|parent| {
parent.spawn(( parent.spawn((
TextSpan::new(format!("{anchor:?}\n")), TextSpan::new(format!("{}, {}\n", anchor.x, anchor.y)),
TextFont::from_font_size(14.0), TextFont::from_font_size(14.0),
TextColor(palettes::tailwind::BLUE_400.into()), TextColor(palettes::tailwind::BLUE_400.into()),
)); ));

View File

@ -287,7 +287,7 @@ fn setup_sticks(
( (
Text2d::default(), Text2d::default(),
Transform::from_xyz(0., STICK_BOUNDS_SIZE + 2., 4.), Transform::from_xyz(0., STICK_BOUNDS_SIZE + 2., 4.),
Anchor::BottomCenter, Anchor::BOTTOM_CENTER,
TextWithAxes { x_axis, y_axis }, TextWithAxes { x_axis, y_axis },
children![ children![
(TextSpan(format!("{:.3}", 0.)), style.clone()), (TextSpan(format!("{:.3}", 0.)), style.clone()),