narui_core 0.1.2

unwrapped core crate of the narui gui framework
Documentation
use crate::{
    eval::layout::{PositionedElement, RenderObjectOrSubPass},
    geom::Rect,
    vulkano_render::primitive_renderer::RenderData,
    Key,
    RenderObject,
    Vec2,
};
use glyph_brush::{
    ab_glyph::{FontArc, PxScale},
    BrushAction,
    BrushError,
    GlyphBrushBuilder,
    Section,
    Text,
};
use hashbrown::HashMap;
use lazy_static::lazy_static;
use ordered_float::OrderedFloat;
use palette::Pixel;
use std::{
    hash::{Hash, Hasher},
    sync::Arc,
};
use vulkano::{
    command_buffer::{CommandBufferExecFuture, PrimaryAutoCommandBuffer},
    device::Queue,
    format::Format,
    image::{ImageDimensions, ImmutableImage, MipmapsCount},
    sync::{GpuFuture, NowFuture},
};

lazy_static! {
    pub static ref FONT: FontArc = FontArc::try_from_slice(notosans::REGULAR_TTF).unwrap();
}

#[derive(Debug, Copy, Clone)]
struct Extra {
    pub color: [f32; 4],
    pub z: f32,
    pub clipping_rect: Rect,
    pub key: Key,
}
impl Extra {
    fn as_arr(&self) -> [OrderedFloat<f32>; 9] {
        [
            OrderedFloat::from(self.color[0]),
            OrderedFloat::from(self.color[1]),
            OrderedFloat::from(self.color[2]),
            OrderedFloat::from(self.color[3]),
            OrderedFloat::from(self.z),
            OrderedFloat::from(self.clipping_rect.pos.x),
            OrderedFloat::from(self.clipping_rect.pos.y),
            OrderedFloat::from(self.clipping_rect.size.x),
            OrderedFloat::from(self.clipping_rect.size.y),
        ]
    }
}
impl Hash for Extra {
    fn hash<H: Hasher>(&self, state: &mut H) { self.as_arr().hash(state) }
}
impl PartialEq for Extra {
    fn eq(&self, other: &Self) -> bool { self.as_arr() == other.as_arr() }
}

type VertexData = (Key, [f32; 4], f32, Rect, Vec2, Vec2);

pub struct GlyphBrush {
    queue: Arc<Queue>,
    glyph_brush: glyph_brush::GlyphBrush<VertexData, Extra>,
    old_data: Vec<(Key, [f32; 4], f32, Rect, Vec2, Vec2)>,
    texture_bytes: Vec<u8>,
    texture: Arc<ImmutableImage>,
    texture_fut: Option<CommandBufferExecFuture<NowFuture, PrimaryAutoCommandBuffer>>,
}

#[derive(Default)]
pub struct GlyphBrushState {
    data: HashMap<Key, tinyset::Set64<u32>>,
}

impl GlyphBrushState {
    pub fn add(&mut self, key: Key, idx: u32) { self.data.entry(key).or_default().insert(idx); }

    pub fn lookup(&'_ mut self, key: Key) -> impl Iterator<Item = u32> + '_ {
        self.data.entry(key).or_default().iter()
    }
}

impl GlyphBrush {
    pub fn new(queue: Arc<Queue>) -> Self {
        let glyph_brush = GlyphBrushBuilder::using_font(FONT.clone()).build();

        let (w, h) = glyph_brush.texture_dimensions();
        let texture_bytes = vec![0u8; (w * h) as usize];

        let (texture, texture_fut) = ImmutableImage::from_iter(
            [0u8].iter().cloned(),
            ImageDimensions::Dim2d { width: 1, height: 1, array_layers: 1 },
            MipmapsCount::One,
            Format::R8_UNORM,
            queue.clone(),
        )
        .unwrap();

        Self {
            queue,
            glyph_brush,
            old_data: vec![],
            texture_bytes,
            texture,
            texture_fut: Some(texture_fut),
        }
    }

