Skip to main content

wisp/scene/
flex_text.rs

1//! `FlexText` — late-pass textured-quad scene node.
2//!
3//! Structurally identical to [`Sprite`](crate::scene::Sprite): a
4//! textured quad with anchor + tint sampled from its container's
5//! transform. The difference is *when* the renderer paints it.
6//!
7//! Wisp's render-bucket order is **sprite → graphics → text**.
8//! Charts emit their bars / gridlines / axis lines as
9//! [`Graphics`](crate::scene::Graphics) primitives, so anything in
10//! the sprite pass renders *under* them. That's correct for
11//! background imagery but wrong for axis tick labels, legends, KPI
12//! big numbers — any text that has to read on top of the chart.
13//!
14//! `FlexText` participates in a **fourth render pass** that runs
15//! after [`Text`](crate::scene::Text) (the bitmap-font pipeline),
16//! using the same instanced textured-quad shader as
17//! [`Sprite`](crate::scene::Sprite). The intended producer is the
18//! flexible-text path: rasterise via
19//! [`crate::text::TextTexturePipeline::render`], call
20//! [`crate::texture::render_texture::RenderTexture::as_texture`],
21//! drop the resulting [`crate::texture::Texture`] into a `FlexText`.
22//!
23//! `FlexText` is *purposefully* not auto-named "Label" or "Overlay"
24//! — call it what it is. Charts can compose it for axis labels,
25//! callers can compose it for any "render this textured quad on top
26//! of graphics" use case.
27
28use glam::Vec2;
29
30use crate::color::Color;
31use crate::scene::container::Container;
32use crate::texture::Texture;
33
34/// Late-pass textured quad. Same data shape as
35/// [`Sprite`](crate::scene::Sprite); the render pipeline puts it in
36/// the fourth render pass so it paints after every
37/// [`Graphics`](crate::scene::Graphics) primitive.
38#[derive(Debug, Clone)]
39pub struct FlexText {
40    /// Scene-graph state (transform, alpha, visible, blend mode, parent / children).
41    pub container: Container,
42    /// GPU texture sampled in the fragment shader. Usually the
43    /// output of
44    /// [`crate::text::TextTexturePipeline::render`] +
45    /// [`crate::texture::render_texture::RenderTexture::as_texture`].
46    pub texture: Texture,
47    /// Normalized anchor in `[0, 1]²`. `0,0` = top-left, `0.5, 0.5`
48    /// = centre.
49    pub anchor: Vec2,
50    /// Multiplied with the sampled texel. Defaults to white so the
51    /// glyph colour baked into the source texture wins.
52    pub tint: Color,
53}
54
55impl FlexText {
56    /// Construct a `FlexText` from `texture` with default container,
57    /// top-left anchor, and white tint.
58    #[must_use]
59    pub fn from_texture(texture: Texture) -> Self {
60        Self {
61            container: Container::default(),
62            texture,
63            anchor: Vec2::ZERO,
64            tint: Color::WHITE,
65        }
66    }
67
68    /// Builder: set the anchor.
69    #[must_use]
70    pub fn with_anchor(mut self, anchor: Vec2) -> Self {
71        self.anchor = anchor;
72        self
73    }
74
75    /// Builder: set the tint.
76    #[must_use]
77    pub fn with_tint(mut self, tint: Color) -> Self {
78        self.tint = tint;
79        self
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use crate::application::{AppConfig, Application};
87
88    fn boot_texture() -> Texture {
89        let app = pollster::block_on(Application::new(AppConfig::default())).expect("init");
90        let bytes = vec![255u8; 4 * 4 * 4];
91        Texture::from_rgba(&app, 4, 4, &bytes)
92    }
93
94    #[test]
95    fn from_texture_defaults() {
96        let texture = boot_texture();
97        let node = FlexText::from_texture(texture);
98        assert_eq!(node.anchor, Vec2::ZERO);
99        assert_eq!(node.tint, Color::WHITE);
100    }
101
102    #[test]
103    fn builder_with_anchor() {
104        let texture = boot_texture();
105        let node = FlexText::from_texture(texture).with_anchor(Vec2::splat(0.5));
106        assert_eq!(node.anchor, Vec2::splat(0.5));
107    }
108
109    #[test]
110    fn builder_with_tint() {
111        let texture = boot_texture();
112        let node = FlexText::from_texture(texture).with_tint(Color::RED);
113        assert_eq!(node.tint, Color::RED);
114    }
115}