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}