Skip to main content

wisp/scene/
sprite.rs

1//! `Sprite` — textured quad with anchor and tint.
2//!
3//! Composed over [`Container`] (no inheritance). Sprites participate in the
4//! scene graph through their inner `container` field; the renderer's batcher
5//! groups sprites that share a texture and blend mode.
6
7use glam::Vec2;
8
9use crate::color::Color;
10use crate::scene::container::Container;
11use crate::texture::Texture;
12
13/// Textured quad node. Local rect is `[0, 1]²`; `anchor` shifts that local
14/// origin (`0,0` = top-left, `1,1` = bottom-right). The sprite's
15/// `Container::transform` is then applied.
16#[derive(Debug, Clone)]
17pub struct Sprite {
18    /// Scene-graph state (transform, alpha, visible, blend mode, parent/children).
19    pub container: Container,
20    /// GPU texture sampled in the fragment shader.
21    pub texture: Texture,
22    /// Normalized anchor in `[0, 1]²`. `0,0` = top-left, `0.5, 0.5` = center.
23    pub anchor: Vec2,
24    /// Multiplied with the sampled texel.
25    pub tint: Color,
26}
27
28impl Sprite {
29    /// Construct a sprite from a texture with default container, top-left
30    /// anchor, and white tint.
31    #[must_use]
32    pub fn from_texture(texture: Texture) -> Self {
33        Self {
34            container: Container::default(),
35            texture,
36            anchor: Vec2::ZERO,
37            tint: Color::WHITE,
38        }
39    }
40
41    /// Builder: set the anchor.
42    #[must_use]
43    pub fn with_anchor(mut self, anchor: Vec2) -> Self {
44        self.anchor = anchor;
45        self
46    }
47
48    /// Builder: set the tint.
49    #[must_use]
50    pub fn with_tint(mut self, tint: Color) -> Self {
51        self.tint = tint;
52        self
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59    use crate::application::{AppConfig, Application};
60
61    fn boot_texture() -> Texture {
62        let app = pollster::block_on(Application::new(AppConfig::default())).expect("init");
63        let bytes = vec![255u8; 4 * 4 * 4];
64        Texture::from_rgba(&app, 4, 4, &bytes)
65    }
66
67    #[test]
68    fn from_texture_defaults() {
69        let texture = boot_texture();
70        let sprite = Sprite::from_texture(texture);
71        assert_eq!(sprite.anchor, Vec2::ZERO);
72        assert_eq!(sprite.tint, Color::WHITE);
73        assert_eq!(
74            sprite.container.transform,
75            crate::scene::Transform::IDENTITY
76        );
77    }
78
79    #[test]
80    fn builder_with_anchor() {
81        let texture = boot_texture();
82        let sprite = Sprite::from_texture(texture).with_anchor(Vec2::splat(0.5));
83        assert_eq!(sprite.anchor, Vec2::splat(0.5));
84    }
85
86    #[test]
87    fn builder_with_tint() {
88        let texture = boot_texture();
89        let sprite = Sprite::from_texture(texture).with_tint(Color::RED);
90        assert_eq!(sprite.tint, Color::RED);
91    }
92}