    pub fn prerender<'a>(&mut self, render_object: &PositionedElement<'a>) {
        let clipping_rect = if let Some(clipping_rect) = render_object.clipping_rect {
            render_object.rect.clip(clipping_rect)
        } else {
            render_object.rect
        };

        if let RenderObjectOrSubPass::RenderObject(RenderObject::Text { key, text, size, color }) =
            &render_object.element
        {
            self.glyph_brush.queue(
                Section::new()
                    .add_text(Text {
                        text: &*text,
                        scale: PxScale::from(*size),
                        font_id: Default::default(),
                        extra: Extra {
                            color: color.into_linear().into_raw::<[f32; 4]>(),
                            z: 1.0 - render_object.z_index as f32 / 65535.0,
                            clipping_rect,
                            key: *key,
                        },
                    })
                    .with_screen_position(Into::<(f32, f32)>::into(render_object.rect.pos))
                    .with_bounds(Into::<(f32, f32)>::into(render_object.rect.size)),
            );
        }
    }

    pub fn finish(
        &mut self,
        data: &mut RenderData,
    ) -> (GlyphBrushState, Arc<ImmutableImage>, Option<impl GpuFuture>) {
        let (width, height) = self.glyph_brush.texture_dimensions();
        let texture_bytes = &mut self.texture_bytes;
        let mut texture_upload = false;
        let mut state = GlyphBrushState::default();
        match (
            std::mem::take(&mut self.old_data),
            self.glyph_brush.process_queued(
                |patch_rect, patch_data| {
                    let patch_width = (patch_rect.max[0] - patch_rect.min[0]) as usize;
                    for (y, line) in patch_data.chunks(patch_width).enumerate() {
                        let line_offset = (y + patch_rect.min[1] as usize) * width as usize
                            + patch_rect.min[0] as usize;
                        for (i, x) in (line_offset..line_offset + patch_width).enumerate() {
                            texture_bytes[x] = line[i];
                        }
                    }
                    texture_upload = true;
                },
                |vertex_data| {
                    let clipping_rect = vertex_data.extra.clipping_rect;
                    let original_rect = Rect::from_corners(
                        vertex_data.pixel_coords.min.into(),
                        vertex_data.pixel_coords.max.into(),
                    );
                    let clipped = original_rect.clip(clipping_rect);

                    let original_tex_rect = Rect::from_corners(
                        vertex_data.tex_coords.min.into(),
                        vertex_data.tex_coords.max.into(),
                    );
                    let clipped_tex_size =
                        (clipped.size / original_rect.size) * original_tex_rect.size;
                    let clipped_tex_rect =
                        Rect { pos: original_tex_rect.pos, size: clipped_tex_size };

                    (
                        vertex_data.extra.key,
                        vertex_data.extra.color,
                        vertex_data.extra.z,
                        clipped,
                        clipped_tex_rect.near_corner(),
                        clipped_tex_rect.size / clipped.size,
                    )
                },
            ),
        ) {
            (_, Ok(BrushAction::Draw(quads))) | (quads, Ok(BrushAction::ReDraw)) => {
                for (key, color, z, rect, tex_base, tex_scale) in &quads {
                    let vertex_idx =
                        data.add_text_quad_data(*color, *z, *rect, *tex_base, *tex_scale);
                    state.add(*key, vertex_idx);
                }
                self.old_data = quads;
            }
            (quads, Err(BrushError::TextureTooSmall { suggested: (w, h) })) => {
                self.texture_bytes = vec![0u8; (w * h) as usize];
                self.glyph_brush.resize_texture(w, h);
                self.old_data = quads;
            }
        }

        if texture_upload {
            let (texture, texture_fut) = ImmutableImage::from_iter(
                self.texture_bytes.iter().cloned(),
                ImageDimensions::Dim2d { width, height, array_layers: 1 },
                MipmapsCount::One,
                Format::R8_UNORM,
                self.queue.clone(),
            )
            .unwrap();
            self.texture = texture;
            self.texture_fut = Some(texture_fut);
        }

        (state, self.texture.clone(), self.texture_fut.take())
    }

    pub fn render<'a>(
        &mut self,
        render_object: &PositionedElement<'a>,
        data: &mut RenderData,
        state: &mut GlyphBrushState,
    ) {
        if let RenderObjectOrSubPass::RenderObject(RenderObject::Text { key, .. }) =
            &render_object.element
        {
            for idx in state.lookup(*key) {
                data.push_quad(idx);
            }
        }
    }
}