use bevy_asset::Handle; use bevy_color::Color; use bevy_ecs::{component::Component, reflect::ReflectComponent}; use bevy_image::Image; use bevy_math::{Rect, Vec2}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{sync_world::SyncToRenderWorld, view::Visibility}; use bevy_transform::components::Transform; use crate::{TextureAtlas, TextureSlicer}; /// Describes a sprite to be rendered to a 2D camera #[derive(Component, Debug, Default, Clone, Reflect)] #[require(Transform, Visibility, SyncToRenderWorld)] #[reflect(Component, Default, Debug)] pub struct Sprite { /// The image used to render the sprite pub image: Handle, /// The (optional) texture atlas used to render the sprite pub texture_atlas: Option, /// The sprite's color tint pub color: Color, /// Flip the sprite along the `X` axis pub flip_x: bool, /// Flip the sprite along the `Y` axis pub flip_y: bool, /// An optional custom size for the sprite that will be used when rendering, instead of the size /// of the sprite's image pub custom_size: Option, /// An optional rectangle representing the region of the sprite's image to render, instead of rendering /// the full image. This is an easy one-off alternative to using a [`TextureAtlas`]. /// /// When used with a [`TextureAtlas`], the rect /// is offset by the atlas's minimal (top-left) corner position. pub rect: Option, /// [`Anchor`] point of the sprite in the world pub anchor: Anchor, /// How the sprite's image will be scaled. pub image_mode: SpriteImageMode, } impl Sprite { /// Create a Sprite with a custom size pub fn sized(custom_size: Vec2) -> Self { Sprite { custom_size: Some(custom_size), ..Default::default() } } /// Create a sprite from an image pub fn from_image(image: Handle) -> Self { Self { image, ..Default::default() } } /// Create a sprite from an image, with an associated texture atlas pub fn from_atlas_image(image: Handle, atlas: TextureAtlas) -> Self { Self { image, texture_atlas: Some(atlas), ..Default::default() } } /// Create a sprite from a solid color pub fn from_color(color: impl Into, size: Vec2) -> Self { Self { color: color.into(), custom_size: Some(size), ..Default::default() } } } impl From> for Sprite { fn from(image: Handle) -> Self { Self::from_image(image) } } /// Controls how the image is altered when scaled. #[derive(Default, Debug, Clone, Reflect, PartialEq)] #[reflect(Debug)] pub enum SpriteImageMode { /// The sprite will take on the size of the image by default, and will be stretched or shrunk if [`Sprite::custom_size`] is set. #[default] Auto, /// The texture will be cut in 9 slices, keeping the texture in proportions on resize Sliced(TextureSlicer), /// The texture will be repeated if stretched beyond `stretched_value` Tiled { /// Should the image repeat horizontally tile_x: bool, /// Should the image repeat vertically tile_y: bool, /// The texture will repeat when the ratio between the *drawing dimensions* of texture and the /// *original texture size* are above this value. stretch_value: f32, }, } impl SpriteImageMode { /// Returns true if this mode uses slices internally ([`SpriteImageMode::Sliced`] or [`SpriteImageMode::Tiled`]) #[inline] pub fn uses_slices(&self) -> bool { matches!( self, SpriteImageMode::Sliced(..) | SpriteImageMode::Tiled { .. } ) } } /// How a sprite is positioned relative to its [`Transform`]. /// It defaults to `Anchor::Center`. #[derive(Component, Debug, Clone, Copy, PartialEq, Default, Reflect)] #[reflect(Component, Default, Debug, PartialEq)] #[doc(alias = "pivot")] pub enum Anchor { #[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 { pub fn as_vec(&self) -> Vec2 { match self { 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), Anchor::CenterLeft => Vec2::new(-0.5, 0.0), Anchor::CenterRight => Vec2::new(0.5, 0.0), 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, } } }