/ egui-winit / src / epi.rs
epi.rs
  1  pub fn points_to_size(points: egui::Vec2) -> winit::dpi::LogicalSize<f64> {
  2      winit::dpi::LogicalSize {
  3          width: points.x as f64,
  4          height: points.y as f64,
  5      }
  6  }
  7  
  8  pub fn window_builder(
  9      native_options: &epi::NativeOptions,
 10      window_settings: &Option<crate::WindowSettings>,
 11  ) -> winit::window::WindowBuilder {
 12      let epi::NativeOptions {
 13          always_on_top,
 14          maximized,
 15          decorated,
 16          drag_and_drop_support,
 17          icon_data,
 18          initial_window_pos,
 19          initial_window_size,
 20          min_window_size,
 21          max_window_size,
 22          resizable,
 23          transparent,
 24          vsync: _,          // used in `fn create_display`
 25          multisampling: _,  // used in `fn create_display`
 26          depth_buffer: _,   // used in `fn create_display`
 27          stencil_buffer: _, // used in `fn create_display`
 28      } = native_options;
 29  
 30      let window_icon = icon_data.clone().and_then(load_icon);
 31  
 32      let mut window_builder = winit::window::WindowBuilder::new()
 33          .with_always_on_top(*always_on_top)
 34          .with_maximized(*maximized)
 35          .with_decorations(*decorated)
 36          .with_resizable(*resizable)
 37          .with_transparent(*transparent)
 38          .with_window_icon(window_icon);
 39  
 40      if let Some(min_size) = *min_window_size {
 41          window_builder = window_builder.with_min_inner_size(points_to_size(min_size));
 42      }
 43      if let Some(max_size) = *max_window_size {
 44          window_builder = window_builder.with_max_inner_size(points_to_size(max_size));
 45      }
 46  
 47      window_builder = window_builder_drag_and_drop(window_builder, *drag_and_drop_support);
 48  
 49      if let Some(window_settings) = window_settings {
 50          window_builder = window_settings.initialize_window(window_builder);
 51      } else {
 52          if let Some(pos) = *initial_window_pos {
 53              window_builder = window_builder.with_position(winit::dpi::PhysicalPosition {
 54                  x: pos.x as f64,
 55                  y: pos.y as f64,
 56              });
 57          }
 58          if let Some(initial_window_size) = *initial_window_size {
 59              window_builder = window_builder.with_inner_size(points_to_size(initial_window_size));
 60          }
 61      }
 62  
 63      window_builder
 64  }
 65  
 66  fn load_icon(icon_data: epi::IconData) -> Option<winit::window::Icon> {
 67      winit::window::Icon::from_rgba(icon_data.rgba, icon_data.width, icon_data.height).ok()
 68  }
 69  
 70  #[cfg(target_os = "windows")]
 71  fn window_builder_drag_and_drop(
 72      window_builder: winit::window::WindowBuilder,
 73      enable: bool,
 74  ) -> winit::window::WindowBuilder {
 75      use winit::platform::windows::WindowBuilderExtWindows as _;
 76      window_builder.with_drag_and_drop(enable)
 77  }
 78  
 79  #[cfg(not(target_os = "windows"))]
 80  fn window_builder_drag_and_drop(
 81      window_builder: winit::window::WindowBuilder,
 82      _enable: bool,
 83  ) -> winit::window::WindowBuilder {
 84      // drag and drop can only be disabled on windows
 85      window_builder
 86  }
 87  
 88  pub fn handle_app_output(
 89      window: &winit::window::Window,
 90      current_pixels_per_point: f32,
 91      app_output: epi::backend::AppOutput,
 92  ) {
 93      let epi::backend::AppOutput {
 94          quit: _,
 95          window_size,
 96          window_title,
 97          decorated,
 98          drag_window,
 99      } = app_output;
100  
101      if let Some(decorated) = decorated {
102          window.set_decorations(decorated);
103      }
104  
105      if let Some(window_size) = window_size {
106          window.set_inner_size(
107              winit::dpi::PhysicalSize {
108                  width: (current_pixels_per_point * window_size.x).round(),
109                  height: (current_pixels_per_point * window_size.y).round(),
110              }
111              .to_logical::<f32>(crate::native_pixels_per_point(window) as f64),
112          );
113      }
114  
115      if let Some(window_title) = window_title {
116          window.set_title(&window_title);
117      }
118  
119      if drag_window {
120          let _ = window.drag_window();
121      }
122  }
123  
124  // ----------------------------------------------------------------------------
125  
126  /// For loading/saving app state and/or egui memory to disk.
127  pub fn create_storage(_app_name: &str) -> Option<Box<dyn epi::Storage>> {
128      #[cfg(feature = "persistence")]
129      if let Some(storage) = epi::file_storage::FileStorage::from_app_name(_app_name) {
130          return Some(Box::new(storage));
131      }
132      None
133  }
134  
135  // ----------------------------------------------------------------------------
136  
137  /// Everything needed to make a winit-based integration for [`epi`].
138  pub struct EpiIntegration {
139      pub frame: epi::Frame,
140      last_auto_save: instant::Instant,
141      pub egui_ctx: egui::Context,
142      pending_full_output: egui::FullOutput,
143      egui_winit: crate::State,
144      /// When set, it is time to quit
145      quit: bool,
146      can_drag_window: bool,
147  }
148  
149  impl EpiIntegration {
150      pub fn new(
151          integration_name: &'static str,
152          gl: std::rc::Rc<glow::Context>,
153          max_texture_side: usize,
154          window: &winit::window::Window,
155          storage: Option<Box<dyn epi::Storage>>,
156      ) -> Self {
157          let egui_ctx = egui::Context::default();
158  
159          *egui_ctx.memory() = load_egui_memory(storage.as_deref()).unwrap_or_default();
160  
161          let prefer_dark_mode = prefer_dark_mode();
162  
163          let frame = epi::Frame {
164              info: epi::IntegrationInfo {
165                  name: integration_name,
166                  web_info: None,
167                  prefer_dark_mode,
168                  cpu_usage: None,
169                  native_pixels_per_point: Some(crate::native_pixels_per_point(window)),
170              },
171              output: Default::default(),
172              storage,
173              gl,
174          };
175  
176          if prefer_dark_mode == Some(true) {
177              egui_ctx.set_visuals(egui::Visuals::dark());
178          } else {
179              egui_ctx.set_visuals(egui::Visuals::light());
180          }
181  
182          Self {
183              frame,
184              last_auto_save: instant::Instant::now(),
185              egui_ctx,
186              egui_winit: crate::State::new(max_texture_side, window),
187              pending_full_output: Default::default(),
188              quit: false,
189              can_drag_window: false,
190          }
191      }
192  
193      pub fn warm_up(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
194          let saved_memory: egui::Memory = self.egui_ctx.memory().clone();
195          self.egui_ctx.memory().set_everything_is_visible(true);
196          let full_output = self.update(app, window);
197          self.pending_full_output.append(full_output); // Handle it next frame
198          *self.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge.
199          self.egui_ctx.clear_animations();
200      }
201  
202      /// If `true`, it is time to shut down.
203      pub fn should_quit(&self) -> bool {
204          self.quit
205      }
206  
207      pub fn on_event(&mut self, app: &mut dyn epi::App, event: &winit::event::WindowEvent<'_>) {
208          use winit::event::{ElementState, MouseButton, WindowEvent};
209  
210          match event {
211              WindowEvent::CloseRequested => self.quit = app.on_exit_event(),
212              WindowEvent::Destroyed => self.quit = true,
213              WindowEvent::MouseInput {
214                  button: MouseButton::Left,
215                  state: ElementState::Pressed,
216                  ..
217              } => self.can_drag_window = true,
218              _ => {}
219          }
220  
221          self.egui_winit.on_event(&self.egui_ctx, event);
222      }
223  
224      pub fn update(
225          &mut self,
226          app: &mut dyn epi::App,
227          window: &winit::window::Window,
228      ) -> egui::FullOutput {
229          let frame_start = instant::Instant::now();
230  
231          let raw_input = self.egui_winit.take_egui_input(window);
232          let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
233              app.update(egui_ctx, &mut self.frame);
234          });
235          self.pending_full_output.append(full_output);
236          let full_output = std::mem::take(&mut self.pending_full_output);
237  
238          {
239              let mut app_output = self.frame.take_app_output();
240              app_output.drag_window &= self.can_drag_window; // Necessary on Windows; see https://github.com/emilk/egui/pull/1108
241              self.can_drag_window = false;
242              if app_output.quit {
243                  self.quit = app.on_exit_event();
244              }
245              crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
246          }
247  
248          let frame_time = (instant::Instant::now() - frame_start).as_secs_f64() as f32;
249          self.frame.info.cpu_usage = Some(frame_time);
250  
251          full_output
252      }
253  
254      pub fn handle_platform_output(
255          &mut self,
256          window: &winit::window::Window,
257          platform_output: egui::PlatformOutput,
258      ) {
259          self.egui_winit
260              .handle_platform_output(window, &self.egui_ctx, platform_output);
261      }
262  
263      // ------------------------------------------------------------------------
264      // Persistance stuff:
265  
266      pub fn maybe_autosave(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
267          let now = instant::Instant::now();
268          if now - self.last_auto_save > app.auto_save_interval() {
269              self.save(app, window);
270              self.last_auto_save = now;
271          }
272      }
273  
274      pub fn save(&mut self, _app: &mut dyn epi::App, _window: &winit::window::Window) {
275          #[cfg(feature = "persistence")]
276          if let Some(storage) = self.frame.storage_mut() {
277              if _app.persist_native_window() {
278                  epi::set_value(
279                      storage,
280                      STORAGE_WINDOW_KEY,
281                      &crate::WindowSettings::from_display(_window),
282                  );
283              }
284              if _app.persist_egui_memory() {
285                  epi::set_value(storage, STORAGE_EGUI_MEMORY_KEY, &*self.egui_ctx.memory());
286              }
287              _app.save(storage);
288              storage.flush();
289          }
290      }
291  }
292  
293  #[cfg(feature = "persistence")]
294  const STORAGE_EGUI_MEMORY_KEY: &str = "egui";
295  #[cfg(feature = "persistence")]
296  const STORAGE_WINDOW_KEY: &str = "window";
297  
298  pub fn load_window_settings(_storage: Option<&dyn epi::Storage>) -> Option<crate::WindowSettings> {
299      #[cfg(feature = "persistence")]
300      {
301          epi::get_value(_storage?, STORAGE_WINDOW_KEY)
302      }
303      #[cfg(not(feature = "persistence"))]
304      None
305  }
306  
307  pub fn load_egui_memory(_storage: Option<&dyn epi::Storage>) -> Option<egui::Memory> {
308      #[cfg(feature = "persistence")]
309      {
310          epi::get_value(_storage?, STORAGE_EGUI_MEMORY_KEY)
311      }
312      #[cfg(not(feature = "persistence"))]
313      None
314  }
315  
316  #[cfg(feature = "dark-light")]
317  fn prefer_dark_mode() -> Option<bool> {
318      match dark_light::detect() {
319          dark_light::Mode::Dark => Some(true),
320          dark_light::Mode::Light => Some(false),
321      }
322  }
323  
324  #[cfg(not(feature = "dark-light"))]
325  fn prefer_dark_mode() -> Option<bool> {
326      None
327  }