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}