lemur/src/app/common.rs

258 lines
7.3 KiB
Rust

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(target_os = "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(target_os = "windows")]
let attributes = attributes.with_corner_preference(CornerPreference::DoNotRound);
Self {
event_loop,
attributes,
}
}
pub fn with_title<T: Into<String>>(self, title: T) -> Self {
Self {
attributes: self.attributes.with_title(title),
..self
}
}
pub fn with_inner_size<S: Into<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<wgpu::Queue>,
pub window: Arc<Window>,
pub surface_desc: wgpu::SurfaceConfiguration,
pub surface: wgpu::Surface<'static>,
pub hidpi_factor: f64,
}
impl WindowState {
fn new(event_loop: &ActiveEventLoop, attributes: WindowAttributes) -> Self {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::PRIMARY,
..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,
}
}
pub fn logical_size(&self) -> LogicalSize<u32> {
PhysicalSize::new(self.surface_desc.width, self.surface_desc.height)
.to_logical(self.hidpi_factor)
}
pub fn handle_resize(&mut self, size: &PhysicalSize<u32>) {
self.surface_desc.width = size.width;
self.surface_desc.height = size.height;
self.surface.configure(&self.device, &self.surface_desc);
}
}
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<MouseCursor>,
}
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<SuspendedContext>,
}
impl ContextGuard {
fn new() -> Self {
Self {
value: Some(SuspendedContext::create()),
}
}
pub fn lock(&mut self) -> Option<ContextLock<'_>> {
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<imgui::Context>,
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<WindowToken<'_>>;
fn right_align_text<T: AsRef<str>>(&self, text: T, space: f32);
}
impl UiExt for imgui::Ui {
fn fullscreen_window(&self) -> Option<WindowToken<'_>> {
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<T: AsRef<str>>(&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);
}
}