use glium::glutin; use glium::glutin::event::{Event, WindowEvent}; use glium::glutin::event_loop::{ControlFlow, EventLoop}; use glium::glutin::window::WindowBuilder; use glium::{Display, Surface}; use imgui::{Context, FontConfig, FontGlyphRanges, FontSource, Ui, ViewportFlags}; use imgui_glium_renderer::Renderer; use imgui_winit_support::{HiDpiMode, WinitPlatform}; use std::collections::HashMap; use std::path::Path; use std::time::Instant; mod clipboard; pub struct System { pub event_loop: EventLoop<()>, pub display: glium::Display, pub imgui: Context, pub platform: WinitPlatform, pub renderer: Renderer, pub font_size: f32, } pub fn init(title: &str) -> System { let title = match Path::new(&title).file_name() { Some(file_name) => file_name.to_str().unwrap(), None => title, }; let event_loop = EventLoop::new(); let context = glutin::ContextBuilder::new().with_vsync(true); let builder = WindowBuilder::new() .with_title(title.to_owned()) .with_inner_size(glutin::dpi::LogicalSize::new(1024f64, 768f64)); let display = Display::new(builder, context, &event_loop).expect("Failed to initialize display"); let mut imgui = Context::create(); imgui.set_ini_filename(None); if let Some(backend) = clipboard::init() { imgui.set_clipboard_backend(backend); } else { eprintln!("Failed to initialize clipboard"); } let mut platform = WinitPlatform::init(&mut imgui); WinitPlatform::init_viewports(&mut imgui, display.gl_window().window(), &event_loop); { let gl_window = display.gl_window(); let window = gl_window.window(); let dpi_mode = if let Ok(factor) = std::env::var("IMGUI_EXAMPLE_FORCE_DPI_FACTOR") { // Allow forcing of HiDPI factor for debugging purposes match factor.parse::() { Ok(f) => HiDpiMode::Locked(f), Err(e) => panic!("Invalid scaling factor: {}", e), } } else { HiDpiMode::Default }; platform.attach_window(imgui.io_mut(), window, dpi_mode); } // Fixed font size. Note imgui_winit_support uses "logical // pixels", which are physical pixels scaled by the devices // scaling factor. Meaning, 13.0 pixels should look the same size // on two different screens, and thus we do not need to scale this // value (as the scaling is handled by winit) let font_size = 13.0; imgui.fonts().add_font(&[ FontSource::TtfData { data: include_bytes!("../../../resources/Roboto-Regular.ttf"), size_pixels: font_size, config: Some(FontConfig { // As imgui-glium-renderer isn't gamma-correct with // it's font rendering, we apply an arbitrary // multiplier to make the font a bit "heavier". With // default imgui-glow-renderer this is unnecessary. rasterizer_multiply: 1.5, // Oversampling font helps improve text rendering at // expense of larger font atlas texture. oversample_h: 4, oversample_v: 4, ..FontConfig::default() }), }, FontSource::TtfData { data: include_bytes!("../../../resources/mplus-1p-regular.ttf"), size_pixels: font_size, config: Some(FontConfig { // Oversampling font helps improve text rendering at // expense of larger font atlas texture. oversample_h: 4, oversample_v: 4, // Range of glyphs to rasterize glyph_ranges: FontGlyphRanges::japanese(), ..FontConfig::default() }), }, ]); let renderer = Renderer::init(&mut imgui, &display).expect("Failed to initialize renderer"); System { event_loop, display, imgui, platform, renderer, font_size, } } struct ViewportStorage<'a, T: 'static> { event_loop: &'a glium::glutin::event_loop::EventLoopWindowTarget, viewports: &'a mut HashMap, } impl<'a, T> imgui_winit_support::WinitPlatformViewportStorage for ViewportStorage<'a, T> { fn create_window(&mut self, id: imgui::Id, flags: imgui::ViewportFlags) { let builder = WindowBuilder::new() .with_always_on_top(flags.contains(ViewportFlags::TOP_MOST)) // .with_decorations(!flags.contains(ViewportFlags::NO_DECORATION)) .with_resizable(true) .with_visible(false); let window = builder.build(self.event_loop).unwrap(); self.viewports.insert(id, window); } fn remove_windows(&mut self, filter: impl Fn(imgui::Id) -> bool) { self.viewports.retain(|id, _| !filter(*id)); } fn get_window( &mut self, id: glium::glutin::window::WindowId, ) -> Option<(imgui::Id, &glium::glutin::window::Window)> { let res = self .viewports .iter() .find(|(_, wnd)| wnd.id() == id) .map(|(id, wnd)| (*id, wnd)); res } fn for_each(&mut self, mut func: impl FnMut(imgui::Id, &glium::glutin::window::Window)) { for (id, wnd) in self.viewports.iter() { func(*id, wnd); } } } impl System { pub fn main_loop(self, mut run_ui: F) { let System { event_loop, display, mut imgui, mut platform, mut renderer, .. } = self; let mut last_frame = Instant::now(); let mut viewports = HashMap::new(); event_loop.run(move |event, window_target, control_flow| match event { Event::NewEvents(_) => { let now = Instant::now(); imgui.io_mut().update_delta_time(now - last_frame); last_frame = now; } Event::MainEventsCleared => { let gl_window = display.gl_window(); platform .prepare_frame(imgui.io_mut(), gl_window.window()) .expect("Failed to prepare frame"); gl_window.window().request_redraw(); let mut storage = ViewportStorage { event_loop: window_target, viewports: &mut viewports, }; platform.update_viewports(&mut imgui, &mut storage); } Event::RedrawRequested(_) => { let ui = imgui.frame(); let mut run = true; run_ui(&mut run, ui); if !run { *control_flow = ControlFlow::Exit; } let gl_window = display.gl_window(); let mut target = display.draw(); target.clear_color_srgb(1.0, 1.0, 1.0, 1.0); platform.prepare_render(ui, gl_window.window()); let draw_data = imgui.render(); renderer .render(&mut target, draw_data) .expect("Rendering failed"); target.finish().expect("Failed to swap buffers"); imgui.update_platform_windows(); } Event::WindowEvent { event: WindowEvent::CloseRequested, window_id, .. } if window_id == display.gl_window().window().id() => { *control_flow = ControlFlow::Exit } event => { let gl_window = display.gl_window(); platform.handle_event(imgui.io_mut(), gl_window.window(), &event); let mut storage = ViewportStorage { event_loop: window_target, viewports: &mut viewports, }; platform.handle_viewport_event( &mut imgui, gl_window.window(), &mut storage, &event, ); } }) } }