mcsdf-font-tech-demo 1.0.1

Multi-channel signed distance fields font tech demo
use crate::ui::widget::{UILayout, UISize, UIWidget};
use glium::backend::Facade;
use glium::draw_parameters::DrawParameters;
use glium::index::PrimitiveType;
use glium::{
    implement_vertex, program, uniform, Blend, Frame, IndexBuffer, Program, Surface, VertexBuffer,
};
use std::rc::Rc;

#[derive(Copy, Clone)]
struct UIBlockVertex {
    pos: [f32; 2],
}

impl UIBlockVertex {
    fn new(x: f32, y: f32) -> Self {
        UIBlockVertex { pos: [x, y] }
    }
}

implement_vertex!(UIBlockVertex, pos);

pub struct UIBlockContext {
    program: Program,
    vertex_buffer: VertexBuffer<UIBlockVertex>,
    index_buffer: IndexBuffer<u16>,
}

impl UIBlockContext {
    #[allow(clippy::redundant_closure)]
    pub fn new<F: ?Sized + Facade>(facade: &F) -> Self {
        let program = program!(facade, 140 => {
        vertex: r#"
            #version 140
            
            in vec2 pos;
            out vec2 vPos;
            out vec2 vMask;

            uniform float uRadius;
            uniform float uSharpness;
            uniform vec2 uScreen;
            uniform vec2 uPosition;
            uniform vec2 uSize;

            void main() {
                vec2 sharpness = vec2(uSharpness);
                vec2 radius = vec2(uRadius);
                vec2 blockSize = uSize + 2.0 * sharpness;
                vec2 size = pos / uScreen * blockSize;
                vec2 offset = (uPosition - sharpness) / uScreen;
                gl_Position = vec4((size + offset) * 2.0 - 1.0, 0.0, 1.0);
                vPos = pos * blockSize - sharpness - radius;
                vMask = uSize - 2.0 * radius;
            }
        "#,
        fragment: r#"
            #version 140

            in vec2 vPos;
            in vec2 vMask;
            out vec4 color;

            uniform float uAlpha;
            uniform float uSharpness;
            uniform float uRadius;
            uniform vec2 uSize;

            uniform float uLeftOffset;
            uniform vec3 uLeftColor;
            uniform float uRightOffset;
            uniform vec3 uRightColor;
            uniform float uInnerShadow;
            uniform vec3 uShadeColor;

            void main() {
                vec2 mask = clamp(vPos, vec2(0.0), vMask);
                float dist = length(vPos - mask);
                float area = 1.0 - clamp((dist - uRadius) / uSharpness, 0.0, 1.0);
                float shade = smoothstep(uInnerShadow, 0.0, dist);
                vec3 c = mix(uLeftColor, uRightColor, smoothstep(uLeftOffset, uRightOffset, vPos.x));
                c = mix(uShadeColor, c, shade);
                color = vec4(c, area * uAlpha);
            }
        "#,
        }).expect("Cannot create program for UIBlock");

        let vertex_buffer = VertexBuffer::immutable(
            facade,
            &[
                UIBlockVertex::new(0.0, 0.0),
                UIBlockVertex::new(0.0, 1.0),
                UIBlockVertex::new(1.0, 1.0),
                UIBlockVertex::new(1.0, 0.0),
            ],
        )
        .expect("Cannot create vertex buffer for UIBlock");

        let index_buffer =
            IndexBuffer::immutable(facade, PrimitiveType::TrianglesList, &[0, 1, 2, 0, 2, 3])
                .expect("Cannot create index buffer for UIBlock");

        Self {
            program,
            vertex_buffer,
            index_buffer,
        }
    }
}

#[derive(Copy, Clone)]
pub struct UIBlockStyle {
    pub alpha: f32,
    pub radius: f32,
    pub sharpness: f32,
    pub left_offset: f32,
    pub left_color: [f32; 3],
    pub right_offset: f32,
    pub right_color: [f32; 3],
    pub inner_shadow: f32,
    pub shade_color: [f32; 3],
}

#[derive(Clone)]
pub struct UIBlock {
    context: Rc<UIBlockContext>,
    style: UIBlockStyle,
}

impl UIBlock {
    pub fn new(context: Rc<UIBlockContext>, style: UIBlockStyle) -> Self {
        Self { context, style }
    }

    pub fn set_style(&mut self, style: UIBlockStyle) {
        self.style = style;
    }

    pub fn get_style(&self) -> UIBlockStyle {
        self.style
    }

    pub fn render_styled(
        &self,
        frame: &mut Frame,
        layout: UILayout,
        style: UIBlockStyle,
        screen: UISize,
    ) {
        let screen = [screen.width, screen.height];
        let limit = layout.width.min(layout.height) / 2.0;

        frame
            .draw(
                &self.context.vertex_buffer,
                &self.context.index_buffer,
                &self.context.program,
                &uniform! {
                    uAlpha: style.alpha,
                    uRadius: style.radius.min(limit),
                    uSharpness: style.sharpness.min(limit),
                    uSize: [layout.width, layout.height],
                    uScreen: screen,
                    uPosition: [layout.left, layout.top],
                    uLeftOffset: style.left_offset,
                    uLeftColor: style.left_color,
                    uRightOffset: style.right_offset,
                    uRightColor: style.right_color,
                    uInnerShadow: style.inner_shadow,
                    uShadeColor: style.shade_color,
                },
                &DrawParameters {
                    blend: Blend::alpha_blending(),
                    color_mask: (true, true, true, false),
                    ..Default::default()
                },
            )
            .expect("Cannot draw UIBlock");
    }
}

impl UIWidget for UIBlock {
    type Event = ();

    fn render(&self, frame: &mut Frame, layout: UILayout, screen: UISize) {
        self.render_styled(frame, layout, self.style, screen);
    }
}