1use ash::extensions::khr::Swapchain;
2use egui_winit::winit::{self, event_loop::EventLoopBuilder};
3use raw_window_handle::HasRawDisplayHandle;
4use std::{
5 ffi::CStr,
6 mem::ManuallyDrop,
7 process::ExitCode,
8 sync::{Arc, Mutex},
9};
10
11use crate::{
12 app::{App, AppCreator, CreationContext},
13 event,
14 integration::{Integration, IntegrationEvent},
15 renderer::ImageRegistry,
16 Allocator, Theme,
17};
18#[cfg(feature = "persistence")]
19use crate::{storage, utils};
20
21pub struct RunOption {
23 pub clear_color: [f32; 4],
25 pub viewport_builder: Option<egui::ViewportBuilder>,
27 pub follow_system_theme: bool,
29 pub default_theme: Theme,
31 #[cfg(feature = "persistence")]
32 pub persistent_windows: bool,
33 #[cfg(feature = "persistence")]
34 pub persistent_egui_memory: bool,
35 pub present_mode: ash::vk::PresentModeKHR,
37}
38impl Default for RunOption {
39 fn default() -> Self {
40 Self {
41 clear_color: [0.0, 0.0, 0.0, 1.0],
42 viewport_builder: None,
43 follow_system_theme: true,
44 default_theme: Theme::Light,
45 #[cfg(feature = "persistence")]
46 persistent_windows: true,
47 #[cfg(feature = "persistence")]
48 persistent_egui_memory: true,
49 present_mode: ash::vk::PresentModeKHR::FIFO,
50 }
51 }
52}
53
54#[derive(Debug, Clone)]
56pub struct ExitSignal {
57 tx: std::sync::mpsc::Sender<ExitCode>,
58}
59impl ExitSignal {
60 pub fn send(&self, exit_code: ExitCode) {
62 self.tx.send(exit_code).unwrap();
63 }
64}
65
66pub fn run<C: AppCreator<A> + 'static, A: Allocator + 'static>(
74 app_id: impl Into<String>,
75 creator: C,
76 run_option: RunOption,
77) -> ExitCode {
78 let app_id = app_id.into();
79
80 let device_extensions = [Swapchain::name().to_owned()];
81
82 let event_loop = EventLoopBuilder::<IntegrationEvent>::with_user_event()
83 .build()
84 .expect("Failed to create event loop");
85
86 #[cfg(feature = "persistence")]
87 let storage = storage::Storage::from_app_id(&app_id).expect("Failed to create storage");
88
89 let context = egui::Context::default();
90 #[cfg(feature = "persistence")]
91 if run_option.persistent_egui_memory {
92 if let Some(memory) = storage.get_egui_memory() {
93 context.memory_mut(|m| *m = memory);
94 }
95 }
96
97 context.set_embed_viewports(false);
98 match run_option.default_theme {
99 Theme::Light => {
100 context.set_visuals(egui::Visuals::light());
101 }
102 Theme::Dark => {
103 context.set_visuals(egui::Visuals::dark());
104 }
105 }
106
107 #[allow(unused_mut)] let main_window = if let Some(mut viewport_builder) = run_option.viewport_builder {
109 #[cfg(feature = "persistence")]
110 if run_option.persistent_windows {
111 let window_settings = storage
112 .get_windows()
113 .and_then(|windows| windows.get(&egui::ViewportId::ROOT).map(|s| s.to_owned()))
114 .map(|mut settings| {
115 let egui_zoom_factor = context.zoom_factor();
116 settings.clamp_size_to_sane_values(utils::largest_monitor_point_size(
117 egui_zoom_factor,
118 &event_loop,
119 ));
120 settings.clamp_position_to_monitors(egui_zoom_factor, &event_loop);
121 settings.to_owned()
122 });
123
124 if let Some(window_settings) = window_settings {
125 viewport_builder = window_settings.initialize_viewport_builder(viewport_builder);
126 }
127 }
128
129 egui_winit::create_winit_window_builder(
130 &context,
131 &event_loop,
132 viewport_builder.with_visible(false),
133 )
134 .with_visible(false)
135 .build(&event_loop)
136 .unwrap()
137 } else {
138 winit::window::WindowBuilder::new()
139 .with_title("egui-ash")
140 .with_visible(false)
141 .build(&event_loop)
142 .unwrap()
143 };
144
145 let instance_extensions =
146 ash_window::enumerate_required_extensions(event_loop.raw_display_handle()).unwrap();
147 let instance_extensions = instance_extensions
148 .into_iter()
149 .map(|&ext| unsafe { CStr::from_ptr(ext).to_owned() })
150 .collect::<Vec<_>>();
151
152 let (image_registry, image_registry_receiver) = ImageRegistry::new();
153
154 let (exit_signal_tx, exit_signal_rx) = std::sync::mpsc::channel();
155 let exit_signal = ExitSignal { tx: exit_signal_tx };
156
157 let cc = CreationContext {
158 main_window: &main_window,
159 context: context.clone(),
160 required_instance_extensions: instance_extensions,
161 required_device_extensions: device_extensions.into_iter().collect(),
162 image_registry,
163 exit_signal,
164 };
165 let (mut app, render_state) = creator.create(cc);
166
167 let mut integration = ManuallyDrop::new(Integration::new(
170 &app_id,
171 &event_loop,
172 context,
173 main_window,
174 render_state,
175 run_option.clear_color,
176 run_option.present_mode,
177 image_registry_receiver,
178 #[cfg(feature = "persistence")]
179 storage,
180 #[cfg(feature = "persistence")]
181 run_option.persistent_windows,
182 #[cfg(feature = "persistence")]
183 run_option.persistent_egui_memory,
184 ));
185
186 let exit_code = Arc::new(Mutex::new(ExitCode::SUCCESS));
187 let exit_code_clone = exit_code.clone();
188 event_loop
189 .run(move |event, event_loop| {
190 event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
191 if let Some(code) = exit_signal_rx.try_recv().ok() {
192 *exit_code_clone.lock().unwrap() = code;
193 event_loop.exit();
194 return;
195 }
196 match event {
197 winit::event::Event::NewEvents(start_cause) => {
198 let app_event = event::Event::AppEvent {
199 event: event::AppEvent::NewEvents(start_cause),
200 };
201 app.handle_event(app_event);
202 }
203 winit::event::Event::WindowEvent {
204 event, window_id, ..
205 } => {
206 let consumed = integration.handle_window_event(
207 window_id,
208 &event,
209 &event_loop,
210 run_option.follow_system_theme,
211 &mut app,
212 );
213 if consumed {
214 return;
215 }
216
217 let Some(viewport_id) = integration.viewport_id_from_window_id(window_id)
218 else {
219 return;
220 };
221 let viewport_event = event::Event::ViewportEvent { viewport_id, event };
222 app.handle_event(viewport_event);
223 }
224 winit::event::Event::DeviceEvent { device_id, event } => {
225 let device_event = event::Event::DeviceEvent { device_id, event };
226 app.handle_event(device_event);
227 }
228 #[allow(unused_variables)] winit::event::Event::UserEvent(integration_event) => {
230 #[cfg(feature = "accesskit")]
231 {
232 integration.handle_accesskit_event(
233 &integration_event.accesskit,
234 event_loop,
235 control_flow,
236 &mut app,
237 );
238 let user_event =
239 event::Event::AccessKitActionRequest(integration_event.accesskit);
240 app.handle_event(user_event);
241 }
242 }
243 winit::event::Event::Suspended => {
244 let app_event = event::Event::AppEvent {
245 event: event::AppEvent::Suspended,
246 };
247 app.handle_event(app_event);
248 }
249 winit::event::Event::Resumed => {
250 let app_event = event::Event::AppEvent {
251 event: event::AppEvent::Resumed,
252 };
253 app.handle_event(app_event);
254 integration.paint_all(event_loop, &mut app);
255 }
256 winit::event::Event::AboutToWait => {
257 let app_event = event::Event::AppEvent {
258 event: event::AppEvent::AboutToWait,
259 };
260 app.handle_event(app_event);
261 integration.paint_all(event_loop, &mut app);
262 }
263 winit::event::Event::MemoryWarning => {
264 let app_event = event::Event::AppEvent {
265 event: event::AppEvent::MemoryWarning,
266 };
267 app.handle_event(app_event);
268 }
269 winit::event::Event::LoopExiting => {
270 let app_event = event::Event::AppEvent {
271 event: event::AppEvent::LoopExiting,
272 };
273 app.handle_event(app_event);
274 #[cfg(feature = "persistence")]
275 integration.save(&mut app);
276 integration.destroy();
277 unsafe {
278 ManuallyDrop::drop(&mut integration);
279 }
280 }
281 }
282 })
283 .expect("Failed to run event loop");
284 let code = exit_code.lock().unwrap();
285 code.clone()
286}