debugui 0.1.0

Add a gui window to your program to view and manipulate values at runtime
Documentation
use std::borrow::Borrow;

use egui_winit::winit::{
    self,
    dpi::PhysicalSize,
    event::{Event, WindowEvent},
    event_loop::ControlFlow,
    window::Window,
};
use parking_lot::Once;

#[macro_export]
macro_rules! init_on {
    ($resources:ident, $loop:expr, $instance:expr, $adapter:expr, $device:expr, $queue:expr) => {
        let mut $resources =
            $crate::enabled::ui::UiResources::new($loop, $instance, $adapter, $device, $queue);
    };
}

#[macro_export]
macro_rules! feed_on {
    ($resources:ident, $event:expr, $control_flow:expr) => {
        $resources.handle_event($event, $control_flow);
    };
}

static UI_LAUNCHED: Once = Once::new();

#[cfg(not(any(target_os = "windows", target_os = "linux")))]
pub fn try_launch_own_thread() {
    UI_LAUNCHED.call_once(|| {
        log::info!("This platform does not support creating an event loop on a separate thread")
    })
}

#[cfg(any(target_os = "windows", target_os = "linux"))]
pub fn try_launch_own_thread() {
    UI_LAUNCHED.call_once(|| {
        std::thread::spawn(ui_thread);
    })
}

pub struct UiResources<W, D, Q> {
    window: W,
    device: D,
    queue: Q,
    state: egui_winit::State,
    renderer: egui_wgpu::Renderer,
    size: PhysicalSize<u32>,
    ctx: egui::Context,
    surface: wgpu::Surface,
    surface_config: wgpu::SurfaceConfiguration,
}

impl<D, Q> UiResources<Window, D, Q>
where
    D: Borrow<wgpu::Device>,
    Q: Borrow<wgpu::Queue>,
{
    pub fn new<T>(
        event_loop: &mut winit::event_loop::EventLoop<T>,
        instance: &wgpu::Instance,
        adapter: &wgpu::Adapter,
        device: D,
        queue: Q,
    ) -> Self {
        // Trigger the once since we're creating the window on the main thread
        UI_LAUNCHED.call_once(|| {});
        let window = winit::window::WindowBuilder::new()
            .with_title("debugui")
            .build(&event_loop)
            .unwrap();

        let surface = unsafe { instance.create_surface(&window) }.unwrap();

        let swapchain_capabilities = surface.get_capabilities(&adapter);
        let swapchain_format = swapchain_capabilities.formats[0];

        let size = window.inner_size();

        let surface_config = wgpu::SurfaceConfiguration {
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
            format: swapchain_format,
            width: size.width,
            height: size.height,
            present_mode: wgpu::PresentMode::Fifo,
            alpha_mode: swapchain_capabilities.alpha_modes[0],
            view_formats: vec![],
        };

        surface.configure(device.borrow(), &surface_config);

        let ctx = egui::Context::default();

        let renderer = egui_wgpu::Renderer::new(device.borrow(), swapchain_format, None, 1);
        let state = egui_winit::State::new(&window);

        Self {
            window,
            device,
            queue,
            state,
            renderer,
            size,
            ctx,
            surface,
            surface_config,
        }
    }
}
impl<W, D, Q> UiResources<W, D, Q>
where
    W: Borrow<Window>,
    D: Borrow<wgpu::Device>,
    Q: Borrow<wgpu::Queue>,
{
    pub fn handle_event<T>(
        &mut self,
        event: &winit::event::Event<'_, T>,
        control_flow: &mut ControlFlow,
    ) -> bool {
        let mut consumed_event = false;
        let device = self.device.borrow();
        let queue = self.queue.borrow();
        let window = self.window.borrow();
        let window_id = window.id();
        if let Event::WindowEvent {
            ref event,
            window_id: id,
        } = event
        {
            if window_id == *id {
                let r = self.state.on_event(&self.ctx, &event);
                consumed_event = r.consumed
            }
        }
        if let Event::RedrawRequested(id) = event {
            if *id != window_id {
                return false;
            }
        }
        match event {
            Event::WindowEvent {
                event: WindowEvent::Resized(new_size),
                window_id,
            } if *window_id == window.id() => {
                self.size = *new_size;
                self.surface_config.width = new_size.width;
                self.surface_config.height = new_size.height;
                self.surface.configure(device, &self.surface_config);
                return true;
            }
            Event::MainEventsCleared | Event::RedrawRequested(_) => {
                self.ctx.begin_frame(self.state.take_egui_input(window));

                draw_frame(&mut self.ctx);

                let egui_output = self.ctx.end_frame();

                let tris = self.ctx.tessellate(egui_output.shapes);

                for (tex_id, delta) in egui_output.textures_delta.set {
                    self.renderer
                        .update_texture(&device, &queue, tex_id, &delta);
                }
                match self.surface.get_current_texture() {
                    Ok(frame) => {
                        let view = frame
                            .texture
                            .create_view(&wgpu::TextureViewDescriptor::default());

                        let mut encoder = device.create_command_encoder(&Default::default());

                        {
                            self.renderer.update_buffers(
                                &device,
                                &queue,
                                &mut encoder,
                                &tris,
                                &egui_wgpu::renderer::ScreenDescriptor {
                                    size_in_pixels: [self.size.width, self.size.height],
                                    pixels_per_point: 1.0,
                                },
                            );

                            let mut egui_pass =
                                encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                                    label: None,
                                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                                        view: &view,
                                        resolve_target: None,
                                        ops: wgpu::Operations::default(),
                                    })],
                                    ..Default::default()
                                });

                            self.renderer.render(
                                &mut egui_pass,
                                &tris,
                                &egui_wgpu::renderer::ScreenDescriptor {
                                    size_in_pixels: [self.size.width, self.size.height],
                                    pixels_per_point: 1.0,
                                },
                            );
                        }
                        queue.submit(Some(encoder.finish()));

                        frame.present();
                    }
                    Err(e) => {
                        println!("Failed to acquire next swap chain texture {}", e);
                    }
                }
                for tex_id in egui_output.textures_delta.free {
                    self.renderer.free_texture(&tex_id);
                }
            }
            Event::WindowEvent {
                event: WindowEvent::CloseRequested,
                ..
            } => {
                *control_flow = ControlFlow::Exit;
            }
            _ => {}
        };
        consumed_event
    }
}

