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}