grafo 0.15.0

A GPU-accelerated rendering library for Rust
Documentation
// TODO: This is needed to avoid false positives generated by the repr(C) expansion in compiler 1.89
#![allow(unused)]
/// Example: Backdrop blur (frosted-glass) effect
///
/// Demonstrates the backdrop effect system: a semi-transparent panel blurs
/// whatever is rendered *behind* it, producing a frosted-glass look.
///
/// The scene has:
/// - Several colored shapes drawn first (the "background content")
/// - A semi-transparent panel on top, with a Gaussian blur backdrop effect
///
/// The blur reuses the same two-pass separable Gaussian blur shader from the
/// `gaussian_blur` example, but applied as a *backdrop* effect rather than a
/// *group* effect.
use futures::executor::block_on;
use grafo::{BorderRadii, Shape};
use grafo::{Color, Stroke};
use std::sync::Arc;
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::window::{Window, WindowId};

const BLUR_EFFECT: u64 = 1;

/// Parameters shared by both blur passes.
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
#[allow(dead_code)]
struct BlurParams {
    radius: f32,
    _pad: f32,
    tex_size: [f32; 2],
}

const HORIZONTAL_BLUR_WGSL: &str = r#"
const DIRECTION: vec2<f32> = vec2<f32>(1.0, 0.0);

struct Params {
    radius: f32,
    _pad: f32,
    tex_size: vec2<f32>,
}
@group(1) @binding(0) var<uniform> params: Params;

@fragment
fn effect_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
    let pixel = DIRECTION / params.tex_size;
    let sigma = max(params.radius / 3.0, 0.001);
    var color = vec4<f32>(0.0);
    var total_weight = 0.0;
    let r = i32(ceil(params.radius));
    for (var i = -r; i <= r; i++) {
        let offset = f32(i);
        let weight = exp(-(offset * offset) / (2.0 * sigma * sigma));
        color += textureSample(t_input, s_input, uv + pixel * offset) * weight;
        total_weight += weight;
    }
    return color / total_weight;
}
"#;

const VERTICAL_BLUR_WGSL: &str = r#"
const DIRECTION: vec2<f32> = vec2<f32>(0.0, 1.0);

struct Params {
    radius: f32,
    _pad: f32,
    tex_size: vec2<f32>,
}
@group(1) @binding(0) var<uniform> params: Params;

@fragment
fn effect_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
    let pixel = DIRECTION / params.tex_size;
    let sigma = max(params.radius / 3.0, 0.001);
    var color = vec4<f32>(0.0);
    var total_weight = 0.0;
    let r = i32(ceil(params.radius));
    for (var i = -r; i <= r; i++) {
        let offset = f32(i);
        let weight = exp(-(offset * offset) / (2.0 * sigma * sigma));
        color += textureSample(t_input, s_input, uv + pixel * offset) * weight;
        total_weight += weight;
    }
    return color / total_weight;
}
"#;

#[derive(Default)]
struct App<'a> {
    window: Option<Arc<Window>>,
    renderer: Option<grafo::Renderer<'a>>,
}

impl<'a> ApplicationHandler for App<'a> {
    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
        let window = Arc::new(
            event_loop
                .create_window(
                    Window::default_attributes()
                        .with_title("Grafo – Backdrop Blur (Frosted Glass)"),
                )
                .unwrap(),
        );

        let window_size = window.inner_size();
        let scale_factor = window.scale_factor();
        let physical_size = (window_size.width, window_size.height);

        let mut renderer = block_on(grafo::Renderer::new(
            window.clone(),
            physical_size,
            scale_factor,
            true,
            false,
            1,
        ));

        // Load the two-pass Gaussian blur effect
        renderer
            .load_effect(BLUR_EFFECT, &[HORIZONTAL_BLUR_WGSL, VERTICAL_BLUR_WGSL])
            .expect("Failed to compile blur effect");

