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
39pub use wgpu;
41
42#[cfg(feature = "imnodes")]
43use dear_imnodes as imnodes;
44#[cfg(feature = "implot")]
45use dear_implot as implot;
46#[cfg(feature = "implot3d")]
47use dear_implot3d as implot3d;
48
49#[derive(Debug, Error)]
50pub enum DearAppError {
51 #[error("WGPU surface lost")]
52 SurfaceLost,
53 #[error("WGPU surface outdated")]
54 SurfaceOutdated,
55 #[error("WGPU surface timeout")]
56 SurfaceTimeout,
57 #[error("WGPU error: {0}")]
58 Wgpu(#[from] wgpu::SurfaceError),
59 #[error("Window creation error: {0}")]
60 WindowCreation(#[from] winit::error::EventLoopError),
61 #[error("Generic error: {0}")]
62 Generic(String),
63}
64
65#[derive(Default, Clone, Copy)]
67pub struct AddOnsConfig {
68 pub with_implot: bool,
69 pub with_imnodes: bool,
70 pub with_implot3d: bool,
71}
72
73impl AddOnsConfig {
74 pub fn auto() -> Self {
78 Self {
79 with_implot: cfg!(feature = "implot"),
80 with_imnodes: cfg!(feature = "imnodes"),
81 with_implot3d: cfg!(feature = "implot3d"),
82 }
83 }
84}
85
86pub struct AddOns<'a> {
88 #[cfg(feature = "implot")]
89 pub implot: Option<&'a implot::PlotContext>,
90 #[cfg(not(feature = "implot"))]
91 pub implot: Option<()>,
92
93 #[cfg(feature = "imnodes")]
94 pub imnodes: Option<&'a imnodes::Context>,
95 #[cfg(not(feature = "imnodes"))]
96 pub imnodes: Option<()>,
97
98 #[cfg(feature = "implot3d")]
99 pub implot3d: Option<&'a implot3d::Plot3DContext>,
100 #[cfg(not(feature = "implot3d"))]
101 pub implot3d: Option<()>,
102 pub docking: DockingApi<'a>,
103 pub gpu: GpuApi<'a>,
104 _marker: PhantomData<&'a ()>,
105}
106
107pub struct RunnerConfig {
109 pub window_title: String,
110 pub window_size: (f64, f64),
111 pub present_mode: wgpu::PresentMode,
112 pub clear_color: [f32; 4],
113 pub wgpu: WgpuConfig,
115 pub docking: DockingConfig,
116 pub ini_filename: Option<PathBuf>,
117 pub restore_previous_geometry: bool,
118 pub redraw: RedrawMode,
119 pub io_config_flags: Option<ConfigFlags>,
122 pub theme: Option<Theme>,
124}
125
126impl Default for RunnerConfig {
127 fn default() -> Self {
128 Self {
129 window_title: format!("Dear ImGui App - {}", env!("CARGO_PKG_VERSION")),
130 window_size: (1280.0, 720.0),
131 present_mode: wgpu::PresentMode::Fifo,
132 clear_color: [0.1, 0.2, 0.3, 1.0],
133 wgpu: WgpuConfig::default(),
134 docking: DockingConfig::default(),
135 ini_filename: None,
136 restore_previous_geometry: true,
137 redraw: RedrawMode::Poll,
138 io_config_flags: None,
139 theme: None,
140 }
141 }
142}
143
144pub struct WgpuConfig {
149 pub backends: wgpu::Backends,
151 pub power_preference: wgpu::PowerPreference,
153 pub force_fallback_adapter: bool,
155 pub device_label: Option<String>,
157 pub required_features: wgpu::Features,
159 pub required_limits: wgpu::Limits,
161 pub memory_hints: wgpu::MemoryHints,
163}
164
165#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
167pub enum WgpuPreset {
168 #[default]
170 Default,
171 HighPerformance,
173 LowPower,
175 Balanced,
177 DownlevelCompatible,
179 SoftwareFallback,
181}
182
183impl Default for WgpuConfig {
184 fn default() -> Self {
185 Self {
186 backends: wgpu::Backends::PRIMARY,
187 power_preference: wgpu::PowerPreference::HighPerformance,
188 force_fallback_adapter: false,
189 device_label: None,
190 required_features: wgpu::Features::empty(),
191 required_limits: wgpu::Limits::default(),
192 memory_hints: wgpu::MemoryHints::default(),
193 }
194 }
195}
196
197impl WgpuConfig {
198 #[must_use]
200 pub fn from_preset(preset: WgpuPreset) -> Self {
201 match preset {
202 WgpuPreset::Default => Self::default(),
203 WgpuPreset::HighPerformance => Self {
204 power_preference: wgpu::PowerPreference::HighPerformance,
205 memory_hints: wgpu::MemoryHints::Performance,
206 ..Self::default()
207 },
208 WgpuPreset::LowPower => Self {
209 power_preference: wgpu::PowerPreference::LowPower,
210 memory_hints: wgpu::MemoryHints::MemoryUsage,
211 ..Self::default()
212 },
213 WgpuPreset::Balanced => Self {
214 power_preference: wgpu::PowerPreference::None,
215 ..Self::default()
216 },
217 WgpuPreset::DownlevelCompatible => Self {
218 power_preference: wgpu::PowerPreference::None,
219 required_limits: wgpu::Limits::downlevel_defaults(),
220 ..Self::default()
221 },
222 WgpuPreset::SoftwareFallback => Self {
223 power_preference: wgpu::PowerPreference::None,
224 force_fallback_adapter: true,
225 required_limits: wgpu::Limits::downlevel_defaults(),
226 ..Self::default()
227 },
228 }
229 }
230}
231
232pub struct DockingConfig {
234 pub enable: bool,
236 pub auto_dockspace: bool,
238 pub dockspace_flags: DockFlags,
240 pub host_window_flags: WindowFlags,
242 pub host_window_name: &'static str,
244}
245
246impl Default for DockingConfig {
247 fn default() -> Self {
248 Self {
249 enable: true,
250 auto_dockspace: true,
251 dockspace_flags: DockFlags::PASSTHRU_CENTRAL_NODE,
252 host_window_flags: WindowFlags::NO_TITLE_BAR
253 | WindowFlags::NO_RESIZE
254 | WindowFlags::NO_MOVE
255 | WindowFlags::NO_COLLAPSE
256 | WindowFlags::NO_BRING_TO_FRONT_ON_FOCUS
257 | WindowFlags::NO_NAV_FOCUS,
258 host_window_name: "DockSpaceHost",
259 }
260 }
261}
262
263#[derive(Clone, Copy, Debug)]
265pub enum RedrawMode {
266 Poll,
268 Wait,
270 WaitUntil { fps: f32 },
272}
273
274#[derive(Clone, Copy, Debug)]
276pub enum Theme {
277 Dark,
278 Light,
279 Classic,
280}
281
282fn apply_theme(ctx: &mut imgui::Context, theme: Theme) {
283 let preset = match theme {
284 Theme::Dark => imgui::ThemePreset::Dark,
285 Theme::Light => imgui::ThemePreset::Light,
286 Theme::Classic => imgui::ThemePreset::Classic,
287 };
288 let mut t = imgui::Theme::default();
289 t.preset = preset;
290 t.apply_to_context(ctx);
291}
292
293pub struct RunnerCallbacks {
295 pub on_setup: Option<Box<dyn FnMut(&mut imgui::Context)>>,
296 pub on_style: Option<Box<dyn FnMut(&mut imgui::Context)>>,
297 pub on_fonts: Option<Box<dyn FnMut(&mut imgui::Context)>>,
298 pub on_post_init: Option<Box<dyn FnMut(&mut imgui::Context)>>,
299 pub on_gpu_init: Option<
300 Box<dyn FnMut(&Arc<Window>, &wgpu::Device, &wgpu::Queue, &wgpu::SurfaceConfiguration)>,
301 >,
302 pub on_event:
303 Option<Box<dyn FnMut(&winit::event::Event<()>, &Arc<Window>, &mut imgui::Context)>>,
304 pub on_exit: Option<Box<dyn FnMut(&mut imgui::Context)>>,
305}
306
307impl Default for RunnerCallbacks {
308 fn default() -> Self {
309 Self {
310 on_setup: None,
311 on_style: None,
312 on_fonts: None,
313 on_post_init: None,
314 on_gpu_init: None,
315 on_event: None,
316 on_exit: None,
317 }
318 }
319}
320
321pub struct AppBuilder {
323 cfg: RunnerConfig,
324 addons: AddOnsConfig,
325 cbs: RunnerCallbacks,
326 on_frame: Option<Box<dyn FnMut(&imgui::Ui, &mut AddOns) + 'static>>,
327}
328
329impl AppBuilder {
330 pub fn new() -> Self {
331 Self {
332 cfg: RunnerConfig::default(),
333 addons: AddOnsConfig::default(),
334 cbs: RunnerCallbacks::default(),
335 on_frame: None,
336 }
337 }
338 pub fn with_config(mut self, cfg: RunnerConfig) -> Self {
339 self.cfg = cfg;
340 self
341 }
342 pub fn with_addons(mut self, addons: AddOnsConfig) -> Self {
343 self.addons = addons;
344 self
345 }
346 pub fn with_theme(mut self, theme: Theme) -> Self {
347 self.cfg.theme = Some(theme);
348 self
349 }
350 pub fn on_setup<F: FnMut(&mut imgui::Context) + 'static>(mut self, f: F) -> Self {
351 self.cbs.on_setup = Some(Box::new(f));
352 self
353 }
354 pub fn on_style<F: FnMut(&mut imgui::Context) + 'static>(mut self, f: F) -> Self {
355 self.cbs.on_style = Some(Box::new(f));
356 self
357 }
358 pub fn on_fonts<F: FnMut(&mut imgui::Context) + 'static>(mut self, f: F) -> Self {
359 self.cbs.on_fonts = Some(Box::new(f));
360 self
361 }
362 pub fn on_post_init<F: FnMut(&mut imgui::Context) + 'static>(mut self, f: F) -> Self {
363 self.cbs.on_post_init = Some(Box::new(f));
364 self
365 }
366 pub fn on_gpu_init<
367 F: FnMut(&Arc<Window>, &wgpu::Device, &wgpu::Queue, &wgpu::SurfaceConfiguration) + 'static,
368 >(
369 mut self,
370 f: F,
371 ) -> Self {
372 self.cbs.on_gpu_init = Some(Box::new(f));
373 self
374 }
375 pub fn on_event<
376 F: FnMut(&winit::event::Event<()>, &Arc<Window>, &mut imgui::Context) + 'static,
377 >(
378 mut self,
379 f: F,
380 ) -> Self {
381 self.cbs.on_event = Some(Box::new(f));
382 self
383 }
384 pub fn on_frame<F: FnMut(&imgui::Ui, &mut AddOns) + 'static>(mut self, f: F) -> Self {
385 self.on_frame = Some(Box::new(f));
386 self
387 }
388 pub fn on_exit<F: FnMut(&mut imgui::Context) + 'static>(mut self, f: F) -> Self {
389 self.cbs.on_exit = Some(Box::new(f));
390 self
391 }
392 pub fn run(mut self) -> Result<(), DearAppError> {
393 let frame_fn = self
394 .on_frame
395 .take()
396 .ok_or_else(|| DearAppError::Generic("on_frame not set in AppBuilder".into()))?;
397 run_with_callbacks(self.cfg, self.addons, self.cbs, frame_fn)
398 }
399}
400
401pub fn run_simple<F>(mut gui: F) -> Result<(), DearAppError>
407where
408 F: FnMut(&imgui::Ui) + 'static,
409{
410 run(
411 RunnerConfig::default(),
412 AddOnsConfig::default(),
413 move |ui, _addons| gui(ui),
414 )
415}
416
417pub fn run<F>(runner: RunnerConfig, addons_cfg: AddOnsConfig, gui: F) -> Result<(), DearAppError>
421where
422 F: FnMut(&imgui::Ui, &mut AddOns) + 'static,
423{
424 run_with_callbacks(runner, addons_cfg, RunnerCallbacks::default(), gui)
425}
426
427pub fn run_with_callbacks<F>(
429 runner: RunnerConfig,
430 addons_cfg: AddOnsConfig,
431 cbs: RunnerCallbacks,
432 gui: F,
433) -> Result<(), DearAppError>
434where
435 F: FnMut(&imgui::Ui, &mut AddOns) + 'static,
436{
437 let event_loop = EventLoop::new()?;
438 match runner.redraw {
439 RedrawMode::Poll => event_loop.set_control_flow(ControlFlow::Poll),
440 RedrawMode::Wait => event_loop.set_control_flow(ControlFlow::Wait),
441 RedrawMode::WaitUntil { fps } => {
442 let frame = Duration::from_secs_f32(1.0f32 / fps.max(1.0));
443 event_loop.set_control_flow(ControlFlow::WaitUntil(Instant::now() + frame));
444 }
445 }
446
447 let mut app = App::new(runner, addons_cfg, cbs, gui);
448 info!("Starting Dear App event loop");
449 event_loop.run_app(&mut app)?;
450 Ok(())
451}
452
453struct ImguiState {
454 context: imgui::Context,
455 platform: imgui_winit::WinitPlatform,
456 renderer: imgui_wgpu::WgpuRenderer,
457}
458
459pub struct DockingController {
461 flags: DockFlags,
462}
463
464pub struct DockingApi<'a> {
465 ctrl: &'a mut DockingController,
466}
467
468impl<'a> DockingApi<'a> {
469 pub fn flags(&self) -> DockFlags {
470 DockFlags::from_bits_retain(self.ctrl.flags.bits())
471 }
472 pub fn set_flags(&mut self, flags: DockFlags) {
473 self.ctrl.flags = flags;
474 }
475}
476
477pub struct GpuApi<'a> {
480 device: &'a wgpu::Device,
481 queue: &'a wgpu::Queue,
482 renderer: &'a mut imgui_wgpu::WgpuRenderer,
483}
484
485impl<'a> GpuApi<'a> {
486 pub fn device(&self) -> &wgpu::Device {
488 self.device
489 }
490 pub fn queue(&self) -> &wgpu::Queue {
492 self.queue
493 }
494 pub fn register_texture(&mut self, texture: &wgpu::Texture, view: &wgpu::TextureView) -> u64 {
496 self.renderer.register_external_texture(texture, view)
497 }
498 pub fn update_texture_view(&mut self, tex_id: u64, view: &wgpu::TextureView) -> bool {
500 self.renderer.update_external_texture_view(tex_id, view)
501 }
502 pub fn unregister_texture(&mut self, tex_id: u64) {
504 self.renderer.unregister_texture(tex_id)
505 }
506 pub fn update_texture_data(
508 &mut self,
509 texture_data: &mut dear_imgui_rs::TextureData,
510 ) -> Result<(), String> {
511 let res = self
512 .renderer
513 .update_texture(texture_data)
514 .map_err(|e| format!("update_texture failed: {e}"))?;
515 res.apply_to(texture_data);
517 Ok(())
518 }
519}
520
521struct AppWindow {
522 #[allow(dead_code)]
524 instance: wgpu::Instance,
525 device: wgpu::Device,
526 queue: wgpu::Queue,
527 window: Arc<Window>,
528 surface_desc: wgpu::SurfaceConfiguration,
529 surface: wgpu::Surface<'static>,
530 imgui: ImguiState,
531
532 #[cfg(feature = "implot")]
534 implot_ctx: Option<implot::PlotContext>,
535 #[cfg(feature = "imnodes")]
536 imnodes_ctx: Option<imnodes::Context>,
537 #[cfg(feature = "implot3d")]
538 implot3d_ctx: Option<implot3d::Plot3DContext>,
539
540 clear_color: wgpu::Color,
542 docking_ctrl: DockingController,
543}
544
545impl AppWindow {
546 fn new(
547 event_loop: &ActiveEventLoop,
548 cfg: &RunnerConfig,
549 addons: &AddOnsConfig,
550 cbs: &mut RunnerCallbacks,
551 ) -> Result<Self, DearAppError> {
552 let _ = addons;
553
554 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
556 backends: cfg.wgpu.backends,
557 ..Default::default()
558 });
559
560 let window = {
561 let size = LogicalSize::new(cfg.window_size.0, cfg.window_size.1);
562 Arc::new(
563 event_loop
564 .create_window(
565 Window::default_attributes()
566 .with_title(cfg.window_title.clone())
567 .with_inner_size(size),
568 )
569 .map_err(|e| DearAppError::Generic(format!("Window creation failed: {e}")))?,
570 )
571 };
572
573 let surface = instance
574 .create_surface(window.clone())
575 .map_err(|e| DearAppError::Generic(format!("Failed to create surface: {e}")))?;
576
577 let adapter = block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
578 power_preference: cfg.wgpu.power_preference,
579 compatible_surface: Some(&surface),
580 force_fallback_adapter: cfg.wgpu.force_fallback_adapter,
581 }))
582 .expect("No suitable GPU adapter found");
583
584 let device_desc = wgpu::DeviceDescriptor {
585 label: cfg.wgpu.device_label.as_deref(),
586 required_features: cfg.wgpu.required_features,
587 required_limits: cfg.wgpu.required_limits.clone(),
588 memory_hints: cfg.wgpu.memory_hints.clone(),
589 ..Default::default()
590 };
591 let (device, queue) = block_on(adapter.request_device(&device_desc))
592 .map_err(|e| DearAppError::Generic(format!("request_device failed: {e}")))?;
593
594 let physical_size = window.inner_size();
596 let caps = surface.get_capabilities(&adapter);
597 let preferred_srgb = [
598 wgpu::TextureFormat::Bgra8UnormSrgb,
599 wgpu::TextureFormat::Rgba8UnormSrgb,
600 ];
601 let format = preferred_srgb
602 .iter()
603 .cloned()
604 .find(|f| caps.formats.contains(f))
605 .unwrap_or(caps.formats[0]);
606
607 let surface_desc = wgpu::SurfaceConfiguration {
608 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
609 format,
610 width: physical_size.width,
611 height: physical_size.height,
612 present_mode: cfg.present_mode,
613 alpha_mode: wgpu::CompositeAlphaMode::Auto,
614 view_formats: vec![],
615 desired_maximum_frame_latency: 2,
616 };
617
618 surface.configure(&device, &surface_desc);
619
620 if let Some(cb) = cbs.on_gpu_init.as_mut() {
621 cb(&window, &device, &queue, &surface_desc);
622 }
623
624 let mut context = imgui::Context::create();
626 if !cfg.restore_previous_geometry {
628 let _ = context.set_ini_filename(None::<String>);
629 } else if let Some(p) = &cfg.ini_filename {
630 let _ = context.set_ini_filename(Some(p.clone()));
631 } else {
632 let _ = context.set_ini_filename(None::<String>);
633 }
634
635 if let Some(cb) = cbs.on_setup.as_mut() {
637 cb(&mut context);
638 }
639 if let Some(theme) = cfg.theme {
641 apply_theme(&mut context, theme);
642 }
643 if let Some(cb) = cbs.on_style.as_mut() {
644 cb(&mut context);
645 }
646 if let Some(cb) = cbs.on_fonts.as_mut() {
647 cb(&mut context);
648 }
649
650 let mut platform = imgui_winit::WinitPlatform::new(&mut context);
651 platform.attach_window(&window, imgui_winit::HiDpiMode::Default, &mut context);
652
653 let init_info =
654 imgui_wgpu::WgpuInitInfo::new(device.clone(), queue.clone(), surface_desc.format);
655 let mut renderer = imgui_wgpu::WgpuRenderer::new(init_info, &mut context)
656 .map_err(|e| DearAppError::Generic(format!("Failed to init renderer: {e}")))?;
657 renderer.set_gamma_mode(imgui_wgpu::GammaMode::Auto);
658
659 {
661 let io = context.io_mut();
662 let mut flags = io.config_flags();
663 if cfg.docking.enable {
664 flags.insert(ConfigFlags::DOCKING_ENABLE);
665 }
666 if let Some(extra) = &cfg.io_config_flags {
667 let merged = flags.bits() | extra.bits();
668 flags = ConfigFlags::from_bits_retain(merged);
669 }
670 io.set_config_flags(flags);
671 }
672
673 #[cfg(feature = "implot")]
674 let implot_ctx = if addons.with_implot {
675 Some(implot::PlotContext::create(&context))
676 } else {
677 None
678 };
679
680 #[cfg(feature = "imnodes")]
681 let imnodes_ctx = if addons.with_imnodes {
682 Some(imnodes::Context::create(&context))
683 } else {
684 None
685 };
686
687 #[cfg(feature = "implot3d")]
688 let implot3d_ctx = if addons.with_implot3d {
689 Some(implot3d::Plot3DContext::create(&context))
690 } else {
691 None
692 };
693
694 let imgui = ImguiState {
695 context,
696 platform,
697 renderer,
698 };
699
700 Ok(Self {
701 instance,
702 device,
703 queue,
704 window,
705 surface_desc,
706 surface,
707 imgui,
708 #[cfg(feature = "implot")]
709 implot_ctx,
710 #[cfg(feature = "imnodes")]
711 imnodes_ctx,
712 #[cfg(feature = "implot3d")]
713 implot3d_ctx,
714 clear_color: wgpu::Color {
715 r: cfg.clear_color[0] as f64,
716 g: cfg.clear_color[1] as f64,
717 b: cfg.clear_color[2] as f64,
718 a: cfg.clear_color[3] as f64,
719 },
720 docking_ctrl: DockingController {
721 flags: DockFlags::from_bits_retain(cfg.docking.dockspace_flags.bits()),
722 },
723 })
724 }
725
726 fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
727 if new_size.width > 0 && new_size.height > 0 {
728 self.surface_desc.width = new_size.width;
729 self.surface_desc.height = new_size.height;
730 self.surface.configure(&self.device, &self.surface_desc);
731 }
732 }
733
734 fn render<F>(&mut self, gui: &mut F, docking: &DockingConfig) -> Result<(), DearAppError>
735 where
736 F: FnMut(&imgui::Ui, &mut AddOns),
737 {
738 self.imgui
739 .platform
740 .prepare_frame(&self.window, &mut self.imgui.context);
741 let ui = self.imgui.context.frame();
742
743 if docking.enable && docking.auto_dockspace {
745 let viewport = ui.main_viewport();
746 ui.set_next_window_viewport(Id::from(viewport.id()));
748 let pos = viewport.pos();
749 let size = viewport.size();
750 let current_flags = DockFlags::from_bits_retain(self.docking_ctrl.flags.bits());
752 let mut win_flags = docking.host_window_flags;
753 if current_flags.contains(DockFlags::PASSTHRU_CENTRAL_NODE) {
754 win_flags |= WindowFlags::NO_BACKGROUND;
755 }
756 ui.window(docking.host_window_name)
757 .flags(win_flags)
758 .position([pos[0], pos[1]], imgui::Condition::Always)
759 .size([size[0], size[1]], imgui::Condition::Always)
760 .build(|| {
761 let ds_flags = DockFlags::from_bits_retain(current_flags.bits());
762 let _ = ui.dockspace_over_main_viewport_with_flags(Id::from(0u32), ds_flags);
763 });
764 }
765
766 let mut addons = AddOns {
768 #[cfg(feature = "implot")]
769 implot: self.implot_ctx.as_ref(),
770 #[cfg(not(feature = "implot"))]
771 implot: None,
772 #[cfg(feature = "imnodes")]
773 imnodes: self.imnodes_ctx.as_ref(),
774 #[cfg(not(feature = "imnodes"))]
775 imnodes: None,
776 #[cfg(feature = "implot3d")]
777 implot3d: self.implot3d_ctx.as_ref(),
778 #[cfg(not(feature = "implot3d"))]
779 implot3d: None,
780 docking: DockingApi {
781 ctrl: &mut self.docking_ctrl,
782 },
783 gpu: GpuApi {
784 device: &self.device,
785 queue: &self.queue,
786 renderer: &mut self.imgui.renderer,
787 },
788 _marker: PhantomData,
789 };
790
791 gui(&ui, &mut addons);
793
794 self.imgui
796 .platform
797 .prepare_render_with_ui(&ui, &self.window);
798
799 let draw_data = self.imgui.context.render();
800
801 let frame = match self.surface.get_current_texture() {
803 Ok(frame) => frame,
804 Err(SurfaceError::Lost | SurfaceError::Outdated) => {
805 self.surface.configure(&self.device, &self.surface_desc);
806 return Ok(());
807 }
808 Err(SurfaceError::Timeout) => return Ok(()),
809 Err(e) => return Err(DearAppError::from(e)),
810 };
811
812 let view = frame
813 .texture
814 .create_view(&wgpu::TextureViewDescriptor::default());
815 let mut encoder = self
816 .device
817 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
818 label: Some("Render Encoder"),
819 });
820
821 {
822 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
823 label: Some("Render Pass"),
824 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
825 view: &view,
826 resolve_target: None,
827 ops: wgpu::Operations {
828 load: wgpu::LoadOp::Clear(self.clear_color),
829 store: wgpu::StoreOp::Store,
830 },
831 depth_slice: None,
832 })],
833 depth_stencil_attachment: None,
834 timestamp_writes: None,
835 occlusion_query_set: None,
836 multiview_mask: None,
837 });
838
839 self.imgui
840 .renderer
841 .new_frame()
842 .map_err(|e| DearAppError::Generic(format!("new_frame failed: {e}")))?;
843 self.imgui
844 .renderer
845 .render_draw_data(draw_data, &mut rpass)
846 .map_err(|e| DearAppError::Generic(format!("render_draw_data failed: {e}")))?;
847 }
848
849 self.queue.submit(Some(encoder.finish()));
850 frame.present();
851 Ok(())
852 }
853}
854
855struct App<F>
856where
857 F: FnMut(&imgui::Ui, &mut AddOns) + 'static,
858{
859 cfg: RunnerConfig,
860 addons_cfg: AddOnsConfig,
861 window: Option<AppWindow>,
862 gui: F,
863 cbs: RunnerCallbacks,
864 last_wake: Instant,
865}
866
867impl<F> App<F>
868where
869 F: FnMut(&imgui::Ui, &mut AddOns) + 'static,
870{
871 fn new(cfg: RunnerConfig, addons_cfg: AddOnsConfig, cbs: RunnerCallbacks, gui: F) -> Self {
872 Self {
873 cfg,
874 addons_cfg,
875 window: None,
876 gui,
877 cbs,
878 last_wake: Instant::now(),
879 }
880 }
881}
882
883impl<F> ApplicationHandler for App<F>
884where
885 F: FnMut(&imgui::Ui, &mut AddOns) + 'static,
886{
887 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
888 if self.window.is_none() {
889 match AppWindow::new(event_loop, &self.cfg, &self.addons_cfg, &mut self.cbs) {
890 Ok(window) => {
891 self.window = Some(window);
892 info!("Window created successfully");
893 if let Some(cb) = self.cbs.on_post_init.as_mut() {
894 if let Some(w) = self.window.as_mut() {
895 cb(&mut w.imgui.context);
896 }
897 }
898 if let Some(w) = self.window.as_ref() {
899 w.window.request_redraw();
900 }
901 }
902 Err(e) => {
903 error!("Failed to create window: {e}");
904 event_loop.exit();
905 }
906 }
907 }
908 }
909
910 fn window_event(
911 &mut self,
912 event_loop: &ActiveEventLoop,
913 window_id: WindowId,
914 event: WindowEvent,
915 ) {
916 match event {
919 WindowEvent::RedrawRequested => {
920 let mut need_recreate = false;
922 if let Some(window) = self.window.as_mut() {
923 let full_event: winit::event::Event<()> = winit::event::Event::WindowEvent {
924 window_id,
925 event: event.clone(),
926 };
927 if let Some(cb) = self.cbs.on_event.as_mut() {
928 cb(&full_event, &window.window, &mut window.imgui.context);
929 }
930 window.imgui.platform.handle_event(
931 &mut window.imgui.context,
932 &window.window,
933 &full_event,
934 );
935
936 if let Err(e) = window.render(&mut self.gui, &self.cfg.docking) {
937 error!("Render error: {e}; attempting to recover by recreating GPU state");
938 need_recreate = true;
939 } else if matches!(self.cfg.redraw, RedrawMode::Poll) {
940 window.window.request_redraw();
941 }
942 }
943
944 if need_recreate {
945 let mut old_window = self.window.take();
947 match AppWindow::new(event_loop, &self.cfg, &self.addons_cfg, &mut self.cbs) {
948 Ok(window) => {
949 self.window = Some(window);
950 info!("Successfully recreated window and GPU state after error");
951 if let Some(window) = self.window.as_mut() {
952 if let Some(cb) = self.cbs.on_post_init.as_mut() {
953 cb(&mut window.imgui.context);
954 }
955 window.window.request_redraw();
956 }
957 }
958 Err(e) => {
959 error!("Failed to recreate window after GPU error: {e}");
960 if let (Some(cb), Some(old)) =
961 (self.cbs.on_exit.as_mut(), old_window.as_mut())
962 {
963 cb(&mut old.imgui.context);
964 }
965 event_loop.exit();
966 }
967 }
968 }
969 }
970 _ => {
971 let window = match self.window.as_mut() {
972 Some(window) => window,
973 None => return,
974 };
975
976 let full_event: winit::event::Event<()> = winit::event::Event::WindowEvent {
977 window_id,
978 event: event.clone(),
979 };
980 if let Some(cb) = self.cbs.on_event.as_mut() {
981 cb(&full_event, &window.window, &mut window.imgui.context);
982 }
983 window.imgui.platform.handle_event(
984 &mut window.imgui.context,
985 &window.window,
986 &full_event,
987 );
988
989 match event {
990 WindowEvent::Resized(physical_size) => {
991 window.resize(physical_size);
992 window.window.request_redraw();
993 }
994 WindowEvent::ScaleFactorChanged { .. } => {
995 let new_size = window.window.inner_size();
996 window.resize(new_size);
997 window.window.request_redraw();
998 }
999 WindowEvent::CloseRequested => {
1000 if let Some(cb) = self.cbs.on_exit.as_mut() {
1001 if let Some(w) = self.window.as_mut() {
1002 cb(&mut w.imgui.context);
1003 }
1004 }
1005 event_loop.exit();
1006 }
1007 _ => {}
1008 }
1009 }
1010 }
1011 }
1012
1013 fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
1014 match self.cfg.redraw {
1015 RedrawMode::Poll => {
1016 event_loop.set_control_flow(ControlFlow::Poll);
1017 if let Some(window) = &self.window {
1018 window.window.request_redraw();
1019 }
1020 }
1021 RedrawMode::Wait => {
1022 event_loop.set_control_flow(ControlFlow::Wait);
1023 }
1024 RedrawMode::WaitUntil { fps } => {
1025 let frame = Duration::from_secs_f32(1.0f32 / fps.max(1.0));
1026 let now = Instant::now();
1027 let mut next_wake = self.last_wake + frame;
1028 if now >= next_wake {
1029 self.last_wake = now;
1030 next_wake = self.last_wake + frame;
1031 if let Some(window) = &self.window {
1032 window.window.request_redraw();
1033 }
1034 }
1035 event_loop.set_control_flow(ControlFlow::WaitUntil(next_wake));
1036 }
1037 }
1038 }
1039}