egui_ash/
run.rs

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
21/// egui-ash run option.
22pub struct RunOption {
23    /// window clear color.
24    pub clear_color: [f32; 4],
25    /// viewport builder for root window.
26    pub viewport_builder: Option<egui::ViewportBuilder>,
27    /// follow system theme.
28    pub follow_system_theme: bool,
29    /// default theme.
30    pub default_theme: Theme,
31    #[cfg(feature = "persistence")]
32    pub persistent_windows: bool,
33    #[cfg(feature = "persistence")]
34    pub persistent_egui_memory: bool,
35    /// vk::PresentModeKHR
36    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/// exit signal sender for exit app.
55#[derive(Debug, Clone)]
56pub struct ExitSignal {
57    tx: std::sync::mpsc::Sender<ExitCode>,
58}
59impl ExitSignal {
60    /// send exit signal.
61    pub fn send(&self, exit_code: ExitCode) {
62        self.tx.send(exit_code).unwrap();
63    }
64}
65
66///egui-ash run function.
67///
68/// ```
69/// fn main() {
70///     egui_winit_ash::run("my_app", MyAppCreator, RunOption::default());
71/// }
72/// ```
73pub 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)] // only mutable when persistence feature is enabled
108    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    // ManuallyDrop is required because the integration object needs to be dropped before
168    // the app drops for gpu_allocator drop order reasons.
169    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)] // only used when accesskit feature is enabled
229                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}