1use web_time::Instant;
4
5use std::path::PathBuf;
6use winit::event_loop::ActiveEventLoop;
7
8use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _};
9
10use egui::{DeferredViewportUiCallback, ViewportBuilder, ViewportId};
11use egui_winit::{EventResponse, WindowSettings};
12
13use crate::epi;
14
15#[cfg_attr(target_os = "ios", allow(dead_code, unused_variables, unused_mut))]
16pub fn viewport_builder(
17 egui_zoom_factor: f32,
18 event_loop: &ActiveEventLoop,
19 native_options: &mut epi::NativeOptions,
20 window_settings: Option<WindowSettings>,
21) -> ViewportBuilder {
22 profiling::function_scope!();
23
24 let mut viewport_builder = native_options.viewport.clone();
25
26 let clamp_size_to_monitor_size = viewport_builder.clamp_size_to_monitor_size.unwrap_or(true);
29
30 #[cfg(not(target_os = "ios"))]
33 let inner_size_points = if let Some(mut window_settings) = window_settings {
34 if clamp_size_to_monitor_size {
37 window_settings.clamp_size_to_sane_values(largest_monitor_point_size(
38 egui_zoom_factor,
39 event_loop,
40 ));
41 }
42 window_settings.clamp_position_to_monitors(egui_zoom_factor, event_loop);
43
44 viewport_builder = window_settings.initialize_viewport_builder(
45 egui_zoom_factor,
46 event_loop,
47 viewport_builder,
48 );
49 window_settings.inner_size_points()
50 } else {
51 if let Some(pos) = viewport_builder.position {
52 viewport_builder = viewport_builder.with_position(pos);
53 }
54
55 if clamp_size_to_monitor_size {
56 if let Some(initial_window_size) = viewport_builder.inner_size {
57 let initial_window_size = egui::NumExt::at_most(
58 initial_window_size,
59 largest_monitor_point_size(egui_zoom_factor, event_loop),
60 );
61 viewport_builder = viewport_builder.with_inner_size(initial_window_size);
62 }
63 }
64
65 viewport_builder.inner_size
66 };
67
68 #[cfg(not(target_os = "ios"))]
69 if native_options.centered {
70 profiling::scope!("center");
71 if let Some(monitor) = event_loop
72 .primary_monitor()
73 .or_else(|| event_loop.available_monitors().next())
74 {
75 let monitor_size = monitor
76 .size()
77 .to_logical::<f32>(egui_zoom_factor as f64 * monitor.scale_factor());
78 let inner_size = inner_size_points.unwrap_or(egui::Vec2 { x: 800.0, y: 600.0 });
79 if 0.0 < monitor_size.width && 0.0 < monitor_size.height {
80 let x = (monitor_size.width - inner_size.x) / 2.0;
81 let y = (monitor_size.height - inner_size.y) / 2.0;
82 viewport_builder = viewport_builder.with_position([x, y]);
83 }
84 }
85 }
86
87 match std::mem::take(&mut native_options.window_builder) {
88 Some(hook) => hook(viewport_builder),
89 None => viewport_builder,
90 }
91}
92
93pub fn apply_window_settings(
94 window: &winit::window::Window,
95 window_settings: Option<WindowSettings>,
96) {
97 profiling::function_scope!();
98 if let Some(window_settings) = window_settings {
99 window_settings.initialize_window(window);
100 }
101}
102
103#[cfg(not(target_os = "ios"))]
104fn largest_monitor_point_size(egui_zoom_factor: f32, event_loop: &ActiveEventLoop) -> egui::Vec2 {
105 profiling::function_scope!();
106 let mut max_size = egui::Vec2::ZERO;
107
108 let available_monitors = {
109 profiling::scope!("available_monitors");
110 event_loop.available_monitors()
111 };
112
113 for monitor in available_monitors {
114 let size = monitor
115 .size()
116 .to_logical::<f32>(egui_zoom_factor as f64 * monitor.scale_factor());
117 let size = egui::vec2(size.width, size.height);
118 max_size = max_size.max(size);
119 }
120
121 if max_size == egui::Vec2::ZERO {
122 egui::Vec2::splat(16000.0)
123 } else {
124 max_size
125 }
126}
127
128pub fn create_storage(_app_name: &str) -> Option<Box<dyn epi::Storage>> {
132 #[cfg(feature = "persistence")]
133 if let Some(storage) = super::file_storage::FileStorage::from_app_id(_app_name) {
134 return Some(Box::new(storage));
135 }
136 None
137}
138
139#[allow(clippy::unnecessary_wraps)]
140pub fn create_storage_with_file(_file: impl Into<PathBuf>) -> Option<Box<dyn epi::Storage>> {
141 #[cfg(feature = "persistence")]
142 return Some(Box::new(
143 super::file_storage::FileStorage::from_ron_filepath(_file),
144 ));
145 #[cfg(not(feature = "persistence"))]
146 None
147}
148
149pub struct EpiIntegration {
155 pub frame: epi::Frame,
156 last_auto_save: Instant,
157 pub beginning: Instant,
158 is_first_frame: bool,
159 pub egui_ctx: egui::Context,
160 pending_full_output: egui::FullOutput,
161
162 close: bool,
164
165 can_drag_window: bool,
166 #[cfg(feature = "persistence")]
167 persist_window: bool,
168 app_icon_setter: super::app_icon::AppTitleIconSetter,
169}
170
171impl EpiIntegration {
172 #[allow(clippy::too_many_arguments)]
173 pub fn new(
174 egui_ctx: egui::Context,
175 window: &winit::window::Window,
176 app_name: &str,
177 native_options: &crate::NativeOptions,
178 storage: Option<Box<dyn epi::Storage>>,
179 #[cfg(feature = "glow")] gl: Option<std::sync::Arc<glow::Context>>,
180 #[cfg(feature = "glow")] glow_register_native_texture: Option<
181 Box<dyn FnMut(glow::Texture) -> egui::TextureId>,
182 >,
183 #[cfg(feature = "wgpu")] wgpu_render_state: Option<egui_wgpu::RenderState>,
184 ) -> Self {
185 let frame = epi::Frame {
186 info: epi::IntegrationInfo { cpu_usage: None },
187 storage,
188 #[cfg(feature = "glow")]
189 gl,
190 #[cfg(feature = "glow")]
191 glow_register_native_texture,
192 #[cfg(feature = "wgpu")]
193 wgpu_render_state,
194 raw_display_handle: window.display_handle().map(|h| h.as_raw()),
195 raw_window_handle: window.window_handle().map(|h| h.as_raw()),
196 };
197
198 let icon = native_options
199 .viewport
200 .icon
201 .clone()
202 .unwrap_or_else(|| std::sync::Arc::new(load_default_egui_icon()));
203
204 let app_icon_setter = super::app_icon::AppTitleIconSetter::new(
205 native_options
206 .viewport
207 .title
208 .clone()
209 .unwrap_or_else(|| app_name.to_owned()),
210 Some(icon),
211 );
212
213 Self {
214 frame,
215 last_auto_save: Instant::now(),
216 egui_ctx,
217 pending_full_output: Default::default(),
218 close: false,
219 can_drag_window: false,
220 #[cfg(feature = "persistence")]
221 persist_window: native_options.persist_window,
222 app_icon_setter,
223 beginning: Instant::now(),
224 is_first_frame: true,
225 }
226 }
227
228 pub fn should_close(&self) -> bool {
230 self.close
231 }
232
233 pub fn on_window_event(
234 &mut self,
235 window: &winit::window::Window,
236 egui_winit: &mut egui_winit::State,
237 event: &winit::event::WindowEvent,
238 ) -> EventResponse {
239 profiling::function_scope!(egui_winit::short_window_event_description(event));
240
241 use winit::event::{ElementState, MouseButton, WindowEvent};
242
243 if let WindowEvent::MouseInput {
244 button: MouseButton::Left,
245 state: ElementState::Pressed,
246 ..
247 } = event
248 {
249 self.can_drag_window = true;
250 }
251
252 egui_winit.on_window_event(window, event)
253 }
254
255 pub fn pre_update(&mut self) {
256 self.app_icon_setter.update();
257 }
258
259 pub fn update(
263 &mut self,
264 app: &mut dyn epi::App,
265 viewport_ui_cb: Option<&DeferredViewportUiCallback>,
266 mut raw_input: egui::RawInput,
267 ) -> egui::FullOutput {
268 raw_input.time = Some(self.beginning.elapsed().as_secs_f64());
269
270 let close_requested = raw_input.viewport().close_requested();
271
272 app.raw_input_hook(&self.egui_ctx, &mut raw_input);
273
274 let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
275 if let Some(viewport_ui_cb) = viewport_ui_cb {
276 profiling::scope!("viewport_callback");
278 viewport_ui_cb(egui_ctx);
279 } else {
280 profiling::scope!("App::update");
281 app.update(egui_ctx, &mut self.frame);
282 }
283 });
284
285 let is_root_viewport = viewport_ui_cb.is_none();
286 if is_root_viewport && close_requested {
287 let canceled = full_output.viewport_output[&ViewportId::ROOT]
288 .commands
289 .contains(&egui::ViewportCommand::CancelClose);
290 if canceled {
291 log::debug!("Closing of root viewport canceled with ViewportCommand::CancelClose");
292 } else {
293 log::debug!("Closing root viewport (ViewportCommand::CancelClose was not sent)");
294 self.close = true;
295 }
296 }
297
298 self.pending_full_output.append(full_output);
299 std::mem::take(&mut self.pending_full_output)
300 }
301
302 pub fn report_frame_time(&mut self, seconds: f32) {
303 self.frame.info.cpu_usage = Some(seconds);
304 }
305
306 pub fn post_rendering(&mut self, window: &winit::window::Window) {
307 profiling::function_scope!();
308 if std::mem::take(&mut self.is_first_frame) {
309 window.set_visible(true);
311 }
312 }
313
314 pub fn maybe_autosave(
318 &mut self,
319 app: &mut dyn epi::App,
320 window: Option<&winit::window::Window>,
321 ) {
322 let now = Instant::now();
323 if now - self.last_auto_save > app.auto_save_interval() {
324 self.save(app, window);
325 self.last_auto_save = now;
326 }
327 }
328
329 #[allow(clippy::unused_self)]
330 pub fn save(&mut self, _app: &mut dyn epi::App, _window: Option<&winit::window::Window>) {
331 #[cfg(feature = "persistence")]
332 if let Some(storage) = self.frame.storage_mut() {
333 profiling::function_scope!();
334
335 if let Some(window) = _window {
336 if self.persist_window {
337 profiling::scope!("native_window");
338 epi::set_value(
339 storage,
340 STORAGE_WINDOW_KEY,
341 &WindowSettings::from_window(self.egui_ctx.zoom_factor(), window),
342 );
343 }
344 }
345 if _app.persist_egui_memory() {
346 profiling::scope!("egui_memory");
347 self.egui_ctx
348 .memory(|mem| epi::set_value(storage, STORAGE_EGUI_MEMORY_KEY, mem));
349 }
350 {
351 profiling::scope!("App::save");
352 _app.save(storage);
353 }
354
355 profiling::scope!("Storage::flush");
356 storage.flush();
357 }
358 }
359}
360
361fn load_default_egui_icon() -> egui::IconData {
362 profiling::function_scope!();
363 crate::icon_data::from_png_bytes(&include_bytes!("../../data/icon.png")[..]).unwrap()
364}
365
366#[cfg(feature = "persistence")]
367const STORAGE_EGUI_MEMORY_KEY: &str = "egui";
368
369#[cfg(feature = "persistence")]
370const STORAGE_WINDOW_KEY: &str = "window";
371
372pub fn load_window_settings(_storage: Option<&dyn epi::Storage>) -> Option<WindowSettings> {
373 profiling::function_scope!();
374 #[cfg(feature = "persistence")]
375 {
376 epi::get_value(_storage?, STORAGE_WINDOW_KEY)
377 }
378 #[cfg(not(feature = "persistence"))]
379 None
380}
381
382pub fn load_egui_memory(_storage: Option<&dyn epi::Storage>) -> Option<egui::Memory> {
383 profiling::function_scope!();
384 #[cfg(feature = "persistence")]
385 {
386 epi::get_value(_storage?, STORAGE_EGUI_MEMORY_KEY)
387 }
388 #[cfg(not(feature = "persistence"))]
389 None
390}