raui_app/app/
mod.rs

1pub mod declarative;
2pub mod immediate;
3pub mod retained;
4
5use crate::{
6    TesselateToGraphics, Vertex, asset_manager::AssetsManager, interactions::AppInteractionsEngine,
7    render_worker::RenderWorkersViewModel, text_measurements::AppTextMeasurementsEngine,
8};
9use glutin::{
10    event::{ElementState, Event, VirtualKeyCode, WindowEvent},
11    window::Window,
12};
13use raui_core::{
14    application::Application,
15    interactive::default_interactions_engine::DefaultInteractionsEngine,
16    layout::{CoordsMapping, CoordsMappingScaling, default_layout_engine::DefaultLayoutEngine},
17    view_model::ViewModel,
18    widget::utils::{Color, Rect},
19};
20use raui_tesselate_renderer::{TesselateRenderer, TessselateRendererDebug};
21use spitfire_fontdue::TextRenderer;
22use spitfire_glow::{
23    app::AppControl,
24    graphics::{Graphics, GraphicsBatch, Shader, Texture},
25    renderer::{GlowTextureFormat, GlowUniformValue},
26};
27use std::{collections::HashMap, time::Instant};
28
29pub use spitfire_glow::app::{App, AppConfig};
30
31#[cfg(debug_assertions)]
32const DEBUG_VERTEX: &str = r#"#version 300 es
33    layout(location = 0) in vec2 a_position;
34    out vec4 v_color;
35    uniform mat4 u_projection_view;
36
37    void main() {
38        gl_Position = u_projection_view * vec4(a_position, 0.0, 1.0);
39    }
40    "#;
41
42#[cfg(debug_assertions)]
43const DEBUG_FRAGMENT: &str = r#"#version 300 es
44    precision highp float;
45    precision highp int;
46    out vec4 o_color;
47    uniform float u_time;
48
49    vec3 hsv2rgb(vec3 c) {
50        vec4 K = vec4(1.0, 2.0/3.0, 1.0/3.0, 3.0);
51        vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
52        return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
53    }
54
55    void main() {
56        vec2 pixel = floor(gl_FragCoord.xy);
57        float hue = fract((floor(pixel.x) + floor(pixel.y)) * 0.01 + u_time);
58        o_color = vec4(hsv2rgb(vec3(hue, 1.0, 1.0)), 1.0);
59    }
60    "#;
61
62macro_rules! hash_map {
63    ($($key:ident => $value:expr),* $(,)?) => {{
64        let mut result = HashMap::default();
65        $(
66            result.insert(stringify!($key).into(), $value);
67        )*
68        result
69    }};
70}
71
72pub(crate) struct SharedApp {
73    #[allow(clippy::type_complexity)]
74    on_update: Option<Box<dyn FnMut(&mut Application, &mut AppControl)>>,
75    /// fn(delta time, graphics interface)
76    #[allow(clippy::type_complexity)]
77    on_redraw: Option<
78        Box<dyn FnMut(f32, &mut Graphics<Vertex>, &mut TextRenderer<Color>, &mut AppControl)>,
79    >,
80    #[allow(clippy::type_complexity)]
81    on_event: Option<
82        Box<
83            dyn FnMut(
84                &mut Application,
85                Event<()>,
86                &mut Window,
87                &mut DefaultInteractionsEngine,
88            ) -> bool,
89        >,
90    >,
91    application: Application,
92    interactions: AppInteractionsEngine,
93    text_renderer: TextRenderer<Color>,
94    timer: Instant,
95    time: f32,
96    assets: AssetsManager,
97    coords_mapping: CoordsMapping,
98    pub coords_mapping_scaling: CoordsMappingScaling,
99    missing_texutre: Option<Texture>,
100    glyphs_texture: Option<Texture>,
101    colored_shader: Option<Shader>,
102    textured_shader: Option<Shader>,
103    text_shader: Option<Shader>,
104    #[cfg(debug_assertions)]
105    debug_shader: Option<Shader>,
106    #[cfg(debug_assertions)]
107    pub show_raui_aabb_mode: u8,
108    #[cfg(debug_assertions)]
109    pub show_raui_aabb_key: VirtualKeyCode,
110    #[cfg(debug_assertions)]
111    pub print_raui_tree_key: VirtualKeyCode,
112    #[cfg(debug_assertions)]
113    pub print_raui_layout_key: VirtualKeyCode,
114    #[cfg(debug_assertions)]
115    pub print_raui_interactions_key: VirtualKeyCode,
116}
117
118impl Default for SharedApp {
119    fn default() -> Self {
120        let mut application = Application::default();
121        application.setup(raui_core::widget::setup);
122        application.setup(raui_material::setup);
123        application.view_models.insert(
124            RenderWorkersViewModel::VIEW_MODEL.to_owned(),
125            ViewModel::new(RenderWorkersViewModel::default(), Default::default()),
126        );
127        Self {
128            on_update: None,
129            on_redraw: None,
130            on_event: None,
131            application,
132            interactions: Default::default(),
133            text_renderer: TextRenderer::new(1024, 1024),
134            timer: Instant::now(),
135            time: 0.0,
136            assets: Default::default(),
137            coords_mapping: Default::default(),
138            coords_mapping_scaling: Default::default(),
139            missing_texutre: None,
140            glyphs_texture: None,
141            colored_shader: None,
142            textured_shader: None,
143            text_shader: None,
144            #[cfg(debug_assertions)]
145            debug_shader: None,
146            #[cfg(debug_assertions)]
147            show_raui_aabb_mode: 0,
148            #[cfg(debug_assertions)]
149            show_raui_aabb_key: VirtualKeyCode::F9,
150            #[cfg(debug_assertions)]
151            print_raui_tree_key: VirtualKeyCode::F10,
152            #[cfg(debug_assertions)]
153            print_raui_layout_key: VirtualKeyCode::F11,
154            #[cfg(debug_assertions)]
155            print_raui_interactions_key: VirtualKeyCode::F12,
156        }
157    }
158}
159
160impl SharedApp {
161    fn init(&mut self, graphics: &mut Graphics<Vertex>) {
162        self.missing_texutre = Some(graphics.pixel_texture([255, 255, 255]).unwrap());
163        self.glyphs_texture = Some(graphics.pixel_texture([0, 0, 0]).unwrap());
164        self.colored_shader = Some(
165            graphics
166                .shader(Shader::COLORED_VERTEX_2D, Shader::PASS_FRAGMENT)
167                .unwrap(),
168        );
169        self.textured_shader = Some(
170            graphics
171                .shader(Shader::TEXTURED_VERTEX_2D, Shader::TEXTURED_FRAGMENT)
172                .unwrap(),
173        );
174        self.text_shader = Some(
175            graphics
176                .shader(Shader::TEXT_VERTEX, Shader::TEXT_FRAGMENT)
177                .unwrap(),
178        );
179        #[cfg(debug_assertions)]
180        {
181            self.debug_shader = Some(graphics.shader(DEBUG_VERTEX, DEBUG_FRAGMENT).unwrap());
182        }
183    }
184
185    fn redraw(&mut self, graphics: &mut Graphics<Vertex>, control: &mut AppControl) {
186        let elapsed = self.timer.elapsed();
187        self.timer = Instant::now();
188        self.time += elapsed.as_secs_f32();
189        if let Some(callback) = self.on_update.as_mut() {
190            callback(&mut self.application, control);
191        }
192        self.text_renderer.clear();
193        if let Some(callback) = self.on_redraw.as_mut() {
194            callback(
195                elapsed.as_secs_f32(),
196                graphics,
197                &mut self.text_renderer,
198                control,
199            );
200        }
201        {
202            self.application
203                .view_models
204                .get_mut(RenderWorkersViewModel::VIEW_MODEL)
205                .unwrap()
206                .write::<RenderWorkersViewModel>()
207                .unwrap()
208                .maintain(
209                    graphics,
210                    &mut self.assets,
211                    self.colored_shader.as_ref().unwrap(),
212                    self.textured_shader.as_ref().unwrap(),
213                    self.text_shader.as_ref().unwrap(),
214                );
215        }
216        self.assets.maintain();
217        self.application.animations_delta_time = elapsed.as_secs_f32();
218        self.coords_mapping = CoordsMapping::new_scaling(
219            Rect {
220                left: 0.0,
221                right: graphics.state.main_camera.screen_size.x,
222                top: 0.0,
223                bottom: graphics.state.main_camera.screen_size.y,
224            },
225            self.coords_mapping_scaling,
226        );
227        if self.application.process() {
228            self.assets.load(self.application.rendered_tree(), graphics);
229            let mut layout_engine = DefaultLayoutEngine::new(AppTextMeasurementsEngine {
230                assets: &self.assets,
231            });
232            let _ = self
233                .application
234                .layout(&self.coords_mapping, &mut layout_engine);
235        } else {
236            self.assets.load(self.application.rendered_tree(), graphics);
237        }
238        let _ = self.application.interact(&mut self.interactions);
239        self.application.consume_signals();
240        let matrix = graphics
241            .state
242            .main_camera
243            .world_projection_matrix()
244            .into_col_array();
245        graphics.state.stream.batch_end();
246        for shader in [
247            self.colored_shader.clone(),
248            self.textured_shader.clone(),
249            self.text_shader.clone(),
250            #[cfg(debug_assertions)]
251            self.debug_shader.clone(),
252        ] {
253            graphics.state.stream.batch(GraphicsBatch {
254                shader,
255                uniforms: hash_map! {
256                    u_image => GlowUniformValue::I1(0),
257                    u_projection_view => GlowUniformValue::M4(matrix),
258                    u_time => GlowUniformValue::F1(self.time),
259                },
260                ..Default::default()
261            });
262            graphics.state.stream.batch_end();
263        }
264        let mut converter = TesselateToGraphics {
265            colored_shader: self.colored_shader.as_ref().unwrap(),
266            textured_shader: self.textured_shader.as_ref().unwrap(),
267            text_shader: self.text_shader.as_ref().unwrap(),
268            #[cfg(debug_assertions)]
269            debug_shader: self.debug_shader.as_ref(),
270            glyphs_texture: self.glyphs_texture.as_ref().unwrap(),
271            missing_texture: self.missing_texutre.as_ref().unwrap(),
272            assets: &self.assets,
273            clip_stack: Vec::with_capacity(64),
274            viewport_height: graphics.state.main_camera.screen_size.y as _,
275            projection_view_matrix: matrix,
276        };
277        let mut renderer = TesselateRenderer::new(
278            &self.assets,
279            &mut converter,
280            &mut graphics.state.stream,
281            &mut self.text_renderer,
282            if cfg!(debug_assertions) {
283                match self.show_raui_aabb_mode {
284                    0 => None,
285                    1 => Some(TessselateRendererDebug {
286                        render_non_visual_nodes: false,
287                    }),
288                    2 => Some(TessselateRendererDebug {
289                        render_non_visual_nodes: true,
290                    }),
291                    _ => unreachable!(),
292                }
293            } else {
294                None
295            },
296        );
297        let _ = self.application.render(&self.coords_mapping, &mut renderer);
298        let [w, h, d] = self.text_renderer.atlas_size();
299        self.glyphs_texture.as_mut().unwrap().upload(
300            w as _,
301            h as _,
302            d as _,
303            GlowTextureFormat::Monochromatic,
304            Some(self.text_renderer.image()),
305        );
306    }
307
308    fn event(&mut self, event: Event<()>, window: &mut Window) -> bool {
309        self.interactions.event(&event, &self.coords_mapping);
310        if let Event::WindowEvent {
311            event: WindowEvent::Resized(_),
312            ..
313        } = &event
314        {
315            self.application.mark_dirty();
316        }
317        #[cfg(debug_assertions)]
318        if let Event::WindowEvent {
319            event: WindowEvent::KeyboardInput { input, .. },
320            ..
321        } = &event
322            && input.state == ElementState::Pressed
323            && let Some(key) = input.virtual_keycode
324        {
325            if key == self.show_raui_aabb_key {
326                self.show_raui_aabb_mode = (self.show_raui_aabb_mode + 1) % 3;
327                println!(
328                    "* SHOW RAUI LAYOUT AABB MODE: {:#?}",
329                    self.show_raui_aabb_mode
330                );
331            } else if key == self.print_raui_tree_key {
332                println!("* RAUI TREE: {:#?}", self.application.rendered_tree());
333            } else if key == self.print_raui_layout_key {
334                println!("* RAUI LAYOUT: {:#?}", self.application.layout_data());
335            } else if key == self.print_raui_interactions_key {
336                println!("* RAUI INTERACTIONS: {:#?}", self.interactions);
337            }
338        }
339        self.on_event
340            .as_mut()
341            .map(|callback| {
342                callback(
343                    &mut self.application,
344                    event,
345                    window,
346                    &mut self.interactions.engine,
347                )
348            })
349            .unwrap_or(true)
350    }
351}