spitfire_gui/
renderer.rs

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}