raekna-ui 0.2.1

This crate contains functionality to draw a plain text calculator using wgpu.
Documentation
use wgpu::{util::StagingBelt, CommandEncoder, Device, TextureFormat, TextureView};
use wgpu_glyph::{ab_glyph::FontArc, GlyphBrush, GlyphBrushBuilder, Section, Text};

use crate::{
    constants::{TEXT_COLOR, TEXT_PADDING, TEXT_SCALING},
    coordinator::{content::Content, dimensions::Dimensions, text_buffer::entry},
};

pub struct TextPainter {
    window_size: (u32, u32),
    glyph_brush: GlyphBrush<()>,
    staging_belt: StagingBelt,
}

impl TextPainter {
    pub fn new(
        size: &winit::dpi::PhysicalSize<u32>,
        device: &Device,
        render_format: TextureFormat,
    ) -> Self {
        let window_size = (size.width, size.height);

        let inconsolata =
            FontArc::try_from_slice(include_bytes!("./resources/Inconsolata-Regular.ttf")).unwrap();
        let glyph_brush = GlyphBrushBuilder::using_font(inconsolata).build(device, render_format);

        let staging_belt = wgpu::util::StagingBelt::new(1024);

        Self {
            window_size,
            glyph_brush,
            staging_belt,
        }
    }

    pub fn resize(&mut self, new_size: &winit::dpi::PhysicalSize<u32>) {
        self.window_size = (new_size.width, new_size.height);
    }

    pub fn draw(
        &mut self,
        device: &Device,
        target: &TextureView,
        encoder: &mut CommandEncoder,
        content: &Content,
        dimensions: &Dimensions,
    ) {
        Self::make_sections(content, dimensions)
            .into_iter()
            .for_each(|section| self.glyph_brush.queue(section));

        let (width, height) = self.window_size;
        self.glyph_brush
            .draw_queued(
                device,
                &mut self.staging_belt,
                encoder,
                target,
                width,
                height,
            )
            .expect("Draw queued");
        self.staging_belt.finish();
    }

    fn make_sections<'a>(content: &'a Content, dimensions: &Dimensions) -> Vec<Section<'a>> {
        let mut y_offset = TEXT_PADDING - content.text_buffer.current_scroll;
        let result_offset = (3_f32 * TEXT_PADDING)
            + (dimensions.glyph_width() * dimensions.content_columns() as f32);
        let mut output = vec![];
        content
            .text_buffer
            .entries
            .iter()
            .enumerate()
            .for_each(|(i, entry)| {
                entry
                    .content
                    .iter()
                    .zip(entry.results.iter())
                    .for_each(|(c, r)| {
                        let is_hidden = y_offset > dimensions.window_height()
                            || y_offset + dimensions.glyph_height() < 0_f32;
                        if !is_hidden {
                            output.extend_from_slice(&Self::line_to_sections(
                                content
                                    .calculator
                                    .get_line(i)
                                    .expect("i cannot be out of bounds"),
                                y_offset,
                                result_offset,
                                c,
                                r,
                                dimensions,
                            ));
                        }
                        y_offset += TEXT_PADDING + dimensions.glyph_height();
                    })
            });
        output
    }

    fn line_to_sections<'a>(
        text: (&'a str, &'a str),
        y_offset: f32,
        result_offset: f32,
        c: &entry::Content,
        r: &entry::Result,
        dimensions: &Dimensions,
    ) -> Vec<Section<'a>> {
        let (cl, rl) = text;
        let mut output = vec![];

        let start_offset = c.segments.get(0).map(|s| s.start).unwrap_or(0);
        c.segments.iter().for_each(|s| {
            let text = &cl[s.start..s.end];
            let x_offset =
                TEXT_PADDING + ((s.start - start_offset) as f32 * dimensions.glyph_width());
            output.push(Self::build_section(text, x_offset, y_offset));
        });
        match r {
            entry::Result::None => {}
            entry::Result::Elipsis => {
                let chars = dimensions.result_columns() - 3;
                output.push(Self::build_section(&rl[..chars], result_offset, y_offset));
                let x_offset = result_offset + (chars as f32 * dimensions.glyph_width());
                output.push(Self::build_section("...", x_offset, y_offset));
            }
            entry::Result::Full => {
                let padding_chars = dimensions.result_columns() - rl.len();
                let padding = (padding_chars as f32 * dimensions.glyph_width()) / 2_f32;
                let x_offset = result_offset + padding;
                output.push(Self::build_section(rl, x_offset, y_offset));
            }
        }

        output
    }

    fn build_section(text: &str, x_offset: f32, y_offset: f32) -> Section {
        Section {
            screen_position: (x_offset, y_offset),
            text: vec![Text::new(text)
                .with_color(TEXT_COLOR)
                .with_scale(TEXT_SCALING)],
            ..Section::default()
        }
    }
}