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