        self.window = Some(window);
        self.renderer = Some(renderer);
    }

    fn window_event(
        &mut self,
        event_loop: &ActiveEventLoop,
        window_id: WindowId,
        event: WindowEvent,
    ) {
        let Some(window) = &self.window else { return };
        let Some(renderer) = &mut self.renderer else {
            return;
        };

        if window_id != window.id() {
            return;
        }

        match event {
            WindowEvent::CloseRequested => event_loop.exit(),
            WindowEvent::Resized(physical_size) => {
                let new_size = (physical_size.width, physical_size.height);
                renderer.resize(new_size);
                window.request_redraw();
            }
            WindowEvent::RedrawRequested => {
                let (pw, ph) = renderer.size();

                // ── Background content (shapes drawn behind the panel) ───
                let bg = Shape::rect(
                    [(20.0, 20.0), (780.0, 580.0)],
                    Stroke::new(2.0, Color::BLACK),
                );
                let bg_id = renderer.add_shape(bg, None, None);
                renderer.set_shape_color(bg_id, Some(Color::rgb(245, 245, 250)));

                // Colorful rectangles that will be visible through the panel
                let r1 = Shape::rect(
                    [(60.0, 80.0), (260.0, 260.0)],
                    Stroke::new(2.0, Color::rgb(0, 0, 0)),
                );
                let r1_id = renderer.add_shape(r1, Some(bg_id), None);
                renderer.set_shape_color(r1_id, Some(Color::rgb(220, 50, 50)));

                let r2 = Shape::rect(
                    [(180.0, 160.0), (420.0, 380.0)],
                    Stroke::new(2.0, Color::rgb(0, 0, 0)),
                );
                let r2_id = renderer.add_shape(r2, Some(bg_id), None);
                renderer.set_shape_color(r2_id, Some(Color::rgb(50, 160, 50)));

                let r3 = Shape::rect(
                    [(340.0, 100.0), (560.0, 300.0)],
                    Stroke::new(2.0, Color::rgb(0, 0, 0)),
                );
                let r3_id = renderer.add_shape(r3, Some(bg_id), None);
                renderer.set_shape_color(r3_id, Some(Color::rgb(50, 80, 220)));

                let r4 = Shape::rect(
                    [(100.0, 350.0), (700.0, 540.0)],
                    Stroke::new(2.0, Color::rgb(0, 0, 0)),
                );
                let r4_id = renderer.add_shape(r4, Some(bg_id), None);
                renderer.set_shape_color(r4_id, Some(Color::rgb(200, 180, 50)));

                // ── Frosted-glass panel with backdrop blur ───────────────
                // This shape is rendered on top; the backdrop effect blurs
                // everything already drawn behind it.
                let panel = Shape::rect(
                    [(120.0, 120.0), (520.0, 460.0)],
                    Stroke::new(2.0, Color::rgb(100, 100, 100)),
                );
                let panel_id = renderer.add_shape(panel, None, None);
                // Semi-transparent white so the blurred background shows through
                renderer.set_shape_color(panel_id, Some(Color::rgba(255, 255, 255, 100)));

                // To test that clipping works correctly with the blur
                let panel_content = Shape::rounded_rect(
                    [(240.0, 240.0), (600.0, 540.0)],
                    BorderRadii::new(100.0),
                    Stroke::new(1.0, Color::rgb(80, 80, 80)),
                );
                let content_id = renderer.add_shape(panel_content, Some(panel_id), None);
                renderer.set_shape_color(content_id, Some(Color::rgba(200, 220, 255, 120)));

                let blur_params = BlurParams {
                    radius: 12.0,
                    _pad: 0.0,
                    tex_size: [pw as f32, ph as f32],
                };
                renderer
                    .set_shape_backdrop_effect(
                        panel_id,
                        BLUR_EFFECT,
                        bytemuck::bytes_of(&blur_params),
                    )
                    .expect("Failed to set backdrop effect");

                // ── A second, smaller frosted panel for comparison ───────
                let panel2 = Shape::rect(
                    [(560.0, 200.0), (740.0, 400.0)],
                    Stroke::new(2.0, Color::rgb(80, 80, 80)),
                );
                let panel2_id = renderer.add_shape(panel2, None, None);
                renderer.set_shape_color(panel2_id, Some(Color::rgba(200, 220, 255, 120)));

                let blur_params2 = BlurParams {
                    radius: 20.0,
                    _pad: 0.0,
                    tex_size: [pw as f32, ph as f32],
                };
                renderer
                    .set_shape_backdrop_effect(
                        panel2_id,
                        BLUR_EFFECT,
                        bytemuck::bytes_of(&blur_params2),
                    )
                    .expect("Failed to set backdrop effect");

                // ── Render ───────────────────────────────────────────────
                match renderer.render() {
                    Ok(_) => {
                        renderer.clear_draw_queue();
                    }
                    Err(wgpu::SurfaceError::Lost) => renderer.resize(renderer.size()),
                    Err(wgpu::SurfaceError::OutOfMemory) => event_loop.exit(),
                    Err(e) => eprintln!("{e:?}"),
                }
            }
            _ => {}
        }
    }
}

pub fn main() {
    env_logger::init();
    let event_loop = EventLoop::new().expect("To create the event loop");

    let mut app = App::default();
    let _ = event_loop.run_app(&mut app);
}