#[cfg(any(target_os = "windows", target_os = "linux"))]
pub fn ui_thread() {
    #[cfg(target_os = "windows")]
    use egui_winit::winit::platform::windows::EventLoopBuilderExtWindows;
    #[cfg(target_os = "linux")]
    use egui_winit::winit::platform::x11::EventLoopBuilderExtX11;

    let mut event_loop = winit::event_loop::EventLoopBuilder::new()
        .with_any_thread(true)
        .build();

    let instance = wgpu::Instance::default();

    let (adapter, device, queue) = setup_wgpu(&instance, None);

    let mut ui = UiResources::new(&mut event_loop, &instance, &adapter, device, queue);

    event_loop.run(move |event, _, control_flow| {
        ui.handle_event(&event, control_flow);
    });
}

pub fn setup_wgpu(
    instance: &wgpu::Instance,
    surface: Option<&wgpu::Surface>,
) -> (wgpu::Adapter, wgpu::Device, wgpu::Queue) {
    pollster::block_on(async {
        let adapter = instance
            .request_adapter(&wgpu::RequestAdapterOptions {
                power_preference: wgpu::PowerPreference::default(),
                force_fallback_adapter: false,
                compatible_surface: surface,
            })
            .await
            .expect("Failed to find an appropriate adapter");

        let (device, queue) = adapter
            .request_device(
                &wgpu::DeviceDescriptor {
                    label: None,
                    limits: wgpu::Limits::default().using_resolution(adapter.limits()),
                    ..Default::default()
                },
                None,
            )
            .await
            .expect("Failed to create device");
        (adapter, device, queue)
    })
}

use crate::PARAMS;

fn draw_frame(ctx: &mut egui::Context) {
    egui::CentralPanel::default().show(&ctx, |ui| {
        for param in PARAMS {
            egui::CollapsingHeader::new(param.name).show(ui, |ui| {
                if let Some((any, viewer)) = param.inner.get() {
                    if viewer.is_for(&**any) {
                        viewer.draw(&**any, ui);
                    } else {
                        ui.label("Viewer doesn't support this type - this is likely a bug");
                    }
                } else {
                    ui.label("Param not initalized yet");
                }
            });
        }
    });
}
trait DrawParam: Sized {
    fn draw(&self, name: &'static str, ui: &mut egui::Ui);
}