spitfire_gui/
context.rs

1use crate::{interactions::GuiInteractionsEngine, renderer::GuiRenderer};
2use fontdue::layout::{HorizontalAlign, VerticalAlign};
3#[cfg(target_arch = "wasm32")]
4use instant::Instant;
5use raui_core::{
6    application::Application,
7    layout::{
8        CoordsMapping, CoordsMappingScaling,
9        default_layout_engine::{DefaultLayoutEngine, TextMeasurementEngine},
10    },
11    make_widget,
12    widget::{
13        component::containers::content_box::content_box,
14        unit::text::{TextBox, TextBoxHorizontalAlign, TextBoxSizeValue, TextBoxVerticalAlign},
15        utils::{Color, Rect, Vec2},
16    },
17};
18use raui_immediate::*;
19use spitfire_draw::{
20    context::DrawContext,
21    text::Text,
22    utils::{ShaderRef, Vertex},
23};
24use spitfire_fontdue::*;
25use spitfire_glow::{
26    graphics::{Graphics, Texture},
27    renderer::{GlowTextureFiltering, GlowTextureFormat},
28};
29#[cfg(not(target_arch = "wasm32"))]
30use std::time::Instant;
31
32pub struct GuiContext {
33    pub coords_map_scaling: CoordsMappingScaling,
34    pub texture_filtering: GlowTextureFiltering,
35    pub interactions: GuiInteractionsEngine,
36    pub application: Application,
37    text_renderer: TextRenderer<Color>,
38    immediate: ImmediateContext,
39    timer: Instant,
40    glyphs_texture: Option<Texture>,
41}
42
43impl Default for GuiContext {
44    fn default() -> Self {
45        Self {
46            coords_map_scaling: Default::default(),
47            texture_filtering: Default::default(),
48            interactions: Default::default(),
49            application: Default::default(),
50            text_renderer: Default::default(),
51            immediate: Default::default(),
52            timer: Instant::now(),
53            glyphs_texture: None,
54        }
55    }
56}
57
58impl GuiContext {
59    pub fn mark_dirty(&mut self) {
60        self.application.mark_dirty();
61    }
62
63    pub fn begin_frame(&self) {
64        ImmediateContext::activate(&self.immediate);
65        begin();
66    }
67
68    pub fn end_frame(
69        &mut self,
70        draw: &mut DrawContext,
71        graphics: &mut Graphics<Vertex>,
72        colored_shader: &ShaderRef,
73        textured_shader: &ShaderRef,
74        text_shader: &ShaderRef,
75    ) {
76        let widgets = end();
77        ImmediateContext::deactivate();
78        self.application
79            .apply(make_widget!(content_box).key("root").listed_slots(widgets));
80        let elapsed = std::mem::replace(&mut self.timer, Instant::now())
81            .elapsed()
82            .as_secs_f32();
83        self.timer = Instant::now();
84        self.application.animations_delta_time = elapsed;
85        let coords_mapping = CoordsMapping::new_scaling(
86            Rect {
87                left: 0.0,
88                right: graphics.state.main_camera.screen_size.x,
89                top: 0.0,
90                bottom: graphics.state.main_camera.screen_size.y,
91            },
92            self.coords_map_scaling,
93        );
94        if self.application.process() {
95            let mut layout_engine =
96                DefaultLayoutEngine::new(GuiTextMeasurementsEngine { context: draw });
97            let _ = self.application.layout(&coords_mapping, &mut layout_engine);
98        }
99        self.interactions.maintain(&coords_mapping);
100        let _ = self.application.interact(&mut self.interactions);
101        self.application.consume_signals();
102        let mut renderer = GuiRenderer {
103            texture_filtering: self.texture_filtering,
104            draw,
105            graphics,
106            colored_shader,
107            textured_shader,
108            text_shader,
109        };
110        let _ = self.application.render(&coords_mapping, &mut renderer);
111        let [w, h, d] = self.text_renderer.atlas_size();
112        if let Some(texture) = self.glyphs_texture.as_mut() {
113            texture.upload(
114                w as _,
115                h as _,
116                d as _,
117                GlowTextureFormat::Monochromatic,
118                Some(self.text_renderer.image()),
119            );
120        } else {
121            self.glyphs_texture = graphics
122                .texture(
123                    w as _,
124                    h as _,
125                    d as _,
126                    GlowTextureFormat::Monochromatic,
127                    Some(self.text_renderer.image()),
128                )
129                .ok();
130        }
131    }
132}
133
134pub struct GuiTextMeasurementsEngine<'a> {
135    context: &'a DrawContext,
136}
137
138impl TextMeasurementEngine for GuiTextMeasurementsEngine<'_> {
139    fn measure_text(
140        &self,
141        size_available: Vec2,
142        mapping: &CoordsMapping,
143        unit: &TextBox,
144    ) -> Option<Rect> {
145        let rect = mapping.virtual_to_real_rect(
146            Rect {
147                left: 0.0,
148                right: size_available.x,
149                top: 0.0,
150                bottom: size_available.y,
151            },
152            false,
153        );
154        let max_width = match unit.width {
155            TextBoxSizeValue::Content => None,
156            TextBoxSizeValue::Fill => Some(rect.width()),
157            TextBoxSizeValue::Exact(v) => Some(v * mapping.scale().x),
158        };
159        let max_height = match unit.height {
160            TextBoxSizeValue::Content => None,
161            TextBoxSizeValue::Fill => Some(rect.height()),
162            TextBoxSizeValue::Exact(v) => Some(v * mapping.scale().y),
163        };
164        let mut text = Text::default()
165            .font(unit.font.name.to_owned())
166            .size(unit.font.size * mapping.scalar_scale(false))
167            .text(unit.text.to_owned())
168            .horizontal_align(match unit.horizontal_align {
169                TextBoxHorizontalAlign::Left => HorizontalAlign::Left,
170                TextBoxHorizontalAlign::Center => HorizontalAlign::Center,
171                TextBoxHorizontalAlign::Right => HorizontalAlign::Right,
172            })
173            .vertical_align(match unit.vertical_align {
174                TextBoxVerticalAlign::Top => VerticalAlign::Top,
175                TextBoxVerticalAlign::Middle => VerticalAlign::Middle,
176                TextBoxVerticalAlign::Bottom => VerticalAlign::Bottom,
177            });
178        text.width = max_width;
179        text.height = max_height;
180        text.get_local_space_bounding_box(self.context, false)
181            .map(|rect| Rect {
182                left: rect.x,
183                top: rect.y,
184                right: rect.w,
185                bottom: rect.h,
186            })
187    }
188}