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}