spitfire-gui 0.36.14

GUI module for Spitfire toolset
Documentation
use crate::{interactions::GuiInteractionsEngine, renderer::GuiRenderer};
use fontdue::layout::{HorizontalAlign, VerticalAlign};
#[cfg(target_arch = "wasm32")]
use instant::Instant;
use raui_core::{
    application::Application,
    layout::{
        CoordsMapping, CoordsMappingScaling,
        default_layout_engine::{DefaultLayoutEngine, TextMeasurementEngine},
    },
    make_widget,
    widget::{
        component::containers::content_box::content_box,
        unit::text::{TextBox, TextBoxHorizontalAlign, TextBoxSizeValue, TextBoxVerticalAlign},
        utils::{Color, Rect, Vec2},
    },
};
use raui_immediate::*;
use spitfire_draw::{
    context::DrawContext,
    text::Text,
    utils::{ShaderRef, Vertex},
};
use spitfire_fontdue::*;
use spitfire_glow::{
    graphics::{Graphics, Texture},
    renderer::{GlowTextureFiltering, GlowTextureFormat},
};
#[cfg(not(target_arch = "wasm32"))]
use std::time::Instant;

pub struct GuiContext {
    pub coords_map_scaling: CoordsMappingScaling,
    pub texture_filtering: GlowTextureFiltering,
    pub interactions: GuiInteractionsEngine,
    pub application: Application,
    text_renderer: TextRenderer<Color>,
    immediate: ImmediateContext,
    timer: Instant,
    glyphs_texture: Option<Texture>,
}

impl Default for GuiContext {
    fn default() -> Self {
        Self {
            coords_map_scaling: Default::default(),
            texture_filtering: Default::default(),
            interactions: Default::default(),
            application: Default::default(),
            text_renderer: Default::default(),
            immediate: Default::default(),
            timer: Instant::now(),
            glyphs_texture: None,
        }
    }
}

impl GuiContext {
    pub fn mark_dirty(&mut self) {
        self.application.mark_dirty();
    }

    pub fn begin_frame(&self) {
        ImmediateContext::activate(&self.immediate);
        begin();
    }

    pub fn end_frame(
        &mut self,
        draw: &mut DrawContext,
        graphics: &mut Graphics<Vertex>,
        colored_shader: &ShaderRef,
        textured_shader: &ShaderRef,
        text_shader: &ShaderRef,
    ) {
        let widgets = end();
        ImmediateContext::deactivate();
        self.application
            .apply(make_widget!(content_box).key("root").listed_slots(widgets));
        let elapsed = std::mem::replace(&mut self.timer, Instant::now())
            .elapsed()
            .as_secs_f32();
        self.timer = Instant::now();
        self.application.animations_delta_time = elapsed;
        let coords_mapping = CoordsMapping::new_scaling(
            Rect {
                left: 0.0,
                right: graphics.state.main_camera.screen_size.x,
                top: 0.0,
                bottom: graphics.state.main_camera.screen_size.y,
            },
            self.coords_map_scaling,
        );
        if self.application.process() {
            let mut layout_engine =
                DefaultLayoutEngine::new(GuiTextMeasurementsEngine { context: draw });
            let _ = self.application.layout(&coords_mapping, &mut layout_engine);
        }
        self.interactions.maintain(&coords_mapping);
        let _ = self.application.interact(&mut self.interactions);
        self.application.consume_signals();
        let mut renderer = GuiRenderer {
            texture_filtering: self.texture_filtering,
            draw,
            graphics,
            colored_shader,
            textured_shader,
            text_shader,
        };
        let _ = self.application.render(&coords_mapping, &mut renderer);
        let [w, h, d] = self.text_renderer.atlas_size();
        if let Some(texture) = self.glyphs_texture.as_mut() {
            texture.upload(
                w as _,
                h as _,
                d as _,
                GlowTextureFormat::Monochromatic,
                Some(self.text_renderer.image()),
            );
        } else {
            self.glyphs_texture = graphics
                .texture(
                    w as _,
                    h as _,
                    d as _,
                    GlowTextureFormat::Monochromatic,
                    Some(self.text_renderer.image()),
                )
                .ok();
        }
    }
}

pub struct GuiTextMeasurementsEngine<'a> {
    context: &'a DrawContext,
}

impl TextMeasurementEngine for GuiTextMeasurementsEngine<'_> {
    fn measure_text(
        &self,
        size_available: Vec2,
        mapping: &CoordsMapping,
        unit: &TextBox,
    ) -> Option<Rect> {
        let rect = mapping.virtual_to_real_rect(
            Rect {
                left: 0.0,
                right: size_available.x,
                top: 0.0,
                bottom: size_available.y,
            },
            false,
        );
        let max_width = match unit.width {
            TextBoxSizeValue::Content => None,
            TextBoxSizeValue::Fill => Some(rect.width()),
            TextBoxSizeValue::Exact(v) => Some(v * mapping.scale().x),
        };
        let max_height = match unit.height {
            TextBoxSizeValue::Content => None,
            TextBoxSizeValue::Fill => Some(rect.height()),
            TextBoxSizeValue::Exact(v) => Some(v * mapping.scale().y),
        };
        let mut text = Text::default()
            .font(unit.font.name.to_owned())
            .size(unit.font.size * mapping.scalar_scale(false))
            .text(unit.text.to_owned())
            .horizontal_align(match unit.horizontal_align {
                TextBoxHorizontalAlign::Left => HorizontalAlign::Left,
                TextBoxHorizontalAlign::Center => HorizontalAlign::Center,
                TextBoxHorizontalAlign::Right => HorizontalAlign::Right,
            })
            .vertical_align(match unit.vertical_align {
                TextBoxVerticalAlign::Top => VerticalAlign::Top,
                TextBoxVerticalAlign::Middle => VerticalAlign::Middle,
                TextBoxVerticalAlign::Bottom => VerticalAlign::Bottom,
            });
        text.width = max_width;
        text.height = max_height;
        text.get_local_space_bounding_box(self.context, false)
            .map(|rect| Rect {
                left: rect.x,
                top: rect.y,
                right: rect.w,
                bottom: rect.h,
            })
    }
}