spitfire_gui/
renderer.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
use fontdue::layout::{HorizontalAlign, VerticalAlign};
use raui_core::prelude::*;
use spitfire_draw::prelude::*;
use spitfire_glow::prelude::*;
use vek::{Rgba, Vec2};

pub struct GuiRenderer<'a> {
    pub texture_filtering: GlowTextureFiltering,
    pub draw: &'a mut DrawContext,
    pub graphics: &'a mut Graphics<Vertex>,
    pub colored_shader: &'a ShaderRef,
    pub textured_shader: &'a ShaderRef,
    pub text_shader: &'a ShaderRef,
}

impl<'a> GuiRenderer<'a> {
    fn draw_node(&mut self, node: &WidgetUnit, mapping: &CoordsMapping, layout: &Layout) {
        match node {
            WidgetUnit::None | WidgetUnit::PortalBox(_) => {}
            WidgetUnit::AreaBox(node) => {
                self.draw_node(&node.slot, mapping, layout);
            }
            WidgetUnit::ContentBox(node) => {
                for item in &node.items {
                    self.draw_node(&item.slot, mapping, layout);
                }
            }
            WidgetUnit::FlexBox(node) => {
                for item in &node.items {
                    self.draw_node(&item.slot, mapping, layout);
                }
            }
            WidgetUnit::GridBox(node) => {
                for item in &node.items {
                    self.draw_node(&item.slot, mapping, layout);
                }
            }
            WidgetUnit::SizeBox(node) => {
                self.draw_node(&node.slot, mapping, layout);
            }
            WidgetUnit::ImageBox(node) => {
                if let Some(layout) = layout.items.get(&node.id) {
                    let rect = mapping.virtual_to_real_rect(layout.ui_space, false);
                    match &node.material {
                        ImageBoxMaterial::Color(color) => {
                            let tint = Rgba {
                                r: color.color.r,
                                g: color.color.g,
                                b: color.color.b,
                                a: color.color.a,
                            };
                            let mut size = Vec2::new(rect.width(), rect.height());
                            let mut position = Vec2::new(rect.left, rect.top);
                            match &color.scaling {
                                ImageBoxImageScaling::Stretch => {
                                    Sprite::default()
                                        .shader(self.colored_shader.clone())
                                        .tint(tint)
                                        .size(size)
                                        .position(position)
                                        .blending(GlowBlending::Alpha)
                                        .screen_space(true)
                                        .draw(self.draw, self.graphics);
                                }
                                ImageBoxImageScaling::Frame(frame) => {
                                    position += size * 0.5;
                                    if frame.frame_keep_aspect_ratio {
                                        let source_aspect =
                                            frame.source.width() / frame.source.height();
                                        let size_aspect = size.x / size.y;
                                        if source_aspect >= size_aspect {
                                            size.y /= source_aspect;
                                        } else {
                                            size.x *= source_aspect;
                                        }
                                    }
                                    NineSliceSprite::default()
                                        .shader(self.colored_shader.clone())
                                        .tint(tint)
                                        .size(size)
                                        .position(position)
                                        .pivot(0.5.into())
                                        .blending(GlowBlending::Alpha)
                                        .margins_source(NineSliceMargins {
                                            left: frame.source.left,
                                            right: frame.source.right,
                                            top: frame.source.top,
                                            bottom: frame.source.bottom,
                                        })
                                        .margins_target(NineSliceMargins {
                                            left: frame.destination.left,
                                            right: frame.destination.right,
                                            top: frame.destination.top,
                                            bottom: frame.destination.bottom,
                                        })
                                        .frame_only(frame.frame_only)
                                        .screen_space(true)
                                        .draw(self.draw, self.graphics);
                                }
                            }
                        }
                        ImageBoxMaterial::Image(image) => {
                            let texture = TextureRef::name(image.id.to_owned());
                            let rect = if let Some(aspect) = node.content_keep_aspect_ratio {
                                let size = self
                                    .draw
                                    .texture(Some(&texture))
                                    .map(|texture| {
                                        Vec2::new(texture.width() as f32, texture.height() as f32)
                                    })
                                    .unwrap_or(Vec2::one());
                                let ox = rect.left;
                                let oy = rect.top;
                                let iw = rect.width();
                                let ih = rect.height();
                                let ra = size.x / size.y;
                                let ia = iw / ih;
                                let scale = if (ra >= ia) != aspect.outside {
                                    iw / size.x
                                } else {
                                    ih / size.y
                                };
                                let w = size.x * scale;
                                let h = size.y * scale;
                                let ow = lerp(0.0, iw - w, aspect.horizontal_alignment);
                                let oh = lerp(0.0, ih - h, aspect.vertical_alignment);
                                Rect {
                                    left: ox + ow,
                                    right: ox + ow + w,
                                    top: oy + oh,
                                    bottom: oy + oh + h,
                                }
                            } else {
                                rect
                            };
                            let tint = Rgba {
                                r: image.tint.r,
                                g: image.tint.g,
                                b: image.tint.b,
                                a: image.tint.a,
                            };
                            let mut size = Vec2::new(rect.width(), rect.height());
                            let mut position = Vec2::new(rect.left, rect.top);
                            match &image.scaling {
                                ImageBoxImageScaling::Stretch => {
                                    Sprite::single(SpriteTexture {
                                        sampler: "u_image".into(),
                                        texture,
                                        filtering: self.texture_filtering,
                                    })
                                    .shader(self.textured_shader.clone())
                                    .region_page(
                                        image
                                            .source_rect
                                            .map(|rect| vek::Rect {
                                                x: rect.left,
                                                y: rect.top,
                                                w: rect.width(),
                                                h: rect.height(),
                                            })
                                            .unwrap_or_else(|| vek::Rect {
                                                x: 0.0,
                                                y: 0.0,
                                                w: 1.0,
                                                h: 1.0,
                                            }),
                                        0.0,
                                    )
                                    .tint(tint)
                                    .size(size)
                                    .position(position)
                                    .blending(GlowBlending::Alpha)
                                    .screen_space(true)
                                    .draw(self.draw, self.graphics);
                                }
                                ImageBoxImageScaling::Frame(frame) => {
                                    position += size * 0.5;
                                    if frame.frame_keep_aspect_ratio {
                                        let source_aspect =
                                            frame.source.width() / frame.source.height();
                                        let size_aspect = size.x / size.y;
                                        if source_aspect >= size_aspect {
                                            size.y /= source_aspect;
                                        } else {
                                            size.x *= source_aspect;
                                        }
                                    }
                                    NineSliceSprite::single(SpriteTexture {
                                        sampler: "u_image".into(),
                                        texture: TextureRef::name(image.id.to_owned()),
                                        filtering: self.texture_filtering,
                                    })
                                    .shader(self.textured_shader.clone())
                                    .tint(tint)
                                    .size(size)
                                    .position(position)
                                    .pivot(0.5.into())
                                    .blending(GlowBlending::Alpha)
                                    .margins_source(NineSliceMargins {
                                        left: frame.source.left,
                                        right: frame.source.right,
                                        top: frame.source.top,
                                        bottom: frame.source.bottom,
                                    })
                                    .margins_target(NineSliceMargins {
                                        left: frame.destination.left,
                                        right: frame.destination.right,
                                        top: frame.destination.top,
                                        bottom: frame.destination.bottom,
                                    })
                                    .frame_only(frame.frame_only)
                                    .screen_space(true)
                                    .draw(self.draw, self.graphics);
                                }
                            }
                        }
                        ImageBoxMaterial::Procedural(_) => {
                            unimplemented!(
                                "Procedural images are not yet implemented in this version!"
                            );
                        }
                    }
                }
            }
            WidgetUnit::TextBox(node) => {
                if let Some(layout) = layout.items.get(node.id()) {
                    let rect = mapping.virtual_to_real_rect(layout.ui_space, false);
                    Text::default()
                        .shader(self.text_shader.clone())
                        .font(node.font.name.to_owned())
                        .size(node.font.size * mapping.scalar_scale(false))
                        .text(node.text.to_owned())
                        .tint(Rgba {
                            r: node.color.r,
                            g: node.color.g,
                            b: node.color.b,
                            a: node.color.a,
                        })
                        .horizontal_align(match node.horizontal_align {
                            TextBoxHorizontalAlign::Left => HorizontalAlign::Left,
                            TextBoxHorizontalAlign::Center => HorizontalAlign::Center,
                            TextBoxHorizontalAlign::Right => HorizontalAlign::Right,
                        })
                        .vertical_align(match node.vertical_align {
                            TextBoxVerticalAlign::Top => VerticalAlign::Top,
                            TextBoxVerticalAlign::Middle => VerticalAlign::Middle,
                            TextBoxVerticalAlign::Bottom => VerticalAlign::Bottom,
                        })
                        .position(Vec2::new(rect.left, rect.top))
                        .width(rect.width())
                        .height(rect.height())
                        .screen_space(true)
                        .draw(self.draw, self.graphics);
                }
            }
        }
    }
}

impl<'a> Renderer<(), ()> for GuiRenderer<'a> {
    fn render(
        &mut self,
        tree: &WidgetUnit,
        mapping: &CoordsMapping,
        layout: &Layout,
    ) -> Result<(), ()> {
        self.draw_node(tree, mapping, layout);
        Ok(())
    }
}