use std::{ ops::{Deref, DerefMut}, sync::Arc, time::Instant, }; use imgui::{FontSource, MouseCursor, SuspendedContext, WindowToken}; use imgui_wgpu::{Renderer, RendererConfig}; use imgui_winit_support::WinitPlatform; use pollster::block_on; #[cfg(windows)] use winit::platform::windows::{CornerPreference, WindowAttributesExtWindows as _}; use winit::{ dpi::{LogicalSize, PhysicalSize, Size}, event_loop::ActiveEventLoop, window::{Window, WindowAttributes}, }; pub struct WindowStateBuilder<'a> { event_loop: &'a ActiveEventLoop, attributes: WindowAttributes, } impl<'a> WindowStateBuilder<'a> { pub fn new(event_loop: &'a ActiveEventLoop) -> Self { let attributes = Window::default_attributes(); #[cfg(windows)] let attributes = attributes.with_corner_preference(CornerPreference::DoNotRound); Self { event_loop, attributes, } } pub fn with_title>(self, title: T) -> Self { Self { attributes: self.attributes.with_title(title), ..self } } pub fn with_inner_size>(self, size: S) -> Self { Self { attributes: self.attributes.with_inner_size(size), ..self } } pub fn build(self) -> WindowState { WindowState::new(self.event_loop, self.attributes) } } #[derive(Debug)] pub struct WindowState { pub device: wgpu::Device, pub queue: Arc, pub window: Arc, pub surface_desc: wgpu::SurfaceConfiguration, pub surface: wgpu::Surface<'static>, pub hidpi_factor: f64, pub minimized: bool, } impl WindowState { fn new(event_loop: &ActiveEventLoop, attributes: WindowAttributes) -> Self { let backends = wgpu::Backends::PRIMARY; #[cfg(windows)] let backends = if winver::WindowsVersion::detect().is_some_and(|v| v.major >= 10) { // DX12 is faster than GL, so don't use GL on windows versions which support DX12. // When wgpu switches to default to DX12, this can be removed. backends - wgpu::Backends::GL } else { backends }; let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends, ..Default::default() }); let window = Arc::new(event_loop.create_window(attributes).unwrap()); let size = window.inner_size(); let hidpi_factor = window.scale_factor(); let surface = instance.create_surface(window.clone()).unwrap(); let adapter = block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::HighPerformance, compatible_surface: Some(&surface), force_fallback_adapter: false, })) .unwrap(); let (device, queue) = block_on(adapter.request_device(&wgpu::DeviceDescriptor::default(), None)).unwrap(); let queue = Arc::new(queue); // Set up swap chain let surface_desc = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: wgpu::TextureFormat::Bgra8UnormSrgb, width: size.width, height: size.height, present_mode: wgpu::PresentMode::Fifo, desired_maximum_frame_latency: 2, alpha_mode: wgpu::CompositeAlphaMode::Auto, view_formats: vec![wgpu::TextureFormat::Bgra8Unorm], }; surface.configure(&device, &surface_desc); Self { device, queue, window, surface_desc, surface, hidpi_factor, minimized: false, } } pub fn logical_size(&self) -> LogicalSize { PhysicalSize::new(self.surface_desc.width, self.surface_desc.height) .to_logical(self.hidpi_factor) } pub fn handle_resize(&mut self, size: &PhysicalSize) { if size.width > 0 && size.height > 0 { self.minimized = false; self.surface_desc.width = size.width; self.surface_desc.height = size.height; self.surface.configure(&self.device, &self.surface_desc); } else { self.minimized = true; } } } pub struct ImguiState { pub context: ContextGuard, pub platform: WinitPlatform, pub renderer: Renderer, pub clear_color: wgpu::Color, pub last_frame: Instant, pub last_cursor: Option, } impl ImguiState { pub fn new(window: &WindowState) -> Self { let mut context_guard = ContextGuard::new(); let mut context = context_guard.lock().unwrap(); let mut platform = imgui_winit_support::WinitPlatform::new(&mut context); platform.attach_window( context.io_mut(), &window.window, imgui_winit_support::HiDpiMode::Default, ); context.set_ini_filename(None); let font_size = (16.0 * window.hidpi_factor) as f32; context.io_mut().font_global_scale = (1.0 / window.hidpi_factor) as f32; context.fonts().add_font(&[FontSource::TtfData { data: include_bytes!("../../assets/selawk.ttf"), size_pixels: font_size, config: Some(imgui::FontConfig { oversample_h: 1, pixel_snap_h: true, size_pixels: font_size, ..Default::default() }), }]); let style = context.style_mut(); style.use_light_colors(); // // Set up dear imgui wgpu renderer // let renderer_config = RendererConfig { texture_format: window.surface_desc.format, ..Default::default() }; let renderer = Renderer::new(&mut context, &window.device, &window.queue, renderer_config); let last_frame = Instant::now(); let last_cursor = None; drop(context); Self { context: context_guard, platform, renderer, clear_color: wgpu::Color::BLACK, last_frame, last_cursor, } } } pub struct ContextGuard { value: Option, } impl ContextGuard { fn new() -> Self { Self { value: Some(SuspendedContext::create()), } } pub fn lock(&mut self) -> Option> { let sus = self.value.take()?; match sus.activate() { Ok(ctx) => Some(ContextLock { ctx: Some(ctx), holder: self, }), Err(sus) => { self.value = Some(sus); None } } } } pub struct ContextLock<'a> { ctx: Option, holder: &'a mut ContextGuard, } impl<'a> Deref for ContextLock<'a> { type Target = imgui::Context; fn deref(&self) -> &Self::Target { self.ctx.as_ref().unwrap() } } impl<'a> DerefMut for ContextLock<'a> { fn deref_mut(&mut self) -> &mut Self::Target { self.ctx.as_mut().unwrap() } } impl<'a> Drop for ContextLock<'a> { fn drop(&mut self) { self.holder.value = self.ctx.take().map(|c| c.suspend()) } } pub trait UiExt { fn fullscreen_window(&self) -> Option>; fn right_align_text>(&self, text: T, space: f32); } impl UiExt for imgui::Ui { fn fullscreen_window(&self) -> Option> { self.window("fullscreen") .position([0.0, 0.0], imgui::Condition::Always) .size(self.io().display_size, imgui::Condition::Always) .flags(imgui::WindowFlags::NO_DECORATION) .begin() } fn right_align_text>(&self, text: T, space: f32) { let width = self.calc_text_size(text.as_ref())[0]; let [left, y] = self.cursor_pos(); let right = left + space; self.set_cursor_pos([right - width, y]); self.text(text); } }