spitfire_draw/
text.rs

1use crate::{
2    context::DrawContext,
3    utils::{Drawable, ShaderRef, Vertex},
4};
5use fontdue::layout::{
6    CoordinateSystem, HorizontalAlign, Layout, LayoutSettings, TextStyle, VerticalAlign,
7};
8use spitfire_glow::{
9    graphics::{Graphics, GraphicsBatch},
10    renderer::{GlowBlending, GlowTextureFiltering, GlowUniformValue},
11};
12use std::{borrow::Cow, collections::HashMap};
13use vek::{Mat4, Quaternion, Rgba, Transform, Vec2, Vec3};
14
15pub struct Text {
16    pub shader: Option<ShaderRef>,
17    pub font: Cow<'static, str>,
18    pub size: f32,
19    pub text: Cow<'static, str>,
20    pub tint: Rgba<f32>,
21    pub horizontal_align: HorizontalAlign,
22    pub vertical_align: VerticalAlign,
23    pub width: Option<f32>,
24    pub height: Option<f32>,
25    pub uniforms: HashMap<Cow<'static, str>, GlowUniformValue>,
26    pub transform: Transform<f32, f32, f32>,
27    pub blending: Option<GlowBlending>,
28    pub screen_space: bool,
29}
30
31impl Default for Text {
32    fn default() -> Self {
33        Self {
34            shader: Default::default(),
35            font: Default::default(),
36            size: 32.0,
37            text: Default::default(),
38            tint: Rgba::white(),
39            horizontal_align: HorizontalAlign::Left,
40            vertical_align: VerticalAlign::Top,
41            width: Default::default(),
42            height: Default::default(),
43            uniforms: Default::default(),
44            transform: Default::default(),
45            blending: Default::default(),
46            screen_space: Default::default(),
47        }
48    }
49}
50
51impl Text {
52    pub fn new(shader: ShaderRef) -> Self {
53        Self {
54            shader: Some(shader),
55            ..Default::default()
56        }
57    }
58
59    pub fn shader(mut self, value: ShaderRef) -> Self {
60        self.shader = Some(value);
61        self
62    }
63
64    pub fn font(mut self, value: impl Into<Cow<'static, str>>) -> Self {
65        self.font = value.into();
66        self
67    }
68
69    pub fn size(mut self, value: f32) -> Self {
70        self.size = value;
71        self
72    }
73
74    pub fn text(mut self, value: impl Into<Cow<'static, str>>) -> Self {
75        self.text = value.into();
76        self
77    }
78
79    pub fn tint(mut self, value: Rgba<f32>) -> Self {
80        self.tint = value;
81        self
82    }
83
84    pub fn horizontal_align(mut self, value: HorizontalAlign) -> Self {
85        self.horizontal_align = value;
86        self
87    }
88
89    pub fn vertical_align(mut self, value: VerticalAlign) -> Self {
90        self.vertical_align = value;
91        self
92    }
93
94    pub fn width(mut self, value: f32) -> Self {
95        self.width = Some(value);
96        self
97    }
98
99    pub fn height(mut self, value: f32) -> Self {
100        self.height = Some(value);
101        self
102    }
103
104    pub fn uniform(mut self, key: Cow<'static, str>, value: GlowUniformValue) -> Self {
105        self.uniforms.insert(key, value);
106        self
107    }
108
109    pub fn transform(mut self, value: Transform<f32, f32, f32>) -> Self {
110        self.transform = value;
111        self
112    }
113
114    pub fn position(mut self, value: Vec2<f32>) -> Self {
115        self.transform.position = value.into();
116        self
117    }
118
119    pub fn orientation(mut self, value: Quaternion<f32>) -> Self {
120        self.transform.orientation = value;
121        self
122    }
123
124    pub fn rotation(mut self, angle_radians: f32) -> Self {
125        self.transform.orientation = Quaternion::rotation_z(angle_radians);
126        self
127    }
128
129    pub fn scale(mut self, value: Vec2<f32>) -> Self {
130        self.transform.scale = Vec3::new(value.x, value.y, 1.0);
131        self
132    }
133
134    pub fn blending(mut self, value: GlowBlending) -> Self {
135        self.blending = Some(value);
136        self
137    }
138
139    pub fn screen_space(mut self, value: bool) -> Self {
140        self.screen_space = value;
141        self
142    }
143}
144
145impl Drawable for Text {
146    fn draw(&self, context: &mut DrawContext, graphics: &mut Graphics<Vertex>) {
147        if let Some(index) = context.fonts.index_of(&self.font) {
148            let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
149            layout.reset(&LayoutSettings {
150                x: 0.0,
151                y: 0.0,
152                max_width: self.width,
153                max_height: self.height,
154                horizontal_align: self.horizontal_align,
155                vertical_align: self.vertical_align,
156                ..Default::default()
157            });
158            layout.append(
159                context.fonts.values(),
160                &TextStyle {
161                    text: &self.text,
162                    px: self.size,
163                    font_index: index,
164                    user_data: self.tint,
165                },
166            );
167            context
168                .text_renderer
169                .include(context.fonts.values(), &layout);
170            graphics.stream.batch_optimized(GraphicsBatch {
171                shader: context.shader(self.shader.as_ref()),
172                uniforms: self
173                    .uniforms
174                    .iter()
175                    .map(|(k, v)| (k.clone(), v.to_owned()))
176                    .chain(std::iter::once((
177                        "u_projection_view".into(),
178                        GlowUniformValue::M4(
179                            if self.screen_space {
180                                graphics.main_camera.screen_matrix()
181                            } else {
182                                graphics.main_camera.world_matrix()
183                            }
184                            .into_col_array(),
185                        ),
186                    )))
187                    .chain(std::iter::once(("u_image".into(), GlowUniformValue::I1(0))))
188                    .collect(),
189                textures: if let Some(texture) = context.fonts_texture() {
190                    vec![(texture, GlowTextureFiltering::Linear)]
191                } else {
192                    vec![]
193                },
194                blending: GlowBlending::Alpha,
195                scissor: Default::default(),
196            });
197            let transform = Mat4::from(context.top_transform()) * Mat4::from(self.transform);
198            graphics.stream.transformed(
199                |stream| {
200                    context.text_renderer.render_to_stream(stream);
201                },
202                |vertex| {
203                    let point = transform.mul_point(Vec2::from(vertex.position));
204                    vertex.position[0] = point.x;
205                    vertex.position[1] = point.y;
206                },
207            );
208        }
209    }
210}