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 NineSliceSprite::default()
78 .shader(self.colored_shader.clone())
79 .tint(tint)
80 .size(size)
81 .position(position)
82 .pivot(0.5.into())
83 .blending(GlowBlending::Alpha)
84 .margins_source(NineSliceMargins {
85 left: frame.source.left,
86 right: frame.source.right,
87 top: frame.source.top,
88 bottom: frame.source.bottom,
89 })
90 .margins_target(NineSliceMargins {
91 left: frame.destination.left,
92 right: frame.destination.right,
93 top: frame.destination.top,
94 bottom: frame.destination.bottom,
95 })
96 .frame_only(frame.frame_only)
97 .screen_space(true)
98 .draw(self.draw, self.graphics);
99 }
100 }
101 }
102 ImageBoxMaterial::Image(image) => {
103 let texture = TextureRef::name(image.id.to_owned());
104 let rect = if let Some(aspect) = node.content_keep_aspect_ratio {
105 let size = self
106 .draw
107 .texture(Some(&texture))
108 .map(|texture| {
109 Vec2::new(texture.width() as f32, texture.height() as f32)
110 })
111 .unwrap_or(Vec2::one());
112 let ox = rect.left;
113 let oy = rect.top;
114 let iw = rect.width();
115 let ih = rect.height();
116 let ra = size.x / size.y;
117 let ia = iw / ih;
118 let scale = if (ra >= ia) != aspect.outside {
119 iw / size.x
120 } else {
121 ih / size.y
122 };
123 let w = size.x * scale;
124 let h = size.y * scale;
125 let ow = lerp(0.0, iw - w, aspect.horizontal_alignment);
126 let oh = lerp(0.0, ih - h, aspect.vertical_alignment);
127 Rect {
128 left: ox + ow,
129 right: ox + ow + w,
130 top: oy + oh,
131 bottom: oy + oh + h,
132 }
133 } else {
134 rect
135 };
136 let tint = Rgba {
137 r: image.tint.r,
138 g: image.tint.g,
139 b: image.tint.b,
140 a: image.tint.a,
141 };
142 let mut size = Vec2::new(rect.width(), rect.height());
143 let mut position = Vec2::new(rect.left, rect.top);
144 match &image.scaling {
145 ImageBoxImageScaling::Stretch => {
146 Sprite::single(SpriteTexture {
147 sampler: "u_image".into(),
148 texture,
149 filtering: self.texture_filtering,
150 })
151 .shader(self.textured_shader.clone())
152 .region_page(
153 image
154 .source_rect
155 .map(|rect| vek::Rect {
156 x: rect.left,
157 y: rect.top,
158 w: rect.width(),
159 h: rect.height(),
160 })
161 .unwrap_or_else(|| vek::Rect {
162 x: 0.0,
163 y: 0.0,
164 w: 1.0,
165 h: 1.0,
166 }),
167 0.0,
168 )
169 .tint(tint)
170 .size(size)
171 .position(position)
172 .blending(GlowBlending::Alpha)
173 .screen_space(true)
174 .draw(self.draw, self.graphics);
175 }
176 ImageBoxImageScaling::Frame(frame) => {
177 position += size * 0.5;
178 if frame.frame_keep_aspect_ratio {
179 let source_aspect =
180 frame.source.width() / frame.source.height();
181 let size_aspect = size.x / size.y;
182 if source_aspect >= size_aspect {
183 size.y /= source_aspect;
184 } else {
185 size.x *= source_aspect;
186 }
187 }
188 NineSliceSprite::single(SpriteTexture {
189 sampler: "u_image".into(),
190 texture: TextureRef::name(image.id.to_owned()),
191 filtering: self.texture_filtering,
192 })
193 .shader(self.textured_shader.clone())
194 .tint(tint)
195 .size(size)
196 .position(position)
197 .pivot(0.5.into())
198 .blending(GlowBlending::Alpha)
199 .margins_source(NineSliceMargins {
200 left: frame.source.left,
201 right: frame.source.right,
202 top: frame.source.top,
203 bottom: frame.source.bottom,
204 })
205 .margins_target(NineSliceMargins {
206 left: frame.destination.left,
207 right: frame.destination.right,
208 top: frame.destination.top,
209 bottom: frame.destination.bottom,
210 })
211 .frame_only(frame.frame_only)
212 .screen_space(true)
213 .draw(self.draw, self.graphics);
214 }
215 }
216 }
217 ImageBoxMaterial::Procedural(_) => {
218 unimplemented!(
219 "Procedural images are not yet implemented in this version!"
220 );
221 }
222 }
223 }
224 }
225 WidgetUnit::TextBox(node) => {
226 if let Some(layout) = layout.items.get(node.id()) {
227 let rect = mapping.virtual_to_real_rect(layout.ui_space, false);
228 Text::default()
229 .shader(self.text_shader.clone())
230 .font(node.font.name.to_owned())
231 .size(node.font.size * mapping.scalar_scale(false))
232 .text(node.text.to_owned())
233 .tint(Rgba {
234 r: node.color.r,
235 g: node.color.g,
236 b: node.color.b,
237 a: node.color.a,
238 })
239 .horizontal_align(match node.horizontal_align {
240 TextBoxHorizontalAlign::Left => HorizontalAlign::Left,
241 TextBoxHorizontalAlign::Center => HorizontalAlign::Center,
242 TextBoxHorizontalAlign::Right => HorizontalAlign::Right,
243 })
244 .vertical_align(match node.vertical_align {
245 TextBoxVerticalAlign::Top => VerticalAlign::Top,
246 TextBoxVerticalAlign::Middle => VerticalAlign::Middle,
247 TextBoxVerticalAlign::Bottom => VerticalAlign::Bottom,
248 })
249 .position(Vec2::new(rect.left, rect.top))
250 .width(rect.width())
251 .height(rect.height())
252 .screen_space(true)
253 .draw(self.draw, self.graphics);
254 }
255 }
256 }
257 }
258}
259
260impl Renderer<(), ()> for GuiRenderer<'_> {
261 fn render(
262 &mut self,
263 tree: &WidgetUnit,
264 mapping: &CoordsMapping,
265 layout: &Layout,
266 ) -> Result<(), ()> {
267 self.draw_node(tree, mapping, layout);
268 Ok(())
269 }
270}