narui_widgets 0.1.2

core widgets for the narui gui framework
Documentation
use narui_core::{
    layout::Transparent,
    re_export::smallvec::smallvec,
    CallbackContext,
    ContextEffect,
    ContextMeasure,
    Fragment,
    Rect,
    SubPassRenderFunction,
    SubPassSetup,
    Vec2,
    WidgetContext,
};
use narui_macros::{rsx, widget};
use std::sync::Arc;
use vulkano::{
    command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage},
    descriptor_set::PersistentDescriptorSet,
    device::{DeviceOwned, Queue},
    pipeline::{
        graphics::{
            color_blend::{
                AttachmentBlend,
                BlendFactor,
                BlendOp,
                ColorBlendAttachmentState,
                ColorBlendState,
                ColorComponents,
            },
            depth_stencil::{CompareOp, DepthState, DepthStencilState},
            input_assembly::{InputAssemblyState, PrimitiveTopology},
            viewport::ViewportState,
        },
        GraphicsPipeline,
        Pipeline,
        PipelineBindPoint,
        StateMode,
    },
    render_pass::Subpass,
    sampler::{Filter, MipmapMode, Sampler, SamplerAddressMode},
};

mod vertex_shader {
    vulkano_shaders::shader! {
        ty: "vertex",
        src: "
            #version 450
            #extension GL_EXT_debug_printf: enable
            layout(push_constant) uniform PushConstantData {
                uint width;
                uint height;
                float z_index;
                vec2 origin;
                vec2 size;
                ivec2 direction;
                vec3 coeffs;
                vec2 window_min;
                vec2 window_max;
            } params;

            layout(location = 0) out vec2 origin;
            layout(location = 1) out ivec2 direction;
            layout(location = 2) out vec3 coeffs;
            layout(location = 3) out vec2 window_min;
            layout(location = 4) out vec2 window_max;

            void main() {
                int idx = gl_VertexIndex;
                int top = idx & 1;
                int left = (idx & 2) / 2;

                origin = params.origin;
                coeffs = params.coeffs;
                direction = params.direction;
                window_min = params.window_min;
                window_max = params.window_max;

                vec2 pos = params.origin + vec2(top, left) * params.size;
                pos = (pos / (vec2(params.width, params.height) / 2.) - vec2(1.));
                gl_Position = vec4(pos, params.z_index, 1.0);
            }
        "
    }
}
mod fragment_shader {
    vulkano_shaders::shader! {
        ty: "fragment",
        src: "
            #version 450
            #extension GL_EXT_debug_printf: enable
            layout(location = 0) out vec4 f_color;
            layout(set = 0, binding = 0) uniform sampler2DMS depth;
            layout(set = 0, binding = 1) uniform sampler2D color;

            layout(location = 0) flat in vec2 origin;
            layout(location = 1) flat in ivec2 direction;
            layout(location = 2) flat in vec3 coeffs;
            layout(location = 3) flat in vec2 window_min;
            layout(location = 4) flat in vec2 window_max;

            void main() {
                ivec2 pos = ivec2(gl_FragCoord.xy - origin);

                if (pos.x < window_min.x || pos.y < window_min.y
                 || pos.x > window_max.x || pos.y > window_max.y) {
                    f_color = texelFetch(color, pos, 0);
                } else {
                    float sigma = (1. / coeffs.x) / sqrt(2. * 3.14159265358979361);
                    float sampleCount = ceil(1.5 * sigma);

                    vec3 g = coeffs;
                    ivec2 size = textureSize(color, 0) - 1;
                    vec4 color_sum = texelFetch(color, pos, 0) * g.x;
                    float coeff_norm = g.x;
                    for (int i = 1; i < sampleCount; i++) {
                        g.xy *= g.yz;
                        color_sum += texelFetch(color, min(pos + i * direction, size), 0) * g.x;
                        color_sum += texelFetch(color, max(pos - i * direction, ivec2(0)), 0) * g.x;
                        coeff_norm += 2.0 * g.x;
                    }
                    vec4 color = color_sum / coeff_norm;
                    f_color = vec4(color.rgb * color.w, color.w);
                }
            }
        "
    }
}

