Skip to main content

workflow_egui/frame/
core.rs

1use crate::frame::app::App;
2use crate::imports::*;
3
4/// The eframe application wrapper that drives an [`App`], bridging eframe's
5/// update loop with runtime event dispatch, keyboard handling, and rendering.
6pub struct Core<T>
7where
8    T: App,
9{
10    app: Box<T>,
11
12    is_shutdown_pending: bool,
13    _settings_storage_requested: bool,
14    _last_settings_storage_request: Instant,
15
16    #[allow(dead_code)]
17    runtime: Runtime,
18    events: ApplicationEventsChannel,
19}
20
21impl<T> Core<T>
22where
23    T: App,
24{
25    /// Core initialization
26    pub fn try_new(
27        cc: &eframe::CreationContext<'_>,
28        runtime: Runtime,
29        app_creator: crate::frame::AppCreator<T>,
30    ) -> Result<Self> {
31        let mut app = app_creator(cc, runtime.clone())?;
32        app.init(&runtime, cc);
33
34        let events = runtime.events().clone();
35
36        Ok(Self {
37            runtime,
38            app,
39            is_shutdown_pending: false,
40            _settings_storage_requested: false,
41            _last_settings_storage_request: Instant::now(),
42            events,
43        })
44    }
45}
46
47impl<T> eframe::App for Core<T>
48where
49    T: App,
50{
51    #[cfg(not(target_arch = "wasm32"))]
52    fn on_exit(&mut self, _gl: Option<&eframe::glow::Context>) {
53        self.is_shutdown_pending = true;
54        Runtime::halt();
55        println!("bye!");
56    }
57
58    fn clear_color(&self, _visuals: &egui::Visuals) -> [f32; 4] {
59        egui::Rgba::TRANSPARENT.to_array()
60    }
61
62    /// Called each time the UI needs repainting, which may be many times per second.
63    /// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
64    //
65    // eframe 0.32+ made `ui(&mut self, &mut Ui, ...)` the required `App` method
66    // and deprecated `update(&mut self, &Context, ...)`. We recover the `Context`
67    // from the root `Ui` so the existing ctx-based rendering keeps working.
68    fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
69        let ctx = &ui.ctx().clone();
70        log_info!("--- update ---");
71
72        for event in self.events.iter() {
73            if let Err(err) = self.handle_events(event.clone(), ctx, frame) {
74                log_error!("error processing wallet runtime event: {}", err);
75            }
76        }
77
78        if self.is_shutdown_pending {
79            return;
80        }
81
82        ctx.input(|input| {
83            input.events.iter().for_each(|event| {
84                if let egui::Event::Key {
85                    key,
86                    pressed,
87                    modifiers,
88                    repeat,
89                    // TODO - propagate
90                    physical_key: _,
91                    // ..
92                } = event
93                {
94                    self.handle_keyboard_events(*key, *pressed, modifiers, *repeat);
95                }
96            });
97        });
98
99        if let Some(device) = self.app.device() {
100            device.set_screen_size(&ctx.content_rect())
101        }
102
103        self.render(ctx, frame);
104
105        // #[cfg(not(target_arch = "wasm32"))]
106        // if let Some(screenshot) = self.screenshot.clone() {
107        //     self.handle_screenshot(ctx, screenshot);
108        // }
109    }
110}
111
112impl<T> Core<T>
113where
114    T: App,
115{
116    fn render(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
117        self.app.render(ctx, frame);
118    }
119
120    // #[cfg(not(target_arch = "wasm32"))]
121    // fn handle_screenshot(&mut self, ctx: &Context, screenshot: Arc<ColorImage>) {
122    //     match rfd::FileDialog::new().save_file() {
123    //         Some(mut path) => {
124    //             path.set_extension("png");
125    //             let screen_rect = ctx.screen_rect();
126    //             let pixels_per_point = ctx.pixels_per_point();
127    //             let screenshot = screenshot.clone();
128    //             let sender = self.sender();
129    //             std::thread::Builder::new()
130    //                 .name("screenshot".to_string())
131    //                 .spawn(move || {
132    //                     let image = screenshot.region(&screen_rect, Some(pixels_per_point));
133    //                     image::save_buffer(
134    //                         &path,
135    //                         image.as_raw(),
136    //                         image.width() as u32,
137    //                         image.height() as u32,
138    //                         image::ColorType::Rgba8,
139    //                     )
140    //                     .unwrap();
141
142    //                     sender
143    //                         .try_send(Events::Notify {
144    //                             user_notification: UserNotification::success(format!(
145    //                                 "Capture saved to\n{}",
146    //                                 path.to_string_lossy()
147    //                             ))
148    //                             .as_toast(),
149    //                         })
150    //                         .unwrap()
151    //                 })
152    //                 .expect("Unable to spawn screenshot thread");
153    //             self.screenshot.take();
154    //         }
155    //         None => {
156    //             self.screenshot.take();
157    //         }
158    //     }
159    // }
160
161    /// Processes a single runtime event, marking shutdown as pending on
162    /// [`RuntimeEvent::Exit`] and forwarding the event to the application.
163    pub fn handle_events(
164        &mut self,
165        event: RuntimeEvent,
166        ctx: &egui::Context,
167        _frame: &mut eframe::Frame,
168    ) -> Result<()> {
169        // log_info!("--- event: {:?}", event);
170        if matches!(event, RuntimeEvent::Exit) {
171            self.is_shutdown_pending = true;
172        }
173
174        self.app.handle_event(ctx, event);
175
176        Ok(())
177    }
178
179    fn handle_keyboard_events(
180        &mut self,
181        key: egui::Key,
182        pressed: bool,
183        modifiers: &egui::Modifiers,
184        _repeat: bool,
185    ) {
186        self.app
187            .handle_keyboard_events(key, pressed, modifiers, false);
188    }
189
190    // pub fn apply_mobile_style(&self, ui: &mut Ui) {
191    //     ui.style_mut().text_styles = self.mobile_style.text_styles.clone();
192    // }
193
194    // pub fn apply_default_style(&self, ui: &mut Ui) {
195    //     ui.style_mut().text_styles = self.default_style.text_styles.clone();
196    // }
197}