1use dear_imgui_rs as imgui;
22use dear_imgui_rs::{ConfigFlags, DockFlags, Id, WindowFlags};
23use dear_imgui_wgpu as imgui_wgpu;
24use dear_imgui_winit as imgui_winit;
25use pollster::block_on;
26use std::marker::PhantomData;
27use std::path::PathBuf;
28use std::sync::Arc;
29use std::time::{Duration, Instant};
30use thiserror::Error;
31use tracing::{error, info};
32use wgpu::SurfaceError;
33use winit::application::ApplicationHandler;
34use winit::dpi::LogicalSize;
35use winit::event::WindowEvent;
36use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
37use winit::window::{Window, WindowId};
38
39#[cfg(feature = "imnodes")]
40use dear_imnodes as imnodes;
41#[cfg(feature = "implot")]
42use dear_implot as implot;
43#[cfg(feature = "implot3d")]
44use dear_implot3d as implot3d;
45
46#[derive(Debug, Error)]
47pub enum DearAppError {
48 #[error("WGPU surface lost")]
49 SurfaceLost,
50 #[error("WGPU surface outdated")]
51 SurfaceOutdated,
52 #[error("WGPU surface timeout")]
53 SurfaceTimeout,
54 #[error("WGPU error: {0}")]
55 Wgpu(#[from] wgpu::SurfaceError),
56 #[error("Window creation error: {0}")]
57 WindowCreation(#[from] winit::error::EventLoopError),
58 #[error("Generic error: {0}")]
59 Generic(String),
60}
61
62#[derive(Default, Clone, Copy)]
64pub struct AddOnsConfig {
65 pub with_implot: bool,
66 pub with_imnodes: bool,
67 pub with_implot3d: bool,
68}
69
70impl AddOnsConfig {
71 pub fn auto() -> Self {
75 Self {
76 with_implot: cfg!(feature = "implot"),
77 with_imnodes: cfg!(feature = "imnodes"),
78 with_implot3d: cfg!(feature = "implot3d"),
79 }
80 }
81}
82
83pub struct AddOns<'a> {
85 #[cfg(feature = "implot")]
86 pub implot: Option<&'a implot::PlotContext>,
87 #[cfg(not(feature = "implot"))]
88 pub implot: Option<()>,
89
90 #[cfg(feature = "imnodes")]
91 pub imnodes: Option<&'a imnodes::Context>,
92 #[cfg(not(feature = "imnodes"))]
93 pub imnodes: Option<()>,
94
95 #[cfg(feature = "implot3d")]
96 pub implot3d: Option<&'a implot3d::Plot3DContext>,
97 #[cfg(not(feature = "implot3d"))]
98 pub implot3d: Option<()>,
99 pub docking: DockingApi<'a>,
100 pub gpu: GpuApi<'a>,
101 _marker: PhantomData<&'a ()>,
102}
103
104pub struct RunnerConfig {
106 pub window_title: String,
107 pub window_size: (f64, f64),
108 pub present_mode: wgpu::PresentMode,
109 pub clear_color: [f32; 4],
110 pub docking: DockingConfig,
111 pub ini_filename: Option<PathBuf>,
112 pub restore_previous_geometry: bool,
113 pub redraw: RedrawMode,
114 pub io_config_flags: Option<ConfigFlags>,
117 pub theme: Option<Theme>,
119}
120
121impl Default for RunnerConfig {
122 fn default() -> Self {
123 Self {
124 window_title: format!("Dear ImGui App - {}", env!("CARGO_PKG_VERSION")),
125 window_size: (1280.0, 720.0),
126 present_mode: wgpu::PresentMode::Fifo,
127 clear_color: [0.1, 0.2, 0.3, 1.0],
128 docking: DockingConfig::default(),
129 ini_filename: None,
130 restore_previous_geometry: true,
131 redraw: RedrawMode::Poll,
132 io_config_flags: None,
133 theme: None,
134 }
135 }
136}
137
138pub struct DockingConfig {
140 pub enable: bool,
142 pub auto_dockspace: bool,
144 pub dockspace_flags: DockFlags,
146 pub host_window_flags: WindowFlags,
148 pub host_window_name: &'static str,
150}
151
152impl Default for DockingConfig {
153 fn default() -> Self {
154 Self {
155 enable: true,
156 auto_dockspace: true,
157 dockspace_flags: DockFlags::PASSTHRU_CENTRAL_NODE,
158 host_window_flags: WindowFlags::NO_TITLE_BAR
159 | WindowFlags::NO_RESIZE
160 | WindowFlags::NO_MOVE
161 | WindowFlags::NO_COLLAPSE
162 | WindowFlags::NO_BRING_TO_FRONT_ON_FOCUS
163 | WindowFlags::NO_NAV_FOCUS,
164 host_window_name: "DockSpaceHost",
165 }
166 }
167}
168
169#[derive(Clone, Copy, Debug)]
171pub enum RedrawMode {
172 Poll,
174 Wait,
176 WaitUntil { fps: f32 },
178}
179
180#[derive(Clone, Copy, Debug)]
182pub enum Theme {
183 Dark,
184 Light,
185 Classic,
186}
187
188fn apply_theme(_ctx: &mut imgui::Context, theme: Theme) {
189 unsafe {
191 match theme {
192 Theme::Dark => dear_imgui_rs::sys::igStyleColorsDark(std::ptr::null_mut()),
193 Theme::Light => dear_imgui_rs::sys::igStyleColorsLight(std::ptr::null_mut()),
194 Theme::Classic => dear_imgui_rs::sys::igStyleColorsClassic(std::ptr::null_mut()),
195 }
196 }
197}
198
199pub struct RunnerCallbacks {
201 pub on_setup: Option<Box<dyn FnMut(&mut imgui::Context)>>,
202 pub on_style: Option<Box<dyn FnMut(&mut imgui::Context)>>,
203 pub on_fonts: Option<Box<dyn FnMut(&mut imgui::Context)>>,
204 pub on_post_init: Option<Box<dyn FnMut(&mut imgui::Context)>>,
205 pub on_event:
206 Option<Box<dyn FnMut(&winit::event::Event<()>, &Arc<Window>, &mut imgui::Context)>>,
207 pub on_exit: Option<Box<dyn FnMut(&mut imgui::Context)>>,
208}
209
210impl Default for RunnerCallbacks {
211 fn default() -> Self {
212 Self {
213 on_setup: None,
214 on_style: None,
215 on_fonts: None,
216 on_post_init: None,
217 on_event: None,
218 on_exit: None,
219 }
220 }
221}
222
223pub struct AppBuilder {
225 cfg: RunnerConfig,
226 addons: AddOnsConfig,
227 cbs: RunnerCallbacks,
228 on_frame: Option<Box<dyn FnMut(&imgui::Ui, &mut AddOns) + 'static>>,
229}
230
231impl AppBuilder {
232 pub fn new() -> Self {
233 Self {
234 cfg: RunnerConfig::default(),
235 addons: AddOnsConfig::default(),
236 cbs: RunnerCallbacks::default(),
237 on_frame: None,
238 }
239 }
240 pub fn with_config(mut self, cfg: RunnerConfig) -> Self {
241 self.cfg = cfg;
242 self
243 }
244 pub fn with_addons(mut self, addons: AddOnsConfig) -> Self {
245 self.addons = addons;
246 self
247 }
248 pub fn with_theme(mut self, theme: Theme) -> Self {
249 self.cfg.theme = Some(theme);
250 self
251 }
252 pub fn on_setup<F: FnMut(&mut imgui::Context) + 'static>(mut self, f: F) -> Self {
253 self.cbs.on_setup = Some(Box::new(f));
254 self
255 }
256 pub fn on_style<F: FnMut(&mut imgui::Context) + 'static>(mut self, f: F) -> Self {
257 self.cbs.on_style = Some(Box::new(f));
258 self
259 }
260 pub fn on_fonts<F: FnMut(&mut imgui::Context) + 'static>(mut self, f: F) -> Self {
261 self.cbs.on_fonts = Some(Box::new(f));
262 self
263 }
264 pub fn on_post_init<F: FnMut(&mut imgui::Context) + 'static>(mut self, f: F) -> Self {
265 self.cbs.on_post_init = Some(Box::new(f));
266 self
267 }
268 pub fn on_event<
269 F: FnMut(&winit::event::Event<()>, &Arc<Window>, &mut imgui::Context) + 'static,
270 >(
271 mut self,
272 f: F,
273 ) -> Self {
274 self.cbs.on_event = Some(Box::new(f));
275 self
276 }
277 pub fn on_frame<F: FnMut(&imgui::Ui, &mut AddOns) + 'static>(mut self, f: F) -> Self {
278 self.on_frame = Some(Box::new(f));
279 self
280 }
281 pub fn on_exit<F: FnMut(&mut imgui::Context) + 'static>(mut self, f: F) -> Self {
282 self.cbs.on_exit = Some(Box::new(f));
283 self
284 }
285 pub fn run(mut self) -> Result<(), DearAppError> {
286 let frame_fn = self
287 .on_frame
288 .take()
289 .ok_or_else(|| DearAppError::Generic("on_frame not set in AppBuilder".into()))?;
290 run_with_callbacks(self.cfg, self.addons, self.cbs, frame_fn)
291 }
292}
293
294pub fn run_simple<F>(mut gui: F) -> Result<(), DearAppError>
300where
301 F: FnMut(&imgui::Ui) + 'static,
302{
303 run(
304 RunnerConfig::default(),
305 AddOnsConfig::default(),
306 move |ui, _addons| gui(ui),
307 )
308}
309
310pub fn run<F>(
314 runner: RunnerConfig,
315 addons_cfg: AddOnsConfig,
316 mut gui: F,
317) -> Result<(), DearAppError>
318where
319 F: FnMut(&imgui::Ui, &mut AddOns) + 'static,
320{
321 run_with_callbacks(runner, addons_cfg, RunnerCallbacks::default(), gui)
322}
323
324pub fn run_with_callbacks<F>(
326 runner: RunnerConfig,
327 addons_cfg: AddOnsConfig,
328 cbs: RunnerCallbacks,
329 gui: F,
330) -> Result<(), DearAppError>
331where
332 F: FnMut(&imgui::Ui, &mut AddOns) + 'static,
333{
334 let event_loop = EventLoop::new()?;
335 match runner.redraw {
336 RedrawMode::Poll => event_loop.set_control_flow(ControlFlow::Poll),
337 RedrawMode::Wait => event_loop.set_control_flow(ControlFlow::Wait),
338 RedrawMode::WaitUntil { .. } => event_loop.set_control_flow(ControlFlow::WaitUntil(
339 Instant::now() + Duration::from_millis(16),
340 )),
341 }
342
343 let mut app = App::new(runner, addons_cfg, cbs, gui);
344 info!("Starting Dear App event loop");
345 event_loop.run_app(&mut app)?;
346 Ok(())
347}
348
349struct ImguiState {
350 context: imgui::Context,
351 platform: imgui_winit::WinitPlatform,
352 renderer: imgui_wgpu::WgpuRenderer,
353 last_frame: Instant,
354}
355
356pub struct DockingController {
358 flags: DockFlags,
359}
360
361pub struct DockingApi<'a> {
362 ctrl: &'a mut DockingController,
363}
364
365impl<'a> DockingApi<'a> {
366 pub fn flags(&self) -> DockFlags {
367 DockFlags::from_bits_retain(self.ctrl.flags.bits())
368 }
369 pub fn set_flags(&mut self, flags: DockFlags) {
370 self.ctrl.flags = flags;
371 }
372}
373
374pub struct GpuApi<'a> {
377 device: &'a wgpu::Device,
378 queue: &'a wgpu::Queue,
379 renderer: &'a mut imgui_wgpu::WgpuRenderer,
380}
381
382impl<'a> GpuApi<'a> {
383 pub fn device(&self) -> &wgpu::Device {
385 self.device
386 }
387 pub fn queue(&self) -> &wgpu::Queue {
389 self.queue
390 }
391 pub fn register_texture(&mut self, texture: &wgpu::Texture, view: &wgpu::TextureView) -> u64 {
393 self.renderer.register_external_texture(texture, view)
394 }
395 pub fn update_texture_view(&mut self, tex_id: u64, view: &wgpu::TextureView) -> bool {
397 self.renderer.update_external_texture_view(tex_id, view)
398 }
399 pub fn unregister_texture(&mut self, tex_id: u64) {
401 self.renderer.unregister_texture(tex_id)
402 }
403 pub fn update_texture_data(
405 &mut self,
406 texture_data: &mut dear_imgui_rs::TextureData,
407 ) -> Result<(), String> {
408 let res = self
409 .renderer
410 .update_texture(texture_data)
411 .map_err(|e| format!("update_texture failed: {e}"))?;
412 res.apply_to(texture_data);
414 Ok(())
415 }
416}
417
418struct AppWindow {
419 device: wgpu::Device,
420 queue: wgpu::Queue,
421 window: Arc<Window>,
422 surface_desc: wgpu::SurfaceConfiguration,
423 surface: wgpu::Surface<'static>,
424 imgui: ImguiState,
425
426 #[cfg(feature = "implot")]
428 implot_ctx: Option<implot::PlotContext>,
429 #[cfg(feature = "imnodes")]
430 imnodes_ctx: Option<imnodes::Context>,
431 #[cfg(feature = "implot3d")]
432 implot3d_ctx: Option<implot3d::Plot3DContext>,
433
434 clear_color: wgpu::Color,
436 docking_ctrl: DockingController,
437}
438
439impl AppWindow {
440 fn new(
441 event_loop: &ActiveEventLoop,
442 cfg: &RunnerConfig,
443 addons: &AddOnsConfig,
444 cbs: &mut RunnerCallbacks,
445 ) -> Result<Self, DearAppError> {
446 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
448 backends: wgpu::Backends::PRIMARY,
449 ..Default::default()
450 });
451
452 let window = {
453 let size = LogicalSize::new(cfg.window_size.0, cfg.window_size.1);
454 Arc::new(
455 event_loop
456 .create_window(
457 Window::default_attributes()
458 .with_title(cfg.window_title.clone())
459 .with_inner_size(size),
460 )
461 .map_err(|e| DearAppError::Generic(format!("Window creation failed: {e}")))?,
462 )
463 };
464
465 let surface = instance
466 .create_surface(window.clone())
467 .map_err(|e| DearAppError::Generic(format!("Failed to create surface: {e}")))?;
468
469 let adapter = block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
470 power_preference: wgpu::PowerPreference::HighPerformance,
471 compatible_surface: Some(&surface),
472 force_fallback_adapter: false,
473 }))
474 .expect("No suitable GPU adapter found");
475
476 let (device, queue) = block_on(adapter.request_device(&wgpu::DeviceDescriptor::default()))
477 .map_err(|e| DearAppError::Generic(format!("request_device failed: {e}")))?;
478
479 let physical_size = window.inner_size();
481 let caps = surface.get_capabilities(&adapter);
482 let preferred_srgb = [
483 wgpu::TextureFormat::Bgra8UnormSrgb,
484 wgpu::TextureFormat::Rgba8UnormSrgb,
485 ];
486 let format = preferred_srgb
487 .iter()
488 .cloned()
489 .find(|f| caps.formats.contains(f))
490 .unwrap_or(caps.formats[0]);
491
492 let surface_desc = wgpu::SurfaceConfiguration {
493 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
494 format,
495 width: physical_size.width,
496 height: physical_size.height,
497 present_mode: cfg.present_mode,
498 alpha_mode: wgpu::CompositeAlphaMode::Auto,
499 view_formats: vec![],
500 desired_maximum_frame_latency: 2,
501 };
502
503 surface.configure(&device, &surface_desc);
504
505 let mut context = imgui::Context::create();
507 if let Some(p) = &cfg.ini_filename {
509 let _ = context.set_ini_filename(Some(p.clone()));
510 } else {
511 let _ = context.set_ini_filename(None::<String>);
512 }
513
514 if let Some(cb) = cbs.on_setup.as_mut() {
516 cb(&mut context);
517 }
518 if let Some(theme) = cfg.theme {
520 apply_theme(&mut context, theme);
521 }
522 if let Some(cb) = cbs.on_style.as_mut() {
523 cb(&mut context);
524 }
525 if let Some(cb) = cbs.on_fonts.as_mut() {
526 cb(&mut context);
527 }
528
529 let mut platform = imgui_winit::WinitPlatform::new(&mut context);
530 platform.attach_window(&window, imgui_winit::HiDpiMode::Default, &mut context);
531
532 let init_info =
533 imgui_wgpu::WgpuInitInfo::new(device.clone(), queue.clone(), surface_desc.format);
534 let mut renderer = imgui_wgpu::WgpuRenderer::new(init_info, &mut context)
535 .map_err(|e| DearAppError::Generic(format!("Failed to init renderer: {e}")))?;
536 renderer.set_gamma_mode(imgui_wgpu::GammaMode::Auto);
537
538 {
540 let io = context.io_mut();
541 let mut flags = io.config_flags();
542 if cfg.docking.enable {
543 flags.insert(ConfigFlags::DOCKING_ENABLE);
544 }
545 if let Some(extra) = &cfg.io_config_flags {
546 let merged = flags.bits() | extra.bits();
547 flags = ConfigFlags::from_bits_retain(merged);
548 }
549 io.set_config_flags(flags);
550 }
551
552 #[cfg(feature = "implot")]
553 let implot_ctx = if addons.with_implot {
554 Some(implot::PlotContext::create(&context))
555 } else {
556 None
557 };
558
559 #[cfg(feature = "imnodes")]
560 let imnodes_ctx = if addons.with_imnodes {
561 Some(imnodes::Context::create(&context))
562 } else {
563 None
564 };
565
566 #[cfg(feature = "implot3d")]
567 let implot3d_ctx = if addons.with_implot3d {
568 Some(implot3d::Plot3DContext::create(&context))
569 } else {
570 None
571 };
572
573 let imgui = ImguiState {
574 context,
575 platform,
576 renderer,
577 last_frame: Instant::now(),
578 };
579
580 Ok(Self {
581 device,
582 queue,
583 window,
584 surface_desc,
585 surface,
586 imgui,
587 #[cfg(feature = "implot")]
588 implot_ctx,
589 #[cfg(feature = "imnodes")]
590 imnodes_ctx,
591 #[cfg(feature = "implot3d")]
592 implot3d_ctx,
593 clear_color: wgpu::Color {
594 r: cfg.clear_color[0] as f64,
595 g: cfg.clear_color[1] as f64,
596 b: cfg.clear_color[2] as f64,
597 a: cfg.clear_color[3] as f64,
598 },
599 docking_ctrl: DockingController {
600 flags: DockFlags::from_bits_retain(cfg.docking.dockspace_flags.bits()),
601 },
602 })
603 }
604
605 fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
606 if new_size.width > 0 && new_size.height > 0 {
607 self.surface_desc.width = new_size.width;
608 self.surface_desc.height = new_size.height;
609 self.surface.configure(&self.device, &self.surface_desc);
610 }
611 }
612
613 fn render<F>(&mut self, gui: &mut F, docking: &DockingConfig) -> Result<(), DearAppError>
614 where
615 F: FnMut(&imgui::Ui, &mut AddOns),
616 {
617 let now = Instant::now();
618 let delta_time = now - self.imgui.last_frame;
619 self.imgui
620 .context
621 .io_mut()
622 .set_delta_time(delta_time.as_secs_f32());
623 self.imgui.last_frame = now;
624
625 let frame = match self.surface.get_current_texture() {
626 Ok(frame) => frame,
627 Err(SurfaceError::Lost | SurfaceError::Outdated) => {
628 self.surface.configure(&self.device, &self.surface_desc);
629 return Ok(());
630 }
631 Err(SurfaceError::Timeout) => {
632 return Ok(());
633 }
634 Err(e) => return Err(DearAppError::from(e)),
635 };
636
637 self.imgui
638 .platform
639 .prepare_frame(&self.window, &mut self.imgui.context);
640 let ui = self.imgui.context.frame();
641
642 if docking.enable && docking.auto_dockspace {
644 let viewport = ui.main_viewport();
645 ui.set_next_window_viewport(Id::from(viewport.id()));
647 let pos = viewport.pos();
648 let size = viewport.size();
649 let current_flags = DockFlags::from_bits_retain(self.docking_ctrl.flags.bits());
651 let mut win_flags = docking.host_window_flags;
652 if current_flags.contains(DockFlags::PASSTHRU_CENTRAL_NODE) {
653 win_flags |= WindowFlags::NO_BACKGROUND;
654 }
655 ui.window(docking.host_window_name)
656 .flags(win_flags)
657 .position([pos[0], pos[1]], imgui::Condition::Always)
658 .size([size[0], size[1]], imgui::Condition::Always)
659 .build(|| {
660 let ds_flags = DockFlags::from_bits_retain(current_flags.bits());
661 let _ = ui.dockspace_over_main_viewport_with_flags(Id::from(0u32), ds_flags);
662 });
663 }
664
665 let mut addons = AddOns {
667 #[cfg(feature = "implot")]
668 implot: self.implot_ctx.as_ref(),
669 #[cfg(not(feature = "implot"))]
670 implot: None,
671 #[cfg(feature = "imnodes")]
672 imnodes: self.imnodes_ctx.as_ref(),
673 #[cfg(not(feature = "imnodes"))]
674 imnodes: None,
675 #[cfg(feature = "implot3d")]
676 implot3d: self.implot3d_ctx.as_ref(),
677 #[cfg(not(feature = "implot3d"))]
678 implot3d: None,
679 docking: DockingApi {
680 ctrl: &mut self.docking_ctrl,
681 },
682 gpu: GpuApi {
683 device: &self.device,
684 queue: &self.queue,
685 renderer: &mut self.imgui.renderer,
686 },
687 _marker: PhantomData,
688 };
689
690 gui(&ui, &mut addons);
692
693 let view = frame
694 .texture
695 .create_view(&wgpu::TextureViewDescriptor::default());
696 let mut encoder = self
697 .device
698 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
699 label: Some("Render Encoder"),
700 });
701
702 let draw_data = self.imgui.context.render();
703
704 {
705 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
706 label: Some("Render Pass"),
707 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
708 view: &view,
709 resolve_target: None,
710 ops: wgpu::Operations {
711 load: wgpu::LoadOp::Clear(self.clear_color),
712 store: wgpu::StoreOp::Store,
713 },
714 depth_slice: None,
715 })],
716 depth_stencil_attachment: None,
717 timestamp_writes: None,
718 occlusion_query_set: None,
719 multiview_mask: None,
720 });
721
722 self.imgui
723 .renderer
724 .new_frame()
725 .map_err(|e| DearAppError::Generic(format!("new_frame failed: {e}")))?;
726 self.imgui
727 .renderer
728 .render_draw_data(draw_data, &mut rpass)
729 .map_err(|e| DearAppError::Generic(format!("render_draw_data failed: {e}")))?;
730 }
731
732 self.queue.submit(Some(encoder.finish()));
733 frame.present();
734 Ok(())
735 }
736}
737
738struct App<F>
739where
740 F: FnMut(&imgui::Ui, &mut AddOns) + 'static,
741{
742 cfg: RunnerConfig,
743 addons_cfg: AddOnsConfig,
744 window: Option<AppWindow>,
745 gui: F,
746 cbs: RunnerCallbacks,
747 last_wake: Instant,
748}
749
750impl<F> App<F>
751where
752 F: FnMut(&imgui::Ui, &mut AddOns) + 'static,
753{
754 fn new(cfg: RunnerConfig, addons_cfg: AddOnsConfig, cbs: RunnerCallbacks, gui: F) -> Self {
755 Self {
756 cfg,
757 addons_cfg,
758 window: None,
759 gui,
760 cbs,
761 last_wake: Instant::now(),
762 }
763 }
764}
765
766impl<F> ApplicationHandler for App<F>
767where
768 F: FnMut(&imgui::Ui, &mut AddOns) + 'static,
769{
770 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
771 if self.window.is_none() {
772 match AppWindow::new(event_loop, &self.cfg, &self.addons_cfg, &mut self.cbs) {
773 Ok(window) => {
774 self.window = Some(window);
775 info!("Window created successfully");
776 if let Some(cb) = self.cbs.on_post_init.as_mut() {
777 if let Some(w) = self.window.as_mut() {
778 cb(&mut w.imgui.context);
779 }
780 }
781 }
782 Err(e) => {
783 error!("Failed to create window: {e}");
784 event_loop.exit();
785 }
786 }
787 }
788 }
789
790 fn window_event(
791 &mut self,
792 event_loop: &ActiveEventLoop,
793 window_id: WindowId,
794 event: WindowEvent,
795 ) {
796 match event {
799 WindowEvent::RedrawRequested => {
800 let mut need_recreate = false;
802 if let Some(window) = self.window.as_mut() {
803 let full_event: winit::event::Event<()> = winit::event::Event::WindowEvent {
804 window_id,
805 event: event.clone(),
806 };
807 if let Some(cb) = self.cbs.on_event.as_mut() {
808 cb(&full_event, &window.window, &mut window.imgui.context);
809 }
810 window.imgui.platform.handle_event(
811 &mut window.imgui.context,
812 &window.window,
813 &full_event,
814 );
815
816 if let Err(e) = window.render(&mut self.gui, &self.cfg.docking) {
817 error!("Render error: {e}; attempting to recover by recreating GPU state");
818 need_recreate = true;
819 } else {
820 window.window.request_redraw();
821 }
822 }
823
824 if need_recreate {
825 self.window = None;
827 if let Err(e) =
828 AppWindow::new(event_loop, &self.cfg, &self.addons_cfg, &mut self.cbs)
829 {
830 error!("Failed to recreate window after GPU error: {e}");
831 if let Some(cb) = self.cbs.on_exit.as_mut() {
832 if let Some(w) = self.window.as_mut() {
834 cb(&mut w.imgui.context);
835 }
836 }
837 event_loop.exit();
838 } else if let Some(window) = self.window.as_mut() {
839 info!("Successfully recreated window and GPU state after error");
840 if let Some(cb) = self.cbs.on_post_init.as_mut() {
841 cb(&mut window.imgui.context);
842 }
843 window.window.request_redraw();
844 }
845 }
846 }
847 _ => {
848 let window = match self.window.as_mut() {
849 Some(window) => window,
850 None => return,
851 };
852
853 let full_event: winit::event::Event<()> = winit::event::Event::WindowEvent {
854 window_id,
855 event: event.clone(),
856 };
857 if let Some(cb) = self.cbs.on_event.as_mut() {
858 cb(&full_event, &window.window, &mut window.imgui.context);
859 }
860 window.imgui.platform.handle_event(
861 &mut window.imgui.context,
862 &window.window,
863 &full_event,
864 );
865
866 match event {
867 WindowEvent::Resized(physical_size) => {
868 window.resize(physical_size);
869 window.window.request_redraw();
870 }
871 WindowEvent::ScaleFactorChanged { .. } => {
872 let new_size = window.window.inner_size();
873 window.resize(new_size);
874 window.window.request_redraw();
875 }
876 WindowEvent::CloseRequested => {
877 if let Some(cb) = self.cbs.on_exit.as_mut() {
878 if let Some(w) = self.window.as_mut() {
879 cb(&mut w.imgui.context);
880 }
881 }
882 event_loop.exit();
883 }
884 _ => {}
885 }
886 }
887 }
888 }
889
890 fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
891 if let Some(window) = &self.window {
892 match self.cfg.redraw {
893 RedrawMode::Poll => {
894 window.window.request_redraw();
895 }
896 RedrawMode::Wait => {
897 }
899 RedrawMode::WaitUntil { fps } => {
900 let frame = (1.0f32 / fps.max(1.0)) as f32;
901 if self.last_wake.elapsed() >= Duration::from_secs_f32(frame) {
902 window.window.request_redraw();
903 self.last_wake = Instant::now();
904 }
905 }
906 }
907 }
908 }
909}