#[widget]
pub fn raw_blur(
    children: Fragment,
    window: Option<Fragment>,
    sigma: f32,
    in_x: bool,
    backdrop_after: Option<usize>,
    context: &mut WidgetContext,
) -> FragmentInner {
    let pipeline_and_sampler = context.effect(
        |context| {
            let render_pass = context.vulkan_context.render_pass.clone();
            let vs = vertex_shader::load(render_pass.device().clone()).unwrap();
            let fs = fragment_shader::load(render_pass.device().clone()).unwrap();
            let pipeline = GraphicsPipeline::start()
                .vertex_shader(vs.entry_point("main").unwrap(), ())
                .input_assembly_state(
                    InputAssemblyState::new().topology(PrimitiveTopology::TriangleList),
                )
                .viewport_state(ViewportState::viewport_dynamic_scissor_irrelevant())
                .fragment_shader(fs.entry_point("main").unwrap(), ())
                .color_blend_state(ColorBlendState {
                    logic_op: None,
                    attachments: vec![ColorBlendAttachmentState {
                        blend: Some(AttachmentBlend {
                            color_op: BlendOp::Add,
                            color_source: BlendFactor::SrcAlpha,
                            color_destination: BlendFactor::OneMinusSrcAlpha,
                            alpha_op: BlendOp::Max,
                            alpha_source: BlendFactor::One,
                            alpha_destination: BlendFactor::One,
                        }),
                        color_write_mask: ColorComponents::all(),
                        color_write_enable: StateMode::Fixed(true),
                    }],
                    blend_constants: StateMode::Fixed([1.0, 1.0, 1.0, 1.0]),
                })
                .depth_stencil_state(DepthStencilState {
                    depth: Some(DepthState {
                        compare_op: StateMode::Fixed(CompareOp::LessOrEqual),
                        ..Default::default()
                    }),
                    ..DepthStencilState::simple_depth_test()
                })
                .render_pass(Subpass::from(render_pass.clone(), 0).unwrap())
                .build(render_pass.device().clone())
                .unwrap();

            let sampler = Sampler::new(
                render_pass.device().clone(),
                Filter::Nearest,
                Filter::Nearest,
                MipmapMode::Nearest,
                SamplerAddressMode::ClampToEdge,
                SamplerAddressMode::ClampToEdge,
                SamplerAddressMode::ClampToEdge,
                0.0,
                1.0,
                0.0,
                0.0,
            )
            .unwrap();

            (pipeline, sampler)
        },
        (),
    );
    let pipeline_and_sampler = pipeline_and_sampler.read();
    let pipeline = pipeline_and_sampler.0.clone();
    let sampler = pipeline_and_sampler.1.clone();
    let queue = context
        .vulkan_context
        .queues
        .iter()
        .find(|&q| q.family().supports_graphics())
        .unwrap()
        .clone();

    fn generate_resolve(
        in_x: bool,
        sigma: f32,
        pipeline: Arc<GraphicsPipeline>,
        sampler: Arc<Sampler>,
        queue: Arc<Queue>,
        window_function: impl Fn(&CallbackContext, Rect, Rect) -> Rect + 'static,
    ) -> SubPassRenderFunction {
        std::rc::Rc::new(
            move |context,
                  color,
                  depth,
                  render_pass,
                  viewport,
                  dimensions,
                  abs_pos,
                  rect,
                  z_index| {
                let window = window_function(context, abs_pos, rect);

                let mut set_builder = PersistentDescriptorSet::start(
                    pipeline.layout().descriptor_set_layouts()[0].clone(),
                );
                set_builder
                    .add_sampled_image(depth, sampler.clone())
                    .unwrap()
                    .add_sampled_image(color, sampler.clone())
                    .unwrap();
                let descriptor_set = set_builder.build().unwrap();

                let coeff_a = 1.0 / ((2.0f32 * 3.151_592_3).sqrt() * sigma);
                let coeff_b = (-0.5 / (sigma * sigma)).exp();
                let coeff_c = coeff_b * coeff_b;

                let push_constants = vertex_shader::ty::PushConstantData {
                    width: dimensions[0],
                    height: dimensions[1],
                    z_index,
                    origin: rect.pos.into(),
                    size: rect.size.into(),
                    coeffs: [coeff_a, coeff_b, coeff_c],
                    direction: if in_x { [1, 0] } else { [0, 1] },
                    window_min: window.top_left_corner().into(),
                    window_max: window.bottom_right_corner().into(),
                    _dummy0: Default::default(),
                    _dummy1: Default::default(),
                    _dummy2: Default::default(),
                };

                let mut builder = AutoCommandBufferBuilder::secondary_graphics(
                    render_pass.device().clone(),
                    queue.family(),
                    CommandBufferUsage::MultipleSubmit,
                    pipeline.subpass().clone(),
                )
                .unwrap();
                builder
                    .bind_descriptor_sets(
                        PipelineBindPoint::Graphics,
                        pipeline.layout().clone(),
                        0,
                        descriptor_set,
                    )
                    .bind_pipeline_graphics(pipeline.clone())
                    .push_constants(pipeline.layout().clone(), 0, push_constants)
                    .set_viewport(0, std::iter::once(viewport))
                    .draw(4, 1, 0, 0)
                    .unwrap();

                builder.build().unwrap()
            },
        )
    }

    let resolve = generate_resolve(
        in_x,
        sigma,
        pipeline.clone(),
        sampler.clone(),
        queue.clone(),
        move |context, abs_pos, rect| match window {
            Some(frag) => {
                let window_rect = context.measure(frag).unwrap();
                window_rect.minus_position(abs_pos.pos)
            }
            None => Rect::from_corners(Vec2::zero(), rect.size),
        },
    );

    let finish =
        generate_resolve(in_x, sigma, pipeline, sampler, queue, move |_, _, _| Rect::zero());

    FragmentInner::Node {
        children: smallvec![children],
        layout: Box::new(Transparent),
        is_clipper: false,
        subpass: Some(SubPassSetup {
            resolve,
            finish: backdrop_after.map(|after| (finish, Some(after))),
        }),
    }
}

#[widget]
pub fn blur(
    children: Fragment,
    #[default] window: Option<Fragment>,
    sigma: f32,
    context: &mut WidgetContext,
) -> Fragment {
    rsx! {
        <raw_blur sigma=sigma in_x=true window=window backdrop_after=None>
            <raw_blur sigma=sigma in_x=false window=window backdrop_after=None>
                {children}
            </raw_blur>
        </raw_blur>
    }
}

#[widget]
pub fn backdrop_blur(children: Fragment, sigma: f32, context: &mut WidgetContext) -> Fragment {
    rsx! {
        <raw_blur sigma=sigma in_x=true window=None backdrop_after=None>
            <raw_blur sigma=sigma in_x=false window=None backdrop_after=Some(1)>
                {children}
            </raw_blur>
        </raw_blur>
    }
}