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}