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 }