spitfire_gui/renderer.rs
1use fontdue::layout::{HorizontalAlign, VerticalAlign};
2use raui_core::prelude::*;
3use spitfire_draw::prelude::*;
4use spitfire_glow::prelude::*;
5use vek::{Rgba, Vec2};
6
7pub struct GuiRenderer<'a> {
8 pub texture_filtering: GlowTextureFiltering,
9 pub draw: &'a mut DrawContext,
10 pub graphics: &'a mut Graphics<Vertex>,
11 pub colored_shader: &'a ShaderRef,
12 pub textured_shader: &'a ShaderRef,
13 pub text_shader: &'a ShaderRef,
14}
15
16impl GuiRenderer<'_> {
17 fn draw_node(&mut self, node: &WidgetUnit, mapping: &CoordsMapping, layout: &Layout) {
18 match node {
19 WidgetUnit::None | WidgetUnit::PortalBox(_) => {}
20 WidgetUnit::AreaBox(node) => {
21 self.draw_node(&node.slot, mapping, layout);
22 }
23 WidgetUnit::ContentBox(node) => {
24 for item in &node.items {
25 self.draw_node(&item.slot, mapping, layout);
26 }
27 }
28 WidgetUnit::FlexBox(node) => {
29 for item in &node.items {
30 self.draw_node(&item.slot, mapping, layout);
31 }
32 }
33 WidgetUnit::GridBox(node) => {
34 for item in &node.items {
35 self.draw_node(&item.slot, mapping, layout);
36 }
37 }
38 WidgetUnit::SizeBox(node) => {
39 self.draw_node(&node.slot, mapping, layout);
40 }
41 WidgetUnit::ImageBox(node) => {
42 if let Some(layout) = layout.items.get(&node.id) {
43 let rect = mapping.virtual_to_real_rect(layout.ui_space, false);
44 match &node.material {
45 ImageBoxMaterial::Color(color) => {
46 let tint = Rgba {
47 r: color.color.r,
48 g: color.color.g,
49 b: color.color.b,
50 a: color.color.a,
51 };
52 let mut size = Vec2::new(rect.width(), rect.height());
53 let mut position = Vec2::new(rect.left, rect.top);
54 match &color.scaling {
55 ImageBoxImageScaling::Stretch => {
56 Sprite::default()
57 .shader(self.colored_shader.clone())
58 .tint(tint)
59 .size(size)
60 .position(position)
61 .blending(GlowBlending::Alpha)
62 .screen_space(true)
63 .draw(self.draw, self.graphics);
64 }
65 ImageBoxImageScaling::Frame(frame) => {
66 position += size * 0.5;
67 if frame.frame_keep_aspect_ratio {
68 let source_aspect =
69 frame.source.width() / frame.source.height();
70 let size_aspect = size.x / size.y;
71 if source_aspect >= size_aspect {
72 size.y /= source_aspect;
73 } else {
74 size.x *= source_aspect;
75 }
76 }
77 let scale = mapping.scalar_scale(false);
78 NineSliceSprite::default()
79 .shader(self.colored_shader.clone())
80 .tint(tint)
81 .size(size)
82 .position(position)
83 .pivot(0.5.into())
84 .blending(GlowBlending::Alpha)
85 .margins_source(NineSliceMargins {
86 left: frame.source.left,
87 right: frame.source.right,
88 top: frame.source.top,
89 bottom: frame.source.bottom,
90 })
91 .margins_target(NineSliceMargins {
92 left: frame.destination.left * scale,
93 right: frame.destination.right * scale,
94 top: frame.destination.top * scale,
95 bottom: frame.destination.bottom * scale,
96 })
97 .frame_only(frame.frame_only)
98 .screen_space(true)
99 .draw(self.draw, self.graphics);
100 }
101 }
102 }
103 ImageBoxMaterial::Image(image) => {
104 let texture = TextureRef::name(image.id.to_owned());
105 let rect = if let Some(aspect) = node.content_keep_aspect_ratio {
106 let size = self
107 .draw
108 .texture(Some(&texture))
109 .map(|texture| {
110 Vec2::new(texture.width() as f32, texture.height() as f32)
111 })
112 .unwrap_or(Vec2::one());
113 let ox = rect.left;
114 let oy = rect.top;
115 let iw = rect.width();
116 let ih = rect.height();
117 let ra = size.x / size.y;
118 let ia = iw / ih;
119 let scale = if (ra >= ia) != aspect.outside {
120 iw / size.x
121 } else {
122 ih / size.y
123 };
124 let w = size.x * scale;
125 let h = size.y * scale;
126 let ow = lerp(0.0, iw - w, aspect.horizontal_alignment);
127 let oh = lerp(0.0, ih - h, aspect.vertical_alignment);
128 Rect {
129 left: ox + ow,
130 right: ox + ow + w,
131 top: oy + oh,
132 bottom: oy + oh + h,
133 }
134 } else {
135 rect
136 };
137 let tint = Rgba {
138 r: image.tint.r,
139 g: image.tint.g,
140 b: image.tint.b,
141 a: image.tint.a,
142 };
143 let mut size = Vec2::new(rect.width(), rect.height());
144 let mut position = Vec2::new(rect.left, rect.top);
145 match &image.scaling {
146 ImageBoxImageScaling::Stretch => {
147 Sprite::single(SpriteTexture {
148 sampler: "u_image".into(),
149 texture,
150 filtering: self.texture_filtering,
151 })
152 .shader(self.textured_shader.clone())
153 .region_page(
154 image
155 .source_rect
156 .map(|rect| vek::Rect {
157 x: rect.left,
158 y: rect.top,
159 w: rect.width(),
160 h: rect.height(),
161 })
162 .unwrap_or_else(|| vek::Rect {
163 x: 0.0,
164 y: 0.0,
165 w: 1.0,
166 h: 1.0,
167 }),
168 0.0,
169 )
170 .tint(tint)
171 .size(size)
172 .position(position)
173 .blending(GlowBlending::Alpha)
174 .screen_space(true)
175 .draw(self.draw, self.graphics);
176 }
177 ImageBoxImageScaling::Frame(frame) => {
178 position += size * 0.5;
179 if frame.frame_keep_aspect_ratio {
180 let source_aspect =
181 frame.source.width() / frame.source.height();
182 let size_aspect = size.x / size.y;
183 if source_aspect >= size_aspect {
184 size.y /= source_aspect;
185 } else {
186 size.x *= source_aspect;
187 }
188 }
189 let scale = mapping.scalar_scale(false);
190 NineSliceSprite::single(SpriteTexture {
191 sampler: "u_image".into(),
192 texture: TextureRef::name(image.id.to_owned()),
193 filtering: self.texture_filtering,
194 })
195 .shader(self.textured_shader.clone())
196 .tint(tint)
197 .size(size)
198 .position(position)
199 .pivot(0.5.into())
200 .blending(GlowBlending::Alpha)
201 .margins_source(NineSliceMargins {
202 left: frame.source.left,
203 right: frame.source.right,
204 top: frame.source.top,
205 bottom: frame.source.bottom,
206 })
207 .margins_target(NineSliceMargins {
208 left: frame.destination.left * scale,
209 right: frame.destination.right * scale,
210 top: frame.destination.top * scale,
211 bottom: frame.destination.bottom * scale,
212 })
213 .frame_only(frame.frame_only)
214 .screen_space(true)
215 .draw(self.draw, self.graphics);
216 }
217 }
218 }
219 ImageBoxMaterial::Procedural(_) => {
220 unimplemented!(
221 "Procedural images are not yet implemented in this version!"
222 );
223 }
224 }
225 }
226 }
227 WidgetUnit::TextBox(node) => {
228 if let Some(layout) = layout.items.get(node.id()) {
229 let rect = mapping.virtual_to_real_rect(layout.ui_space, false);
230 Text::default()
231 .shader(self.text_shader.clone())
232 .font(node.font.name.to_owned())
233 .size(node.font.size * mapping.scalar_scale(false))
234 .text(node.text.to_owned())
235 .tint(Rgba {
236 r: node.color.r,
237 g: node.color.g,
238 b: node.color.b,
239 a: node.color.a,
240 })
241 .horizontal_align(match node.horizontal_align {
242 TextBoxHorizontalAlign::Left => HorizontalAlign::Left,
243 TextBoxHorizontalAlign::Center => HorizontalAlign::Center,
244 TextBoxHorizontalAlign::Right => HorizontalAlign::Right,
245 })
246 .vertical_align(match node.vertical_align {
247 TextBoxVerticalAlign::Top => VerticalAlign::Top,
248 TextBoxVerticalAlign::Middle => VerticalAlign::Middle,
249 TextBoxVerticalAlign::Bottom => VerticalAlign::Bottom,
250 })
251 .position(Vec2::new(rect.left, rect.top))
252 .width(rect.width())
253 .height(rect.height())
254 .screen_space(true)
255 .draw(self.draw, self.graphics);
256 }
257 }
258 }
259 }
260}
261
262impl Renderer<(), ()> for GuiRenderer<'_> {
263 fn render(
264 &mut self,
265 tree: &WidgetUnit,
266 mapping: &CoordsMapping,
267 layout: &Layout,
268 ) -> Result<(), ()> {
269 self.draw_node(tree, mapping, layout);
270 Ok(())
271 }
272}