use super::Result;
use crate::Error;
use crate::renderer::EguiRenderer;
use egui::Vec2;
use egui_wgpu::wgpu::SurfaceError;
use egui_wgpu::{ScreenDescriptor, wgpu};
use std::sync::Arc;
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::ActiveEventLoop;
use winit::window::{Window, WindowAttributes, WindowId};
pub struct State {
pub device: wgpu::Device,
pub queue: wgpu::Queue,
pub surface_config: wgpu::SurfaceConfiguration,
pub surface: wgpu::Surface<'static>,
pub scale_factor: f32,
pub egui_renderer: EguiRenderer,
}
impl State {
async fn new(
instance: &wgpu::Instance,
surface: wgpu::Surface<'static>,
window: &winit::window::Window,
width: u32,
height: u32,
) -> Result<Self> {
let power_pref = wgpu::PowerPreference::default();
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: power_pref,
force_fallback_adapter: false,
compatible_surface: Some(&surface),
})
.await
.ok_or(Error::FailedToFindAdapter)?;
let features = wgpu::Features::empty();
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
required_features: features,
required_limits: Default::default(),
memory_hints: Default::default(),
},
None,
)
.await?;
let swapchain_capabilities = surface.get_capabilities(&adapter);
let selected_format = wgpu::TextureFormat::Bgra8UnormSrgb;
let swapchain_format = swapchain_capabilities
.formats
.iter()
.find(|d| **d == selected_format)
.ok_or(Error::FailedToSelectSurfaceTextureFormat)?;
let surface_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: *swapchain_format,
width,
height,
present_mode: wgpu::PresentMode::AutoVsync,
desired_maximum_frame_latency: 0,
alpha_mode: swapchain_capabilities.alpha_modes[0],
view_formats: vec![],
};
surface.configure(&device, &surface_config);
let egui_renderer = EguiRenderer::new(&device, surface_config.format, None, 1, window);
let scale_factor = 1.0;
Ok(Self {
device,
queue,
surface,
surface_config,
egui_renderer,
scale_factor,
})
}
fn resize_surface(&mut self, width: u32, height: u32) {
self.surface_config.width = width;
self.surface_config.height = height;
self.surface.configure(&self.device, &self.surface_config);
}
}
pub struct PopupWindow<F, R> {
instance: wgpu::Instance,
state: Option<State>,
window: Option<Arc<Window>>,
pub draw_result: Option<R>,
draw_fn: F,
min_sz: Option<Vec2>,
window_info: WindowAttributes,
pub error: Option<Error>,
}
impl<F, R> PopupWindow<F, R>
where
F: FnMut(&mut egui::Ui) -> Option<R>,
{
pub fn new(draw_fn: F, window_info: WindowAttributes) -> Self {
let instance = egui_wgpu::wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
Self {
instance,
state: None,
window: None,
draw_result: None,
draw_fn,
min_sz: None,
window_info,
error: None,
}
}
async fn set_window(&mut self, window: Window) {
assert!(self.error.is_none());
let window = Arc::new(window);
let initial_sz = window.inner_size();
let surface = match self.instance.create_surface(window.clone()) {
Ok(v) => v,
Err(e) => {
self.error.replace(e.into());
return;
}
};
let state = match State::new(
&self.instance,
surface,
&window,
initial_sz.width,
initial_sz.height,
)
.await
{
Ok(v) => v,
Err(e) => {
self.error.replace(e);
return;
}
};
self.window.get_or_insert(window);
self.state.get_or_insert(state);
}
fn handle_resized(&mut self, width: u32, height: u32) {
if width > 0 && height > 0 {
let min_sz = self.min_sz.unwrap_or(Vec2::ZERO);
let width = width.max(min_sz.x as u32);
let height = height.max(min_sz.y as u32);
self.state.as_mut().unwrap().resize_surface(width, height);
}
}
fn handle_redraw(&mut self) -> Option<R> {
assert!(self.error.is_none());
if let Some(window) = self.window.as_ref() {
if let Some(min) = window.is_minimized() {
if min {
tracing::debug!("redraw called while window is minimized, ignoring");
return None;
}
}
}
let state = self.state.as_mut().unwrap();
let screen_descriptor = ScreenDescriptor {
size_in_pixels: [state.surface_config.width, state.surface_config.height],
pixels_per_point: self.window.as_ref().unwrap().scale_factor() as f32
* state.scale_factor,
};
let surface_texture = state.surface.get_current_texture();
match surface_texture {
Err(SurfaceError::Outdated) => {
tracing::debug!("wgpu surface outdated");
return None;
}
Err(e) => {
self.error.replace(e.into());
return None;
}
Ok(_) => {}
};
let surface_texture = surface_texture.unwrap();
let surface_view = surface_texture
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = state
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
let window = self.window.as_ref().unwrap();
let mut out = None;
{
state.egui_renderer.begin_frame(window);
egui::CentralPanel::default().show(state.egui_renderer.context(), |ui| {
out = (self.draw_fn)(ui);
});
state.egui_renderer.end_frame_and_draw(
&state.device,
&state.queue,
&mut encoder,
window,
&surface_view,
screen_descriptor,
);
};
state.queue.submit(Some(encoder.finish()));
surface_texture.present();
out
}
fn close(&mut self, event_loop: &ActiveEventLoop) {
self.state.take();
assert!(
Arc::strong_count(self.window.as_ref().unwrap()) <= 1,
"we are not the last window reference?"
);
self.window.take();
event_loop.exit();
}
}
impl<F, R> ApplicationHandler for PopupWindow<F, R>
where
F: FnMut(&mut egui::Ui) -> Option<R>,
{
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.error.is_some() {
return;
}
let window = event_loop.create_window(self.window_info.clone()).unwrap();
pollster::block_on(self.set_window(window));
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _: WindowId, event: WindowEvent) {
if self.error.is_some() {
self.close(event_loop);
}
let Some(win) = self.window.as_ref() else {
return;
};
self.state
.as_mut()
.unwrap()
.egui_renderer
.handle_input(win, &event);
match event {
WindowEvent::CloseRequested => {
self.close(event_loop);
}
WindowEvent::RedrawRequested => {
self.draw_result = self.handle_redraw();
if self.draw_result.is_some() {
self.close(event_loop);
} else {
self.window.as_ref().unwrap().request_redraw();
}
}
WindowEvent::Resized(new_size) => {
self.handle_resized(new_size.width, new_size.height);
}
_ => (),
}
}
}