1use dear_imgui_rs as imgui;
22use dear_imgui_rs::{ConfigFlags, DockFlags, 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 _marker: PhantomData<&'a ()>,
101}
102
103pub struct RunnerConfig {
105 pub window_title: String,
106 pub window_size: (f64, f64),
107 pub present_mode: wgpu::PresentMode,
108 pub clear_color: [f32; 4],
109 pub docking: DockingConfig,
110 pub ini_filename: Option<PathBuf>,
111 pub restore_previous_geometry: bool,
112 pub redraw: RedrawMode,
113 pub io_config_flags: Option<ConfigFlags>,
116 pub theme: Option<Theme>,
118}
119
120impl Default for RunnerConfig {
121 fn default() -> Self {
122 Self {
123 window_title: format!("Dear ImGui App - {}", env!("CARGO_PKG_VERSION")),
124 window_size: (1280.0, 720.0),
125 present_mode: wgpu::PresentMode::Fifo,
126 clear_color: [0.1, 0.2, 0.3, 1.0],
127 docking: DockingConfig::default(),
128 ini_filename: None,
129 restore_previous_geometry: true,
130 redraw: RedrawMode::Poll,
131 io_config_flags: None,
132 theme: None,
133 }
134 }
135}
136
137pub struct DockingConfig {
139 pub enable: bool,
141 pub auto_dockspace: bool,
143 pub dockspace_flags: DockFlags,
145 pub host_window_flags: WindowFlags,
147 pub host_window_name: &'static str,
149}
150
151impl Default for DockingConfig {
152 fn default() -> Self {
153 Self {
154 enable: true,
155 auto_dockspace: true,
156 dockspace_flags: DockFlags::PASSTHRU_CENTRAL_NODE,
157 host_window_flags: WindowFlags::NO_TITLE_BAR
158 | WindowFlags::NO_RESIZE
159 | WindowFlags::NO_MOVE
160 | WindowFlags::NO_COLLAPSE
161 | WindowFlags::NO_BRING_TO_FRONT_ON_FOCUS
162 | WindowFlags::NO_NAV_FOCUS,
163 host_window_name: "DockSpaceHost",
164 }
165 }
166}
167
168#[derive(Clone, Copy, Debug)]
170pub enum RedrawMode {
171 Poll,
173 Wait,
175 WaitUntil { fps: f32 },
177}
178
179#[derive(Clone, Copy, Debug)]
181pub enum Theme {
182 Dark,
183 Light,
184 Classic,
185}
186
187fn apply_theme(_ctx: &mut imgui::Context, theme: Theme) {
188 unsafe {
190 match theme {
191 Theme::Dark => dear_imgui_rs::sys::igStyleColorsDark(std::ptr::null_mut()),
192 Theme::Light => dear_imgui_rs::sys::igStyleColorsLight(std::ptr::null_mut()),
193 Theme::Classic => dear_imgui_rs::sys::igStyleColorsClassic(std::ptr::null_mut()),
194 }
195 }
196}
197
198pub struct RunnerCallbacks {
200 pub on_setup: Option<Box<dyn FnMut(&mut imgui::Context)>>,
201 pub on_style: Option<Box<dyn FnMut(&mut imgui::Context)>>,
202 pub on_fonts: Option<Box<dyn FnMut(&mut imgui::Context)>>,
203 pub on_post_init: Option<Box<dyn FnMut(&mut imgui::Context)>>,
204 pub on_event:
205 Option<Box<dyn FnMut(&winit::event::Event<()>, &Arc<Window>, &mut imgui::Context)>>,
206 pub on_exit: Option<Box<dyn FnMut(&mut imgui::Context)>>,
207}
208
209impl Default for RunnerCallbacks {
210 fn default() -> Self {
211 Self {
212 on_setup: None,
213 on_style: None,
214 on_fonts: None,
215 on_post_init: None,
216 on_event: None,
217 on_exit: None,
218 }
219 }
220}
221
222pub struct AppBuilder {
224 cfg: RunnerConfig,
225 addons: AddOnsConfig,
226 cbs: RunnerCallbacks,
227 on_frame: Option<Box<dyn FnMut(&imgui::Ui, &mut AddOns) + 'static>>,
228}
229
230impl AppBuilder {
231 pub fn new() -> Self {
232 Self {
233 cfg: RunnerConfig::default(),
234 addons: AddOnsConfig::default(),
235 cbs: RunnerCallbacks::default(),
236 on_frame: None,
237 }
238 }
239 pub fn with_config(mut self, cfg: RunnerConfig) -> Self {
240 self.cfg = cfg;
241 self
242 }
243 pub fn with_addons(mut self, addons: AddOnsConfig) -> Self {
244 self.addons = addons;
245 self
246 }
247 pub fn with_theme(mut self, theme: Theme) -> Self {
248 self.cfg.theme = Some(theme);
249 self
250 }
251 pub fn on_setup<F: FnMut(&mut imgui::Context) + 'static>(mut self, f: F) -> Self {
252 self.cbs.on_setup = Some(Box::new(f));
253 self
254 }
255 pub fn on_style<F: FnMut(&mut imgui::Context) + 'static>(mut self, f: F) -> Self {
256 self.cbs.on_style = Some(Box::new(f));
257 self
258 }
259 pub fn on_fonts<F: FnMut(&mut imgui::Context) + 'static>(mut self, f: F) -> Self {
260 self.cbs.on_fonts = Some(Box::new(f));
261 self
262 }
263 pub fn on_post_init<F: FnMut(&mut imgui::Context) + 'static>(mut self, f: F) -> Self {
264 self.cbs.on_post_init = Some(Box::new(f));
265 self
266 }
267 pub fn on_event<
268 F: FnMut(&winit::event::Event<()>, &Arc<Window>, &mut imgui::Context) + 'static,
269 >(
270 mut self,
271 f: F,
272 ) -> Self {
273 self.cbs.on_event = Some(Box::new(f));
274 self
275 }
276 pub fn on_frame<F: FnMut(&imgui::Ui, &mut AddOns) + 'static>(mut self, f: F) -> Self {
277 self.on_frame = Some(Box::new(f));
278 self
279 }
280 pub fn on_exit<F: FnMut(&mut imgui::Context) + 'static>(mut self, f: F) -> Self {
281 self.cbs.on_exit = Some(Box::new(f));
282 self
283 }
284 pub fn run(mut self) -> Result<(), DearAppError> {
285 let frame_fn = self
286 .on_frame
287 .take()
288 .ok_or_else(|| DearAppError::Generic("on_frame not set in AppBuilder".into()))?;
289 run_with_callbacks(self.cfg, self.addons, self.cbs, frame_fn)
290 }
291}
292
293pub fn run_simple<F>(mut gui: F) -> Result<(), DearAppError>
299where
300 F: FnMut(&imgui::Ui) + 'static,
301{
302 run(
303 RunnerConfig::default(),
304 AddOnsConfig::default(),
305 move |ui, _addons| gui(ui),
306 )
307}
308
309pub fn run<F>(
313 runner: RunnerConfig,
314 addons_cfg: AddOnsConfig,
315 mut gui: F,
316) -> Result<(), DearAppError>
317where
318 F: FnMut(&imgui::Ui, &mut AddOns) + 'static,
319{
320 run_with_callbacks(runner, addons_cfg, RunnerCallbacks::default(), gui)
321}
322
323pub fn run_with_callbacks<F>(
325 runner: RunnerConfig,
326 addons_cfg: AddOnsConfig,
327 cbs: RunnerCallbacks,
328 gui: F,
329) -> Result<(), DearAppError>
330where
331 F: FnMut(&imgui::Ui, &mut AddOns) + 'static,
332{
333 let event_loop = EventLoop::new()?;
334 match runner.redraw {
335 RedrawMode::Poll => event_loop.set_control_flow(ControlFlow::Poll),
336 RedrawMode::Wait => event_loop.set_control_flow(ControlFlow::Wait),
337 RedrawMode::WaitUntil { .. } => event_loop.set_control_flow(ControlFlow::WaitUntil(
338 Instant::now() + Duration::from_millis(16),
339 )),
340 }
341
342 let mut app = App::new(runner, addons_cfg, cbs, gui);
343 info!("Starting Dear App event loop");
344 event_loop.run_app(&mut app)?;
345 Ok(())
346}
347
348struct ImguiState {
349 context: imgui::Context,
350 platform: imgui_winit::WinitPlatform,
351 renderer: imgui_wgpu::WgpuRenderer,
352 last_frame: Instant,
353}
354
355pub struct DockingController {
357 flags: DockFlags,
358}
359
360pub struct DockingApi<'a> {
361 ctrl: &'a mut DockingController,
362}
363
364impl<'a> DockingApi<'a> {
365 pub fn flags(&self) -> DockFlags {
366 DockFlags::from_bits_retain(self.ctrl.flags.bits())
367 }
368 pub fn set_flags(&mut self, flags: DockFlags) {
369 self.ctrl.flags = flags;
370 }
371}
372
373struct AppWindow {
374 device: wgpu::Device,
375 queue: wgpu::Queue,
376 window: Arc<Window>,
377 surface_desc: wgpu::SurfaceConfiguration,
378 surface: wgpu::Surface<'static>,
379 imgui: ImguiState,
380
381 #[cfg(feature = "implot")]
383 implot_ctx: Option<implot::PlotContext>,
384 #[cfg(feature = "imnodes")]
385 imnodes_ctx: Option<imnodes::Context>,
386 #[cfg(feature = "implot3d")]
387 implot3d_ctx: Option<implot3d::Plot3DContext>,
388
389 clear_color: wgpu::Color,
391 docking_ctrl: DockingController,
392}
393
394impl AppWindow {
395 fn new(
396 event_loop: &ActiveEventLoop,
397 cfg: &RunnerConfig,
398 addons: &AddOnsConfig,
399 cbs: &mut RunnerCallbacks,
400 ) -> Result<Self, DearAppError> {
401 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
403 backends: wgpu::Backends::PRIMARY,
404 ..Default::default()
405 });
406
407 let window = {
408 let size = LogicalSize::new(cfg.window_size.0, cfg.window_size.1);
409 Arc::new(
410 event_loop
411 .create_window(
412 Window::default_attributes()
413 .with_title(cfg.window_title.clone())
414 .with_inner_size(size),
415 )
416 .map_err(|e| DearAppError::Generic(format!("Window creation failed: {e}")))?,
417 )
418 };
419
420 let surface = instance
421 .create_surface(window.clone())
422 .map_err(|e| DearAppError::Generic(format!("Failed to create surface: {e}")))?;
423
424 let adapter = block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
425 power_preference: wgpu::PowerPreference::HighPerformance,
426 compatible_surface: Some(&surface),
427 force_fallback_adapter: false,
428 }))
429 .expect("No suitable GPU adapter found");
430
431 let (device, queue) = block_on(adapter.request_device(&wgpu::DeviceDescriptor::default()))
432 .map_err(|e| DearAppError::Generic(format!("request_device failed: {e}")))?;
433
434 let physical_size = window.inner_size();
436 let caps = surface.get_capabilities(&adapter);
437 let preferred_srgb = [
438 wgpu::TextureFormat::Bgra8UnormSrgb,
439 wgpu::TextureFormat::Rgba8UnormSrgb,
440 ];
441 let format = preferred_srgb
442 .iter()
443 .cloned()
444 .find(|f| caps.formats.contains(f))
445 .unwrap_or(caps.formats[0]);
446
447 let surface_desc = wgpu::SurfaceConfiguration {
448 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
449 format,
450 width: physical_size.width,
451 height: physical_size.height,
452 present_mode: cfg.present_mode,
453 alpha_mode: wgpu::CompositeAlphaMode::Auto,
454 view_formats: vec![],
455 desired_maximum_frame_latency: 2,
456 };
457
458 surface.configure(&device, &surface_desc);
459
460 let mut context = imgui::Context::create();
462 if let Some(p) = &cfg.ini_filename {
464 let _ = context.set_ini_filename(Some(p.clone()));
465 } else {
466 let _ = context.set_ini_filename(None::<String>);
467 }
468
469 if let Some(cb) = cbs.on_setup.as_mut() {
471 cb(&mut context);
472 }
473 if let Some(theme) = cfg.theme {
475 apply_theme(&mut context, theme);
476 }
477 if let Some(cb) = cbs.on_style.as_mut() {
478 cb(&mut context);
479 }
480 if let Some(cb) = cbs.on_fonts.as_mut() {
481 cb(&mut context);
482 }
483
484 let mut platform = imgui_winit::WinitPlatform::new(&mut context);
485 platform.attach_window(&window, imgui_winit::HiDpiMode::Default, &mut context);
486
487 let init_info =
488 imgui_wgpu::WgpuInitInfo::new(device.clone(), queue.clone(), surface_desc.format);
489 let mut renderer = imgui_wgpu::WgpuRenderer::new(init_info, &mut context)
490 .map_err(|e| DearAppError::Generic(format!("Failed to init renderer: {e}")))?;
491 renderer.set_gamma_mode(imgui_wgpu::GammaMode::Auto);
492
493 {
495 let io = context.io_mut();
496 let mut flags = io.config_flags();
497 if cfg.docking.enable {
498 flags.insert(ConfigFlags::DOCKING_ENABLE);
499 }
500 if let Some(extra) = &cfg.io_config_flags {
501 let merged = flags.bits() | extra.bits();
502 flags = ConfigFlags::from_bits_retain(merged);
503 }
504 io.set_config_flags(flags);
505 }
506
507 #[cfg(feature = "implot")]
508 let implot_ctx = if addons.with_implot {
509 Some(implot::PlotContext::create(&context))
510 } else {
511 None
512 };
513
514 #[cfg(feature = "imnodes")]
515 let imnodes_ctx = if addons.with_imnodes {
516 Some(imnodes::Context::create(&context))
517 } else {
518 None
519 };
520
521 #[cfg(feature = "implot3d")]
522 let implot3d_ctx = if addons.with_implot3d {
523 Some(implot3d::Plot3DContext::create(&context))
524 } else {
525 None
526 };
527
528 let imgui = ImguiState {
529 context,
530 platform,
531 renderer,
532 last_frame: Instant::now(),
533 };
534
535 Ok(Self {
536 device,
537 queue,
538 window,
539 surface_desc,
540 surface,
541 imgui,
542 #[cfg(feature = "implot")]
543 implot_ctx,
544 #[cfg(feature = "imnodes")]
545 imnodes_ctx,
546 #[cfg(feature = "implot3d")]
547 implot3d_ctx,
548 clear_color: wgpu::Color {
549 r: cfg.clear_color[0] as f64,
550 g: cfg.clear_color[1] as f64,
551 b: cfg.clear_color[2] as f64,
552 a: cfg.clear_color[3] as f64,
553 },
554 docking_ctrl: DockingController {
555 flags: DockFlags::from_bits_retain(cfg.docking.dockspace_flags.bits()),
556 },
557 })
558 }
559
560 fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
561 if new_size.width > 0 && new_size.height > 0 {
562 self.surface_desc.width = new_size.width;
563 self.surface_desc.height = new_size.height;
564 self.surface.configure(&self.device, &self.surface_desc);
565 }
566 }
567
568 fn render<F>(&mut self, gui: &mut F, docking: &DockingConfig) -> Result<(), DearAppError>
569 where
570 F: FnMut(&imgui::Ui, &mut AddOns),
571 {
572 let now = Instant::now();
573 let delta_time = now - self.imgui.last_frame;
574 self.imgui
575 .context
576 .io_mut()
577 .set_delta_time(delta_time.as_secs_f32());
578 self.imgui.last_frame = now;
579
580 let frame = match self.surface.get_current_texture() {
581 Ok(frame) => frame,
582 Err(SurfaceError::Lost | SurfaceError::Outdated) => {
583 self.surface.configure(&self.device, &self.surface_desc);
584 return Ok(());
585 }
586 Err(SurfaceError::Timeout) => {
587 return Ok(());
588 }
589 Err(e) => return Err(DearAppError::from(e)),
590 };
591
592 self.imgui
593 .platform
594 .prepare_frame(&self.window, &mut self.imgui.context);
595 let ui = self.imgui.context.frame();
596
597 if docking.enable && docking.auto_dockspace {
599 let viewport = ui.main_viewport();
600 let pos = viewport.pos();
602 let size = viewport.size();
603 let current_flags = DockFlags::from_bits_retain(self.docking_ctrl.flags.bits());
605 let mut win_flags = docking.host_window_flags;
606 if current_flags.contains(DockFlags::PASSTHRU_CENTRAL_NODE) {
607 win_flags |= WindowFlags::NO_BACKGROUND;
608 }
609 ui.window(docking.host_window_name)
610 .flags(win_flags)
611 .position([pos[0], pos[1]], imgui::Condition::Always)
612 .size([size[0], size[1]], imgui::Condition::Always)
613 .build(|| {
614 let ds_flags = DockFlags::from_bits_retain(current_flags.bits());
615 let _ = ui.dockspace_over_main_viewport_with_flags(0, ds_flags);
617 });
618 }
619
620 let mut addons = AddOns {
622 #[cfg(feature = "implot")]
623 implot: self.implot_ctx.as_ref(),
624 #[cfg(not(feature = "implot"))]
625 implot: None,
626 #[cfg(feature = "imnodes")]
627 imnodes: self.imnodes_ctx.as_ref(),
628 #[cfg(not(feature = "imnodes"))]
629 imnodes: None,
630 #[cfg(feature = "implot3d")]
631 implot3d: self.implot3d_ctx.as_ref(),
632 #[cfg(not(feature = "implot3d"))]
633 implot3d: None,
634 docking: DockingApi {
635 ctrl: &mut self.docking_ctrl,
636 },
637 _marker: PhantomData,
638 };
639
640 gui(&ui, &mut addons);
642
643 let view = frame
644 .texture
645 .create_view(&wgpu::TextureViewDescriptor::default());
646 let mut encoder = self
647 .device
648 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
649 label: Some("Render Encoder"),
650 });
651
652 let draw_data = self.imgui.context.render();
653
654 {
655 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
656 label: Some("Render Pass"),
657 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
658 view: &view,
659 resolve_target: None,
660 ops: wgpu::Operations {
661 load: wgpu::LoadOp::Clear(self.clear_color),
662 store: wgpu::StoreOp::Store,
663 },
664 depth_slice: None,
665 })],
666 depth_stencil_attachment: None,
667 timestamp_writes: None,
668 occlusion_query_set: None,
669 });
670
671 self.imgui
672 .renderer
673 .new_frame()
674 .map_err(|e| DearAppError::Generic(format!("new_frame failed: {e}")))?;
675 self.imgui
676 .renderer
677 .render_draw_data(draw_data, &mut rpass)
678 .map_err(|e| DearAppError::Generic(format!("render_draw_data failed: {e}")))?;
679 }
680
681 self.queue.submit(Some(encoder.finish()));
682 frame.present();
683 Ok(())
684 }
685}
686
687struct App<F>
688where
689 F: FnMut(&imgui::Ui, &mut AddOns) + 'static,
690{
691 cfg: RunnerConfig,
692 addons_cfg: AddOnsConfig,
693 window: Option<AppWindow>,
694 gui: F,
695 cbs: RunnerCallbacks,
696 last_wake: Instant,
697}
698
699impl<F> App<F>
700where
701 F: FnMut(&imgui::Ui, &mut AddOns) + 'static,
702{
703 fn new(cfg: RunnerConfig, addons_cfg: AddOnsConfig, cbs: RunnerCallbacks, gui: F) -> Self {
704 Self {
705 cfg,
706 addons_cfg,
707 window: None,
708 gui,
709 cbs,
710 last_wake: Instant::now(),
711 }
712 }
713}
714
715impl<F> ApplicationHandler for App<F>
716where
717 F: FnMut(&imgui::Ui, &mut AddOns) + 'static,
718{
719 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
720 if self.window.is_none() {
721 match AppWindow::new(event_loop, &self.cfg, &self.addons_cfg, &mut self.cbs) {
722 Ok(window) => {
723 self.window = Some(window);
724 info!("Window created successfully");
725 if let Some(cb) = self.cbs.on_post_init.as_mut() {
726 if let Some(w) = self.window.as_mut() {
727 cb(&mut w.imgui.context);
728 }
729 }
730 }
731 Err(e) => {
732 error!("Failed to create window: {e}");
733 event_loop.exit();
734 }
735 }
736 }
737 }
738
739 fn window_event(
740 &mut self,
741 event_loop: &ActiveEventLoop,
742 window_id: WindowId,
743 event: WindowEvent,
744 ) {
745 let window = match self.window.as_mut() {
746 Some(window) => window,
747 None => return,
748 };
749
750 let full_event: winit::event::Event<()> = winit::event::Event::WindowEvent {
751 window_id,
752 event: event.clone(),
753 };
754 if let Some(cb) = self.cbs.on_event.as_mut() {
755 cb(&full_event, &window.window, &mut window.imgui.context);
756 }
757 window
758 .imgui
759 .platform
760 .handle_event(&mut window.imgui.context, &window.window, &full_event);
761
762 match event {
763 WindowEvent::Resized(physical_size) => {
764 window.resize(physical_size);
765 window.window.request_redraw();
766 }
767 WindowEvent::ScaleFactorChanged { .. } => {
768 let new_size = window.window.inner_size();
769 window.resize(new_size);
770 window.window.request_redraw();
771 }
772 WindowEvent::CloseRequested => {
773 if let Some(cb) = self.cbs.on_exit.as_mut() {
774 if let Some(w) = self.window.as_mut() {
775 cb(&mut w.imgui.context);
776 }
777 }
778 event_loop.exit();
779 }
780 WindowEvent::RedrawRequested => {
781 if let Err(e) = window.render(&mut self.gui, &self.cfg.docking) {
782 error!("Render error: {e}");
783 }
784 window.window.request_redraw();
785 }
786 _ => {}
787 }
788 }
789
790 fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
791 if let Some(window) = &self.window {
792 match self.cfg.redraw {
793 RedrawMode::Poll => {
794 window.window.request_redraw();
795 }
796 RedrawMode::Wait => {
797 }
799 RedrawMode::WaitUntil { fps } => {
800 let frame = (1.0f32 / fps.max(1.0)) as f32;
801 if self.last_wake.elapsed() >= Duration::from_secs_f32(frame) {
802 window.window.request_redraw();
803 self.last_wake = Instant::now();
804 }
805 }
806 }
807 }
808 }
809}