1use crate::control::*;
2use crate::core::*;
3use egui_glow::Painter;
4use std::cell::RefCell;
5
6#[doc(hidden)]
7pub use egui;
8
9pub struct GUI {
13 painter: RefCell<Painter>,
14 egui_context: egui::Context,
15 output: RefCell<Option<egui::FullOutput>>,
16 viewport: Viewport,
17 modifiers: Modifiers,
18}
19
20impl GUI {
21 pub fn new(context: &Context) -> Self {
25 use std::ops::Deref;
26 Self::from_gl_context(context.deref().clone())
27 }
28
29 pub fn from_gl_context(context: std::sync::Arc<crate::context::Context>) -> Self {
33 GUI {
34 egui_context: egui::Context::default(),
35 painter: RefCell::new(Painter::new(context, "", None, true).unwrap()),
36 output: RefCell::new(None),
37 viewport: Viewport::new_at_origo(1, 1),
38 modifiers: Modifiers::default(),
39 }
40 }
41
42 pub fn context(&self) -> &egui::Context {
46 &self.egui_context
47 }
48
49 pub fn update(
55 &mut self,
56 events: &mut [Event],
57 accumulated_time_in_ms: f64,
58 viewport: Viewport,
59 device_pixel_ratio: f32,
60 callback: impl FnMut(&mut egui::Ui),
61 ) -> bool {
62 self.egui_context.set_pixels_per_point(device_pixel_ratio);
63 self.viewport = viewport;
64 let egui_input = egui::RawInput {
65 screen_rect: Some(egui::Rect {
66 min: egui::Pos2 {
67 x: viewport.x as f32 / device_pixel_ratio,
68 y: viewport.y as f32 / device_pixel_ratio,
69 },
70 max: egui::Pos2 {
71 x: viewport.x as f32 / device_pixel_ratio
72 + viewport.width as f32 / device_pixel_ratio,
73 y: viewport.y as f32 / device_pixel_ratio
74 + viewport.height as f32 / device_pixel_ratio,
75 },
76 }),
77 time: Some(accumulated_time_in_ms * 0.001),
78 modifiers: (&self.modifiers).into(),
79 events: events
80 .iter()
81 .filter_map(|event| match event {
82 Event::KeyPress {
83 kind,
84 modifiers,
85 handled,
86 } => {
87 if !handled {
88 translate_key(kind).map(|key| egui::Event::Key {
89 key,
90 pressed: true,
91 modifiers: modifiers.into(),
92 repeat: false,
93 physical_key: None,
94 })
95 } else {
96 None
97 }
98 }
99 Event::KeyRelease {
100 kind,
101 modifiers,
102 handled,
103 } => {
104 if !handled {
105 translate_key(kind).map(|key| egui::Event::Key {
106 key,
107 pressed: false,
108 modifiers: modifiers.into(),
109 repeat: false,
110 physical_key: None,
111 })
112 } else {
113 None
114 }
115 }
116 Event::MousePress {
117 button,
118 position,
119 modifiers,
120 handled,
121 } => {
122 if !handled {
123 Some(egui::Event::PointerButton {
124 pos: egui::Pos2 {
125 x: position.x / device_pixel_ratio,
126 y: (viewport.height as f32 - position.y) / device_pixel_ratio,
127 },
128 button: button.into(),
129 pressed: true,
130 modifiers: modifiers.into(),
131 })
132 } else {
133 None
134 }
135 }
136 Event::MouseRelease {
137 button,
138 position,
139 modifiers,
140 handled,
141 } => {
142 if !handled {
143 Some(egui::Event::PointerButton {
144 pos: egui::Pos2 {
145 x: position.x / device_pixel_ratio,
146 y: (viewport.height as f32 - position.y) / device_pixel_ratio,
147 },
148 button: button.into(),
149 pressed: false,
150 modifiers: modifiers.into(),
151 })
152 } else {
153 None
154 }
155 }
156 Event::MouseMotion {
157 position, handled, ..
158 } => {
159 if !handled {
160 Some(egui::Event::PointerMoved(egui::Pos2 {
161 x: position.x / device_pixel_ratio,
162 y: (viewport.height as f32 - position.y) / device_pixel_ratio,
163 }))
164 } else {
165 None
166 }
167 }
168 Event::Text(text) => Some(egui::Event::Text(text.clone())),
169 Event::MouseLeave => Some(egui::Event::PointerGone),
170 Event::MouseWheel {
171 delta,
172 handled,
173 modifiers,
174 ..
175 } => {
176 if !handled {
177 Some(egui::Event::MouseWheel {
178 delta: egui::Vec2::new(delta.0, delta.1),
179 unit: egui::MouseWheelUnit::Point,
180 modifiers: modifiers.into(),
181 phase: egui::TouchPhase::Move,
182 })
183 } else {
184 None
185 }
186 }
187 Event::PinchGesture { delta, handled, .. } => {
188 if !handled {
189 Some(egui::Event::Zoom(delta.exp()))
190 } else {
191 None
192 }
193 }
194 _ => None,
195 })
196 .collect::<Vec<_>>(),
197 ..Default::default()
198 };
199
200 *self.output.borrow_mut() = Some(self.egui_context.run_ui(egui_input, callback));
201
202 for event in events.iter_mut() {
203 if let Event::ModifiersChange { modifiers } = event {
204 self.modifiers = *modifiers;
205 }
206 if self.egui_context.egui_is_using_pointer() {
207 match event {
208 Event::MousePress {
209 ref mut handled, ..
210 } => {
211 *handled = true;
212 }
213 Event::MouseRelease {
214 ref mut handled, ..
215 } => {
216 *handled = true;
217 }
218 Event::MouseWheel {
219 ref mut handled, ..
220 } => {
221 *handled = true;
222 }
223 Event::MouseMotion {
224 ref mut handled, ..
225 } => {
226 *handled = true;
227 }
228 Event::PinchGesture {
229 ref mut handled, ..
230 } => {
231 *handled = true;
232 }
233 Event::RotationGesture {
234 ref mut handled, ..
235 } => {
236 *handled = true;
237 }
238 _ => {}
239 }
240 }
241
242 if self.egui_context.egui_wants_keyboard_input() {
243 match event {
244 Event::KeyRelease {
245 ref mut handled, ..
246 } => {
247 *handled = true;
248 }
249 Event::KeyPress {
250 ref mut handled, ..
251 } => {
252 *handled = true;
253 }
254 _ => {}
255 }
256 }
257 }
258 self.egui_context.egui_wants_pointer_input()
259 || self.egui_context.egui_wants_keyboard_input()
260 }
261
262 pub fn render(&self) -> Result<(), crate::CoreError> {
267 let output = self
268 .output
269 .borrow_mut()
270 .take()
271 .expect("need to call GUI::update before GUI::render");
272 let scale = self.egui_context.pixels_per_point();
273 let clipped_meshes = self.egui_context.tessellate(output.shapes, scale);
274 self.painter.borrow_mut().paint_and_update_textures(
275 [self.viewport.width, self.viewport.height],
276 scale,
277 &clipped_meshes,
278 &output.textures_delta,
279 );
280 #[cfg(not(target_arch = "wasm32"))]
281 #[allow(unsafe_code)]
282 unsafe {
283 use glow::HasContext as _;
284 self.painter.borrow().gl().disable(glow::FRAMEBUFFER_SRGB);
285 }
286 Ok(())
287 }
288}
289
290impl Drop for GUI {
291 fn drop(&mut self) {
292 self.painter.borrow_mut().destroy();
293 }
294}
295
296fn translate_key(key: &Key) -> Option<egui::Key> {
297 use crate::control::Key as InputKey;
298 use egui::Key;
299
300 Some(match key {
301 InputKey::ArrowDown => Key::ArrowDown,
302 InputKey::ArrowLeft => Key::ArrowLeft,
303 InputKey::ArrowRight => Key::ArrowRight,
304 InputKey::ArrowUp => Key::ArrowUp,
305 InputKey::Escape => Key::Escape,
306 InputKey::Tab => Key::Tab,
307 InputKey::Backspace => Key::Backspace,
308 InputKey::Enter => Key::Enter,
309 InputKey::Space => Key::Space,
310 InputKey::Insert => Key::Insert,
311 InputKey::Delete => Key::Delete,
312 InputKey::Home => Key::Home,
313 InputKey::End => Key::End,
314 InputKey::PageUp => Key::PageUp,
315 InputKey::PageDown => Key::PageDown,
316 InputKey::Copy => Key::Copy,
317 InputKey::Paste => Key::Paste,
318 InputKey::Cut => Key::Cut,
319 InputKey::Num0 => Key::Num0,
320 InputKey::Num1 => Key::Num1,
321 InputKey::Num2 => Key::Num2,
322 InputKey::Num3 => Key::Num3,
323 InputKey::Num4 => Key::Num4,
324 InputKey::Num5 => Key::Num5,
325 InputKey::Num6 => Key::Num6,
326 InputKey::Num7 => Key::Num7,
327 InputKey::Num8 => Key::Num8,
328 InputKey::Num9 => Key::Num9,
329 InputKey::A => Key::A,
330 InputKey::B => Key::B,
331 InputKey::C => Key::C,
332 InputKey::D => Key::D,
333 InputKey::E => Key::E,
334 InputKey::F => Key::F,
335 InputKey::G => Key::G,
336 InputKey::H => Key::H,
337 InputKey::I => Key::I,
338 InputKey::J => Key::J,
339 InputKey::K => Key::K,
340 InputKey::L => Key::L,
341 InputKey::M => Key::M,
342 InputKey::N => Key::N,
343 InputKey::O => Key::O,
344 InputKey::P => Key::P,
345 InputKey::Q => Key::Q,
346 InputKey::R => Key::R,
347 InputKey::S => Key::S,
348 InputKey::T => Key::T,
349 InputKey::U => Key::U,
350 InputKey::V => Key::V,
351 InputKey::W => Key::W,
352 InputKey::X => Key::X,
353 InputKey::Y => Key::Y,
354 InputKey::Z => Key::Z,
355 InputKey::F1 => Key::F1,
356 InputKey::F2 => Key::F2,
357 InputKey::F3 => Key::F3,
358 InputKey::F4 => Key::F4,
359 InputKey::F5 => Key::F5,
360 InputKey::F6 => Key::F6,
361 InputKey::F7 => Key::F7,
362 InputKey::F8 => Key::F8,
363 InputKey::F9 => Key::F9,
364 InputKey::F10 => Key::F10,
365 InputKey::F11 => Key::F11,
366 InputKey::F12 => Key::F12,
367 InputKey::F13 => Key::F13,
368 InputKey::F14 => Key::F14,
369 InputKey::F15 => Key::F15,
370 InputKey::F16 => Key::F16,
371 InputKey::F17 => Key::F17,
372 InputKey::F18 => Key::F18,
373 InputKey::F19 => Key::F19,
374 InputKey::F20 => Key::F20,
375 InputKey::F21 => Key::F21,
376 InputKey::F22 => Key::F22,
377 InputKey::F23 => Key::F23,
378 InputKey::F24 => Key::F24,
379 InputKey::Apostrophe => Key::Quote,
380 InputKey::Backslash => Key::Backslash,
381 InputKey::Colon => Key::Colon,
382 InputKey::Comma => Key::Comma,
383 InputKey::Equals => Key::Equals,
384 InputKey::Grave => Key::Backtick,
385 InputKey::LBracket => Key::OpenBracket,
386 InputKey::Minus => Key::Minus,
387 InputKey::Period => Key::Period,
388 InputKey::Plus => Key::Plus,
389 InputKey::RBracket => Key::CloseBracket,
390 InputKey::Semicolon => Key::Semicolon,
391 InputKey::Slash => Key::Slash,
392 _ => return None,
393 })
394}
395
396impl From<&Modifiers> for egui::Modifiers {
397 fn from(modifiers: &Modifiers) -> Self {
398 Self {
399 alt: modifiers.alt,
400 ctrl: modifiers.ctrl,
401 shift: modifiers.shift,
402 command: modifiers.command,
403 mac_cmd: cfg!(target_os = "macos") && modifiers.command,
404 }
405 }
406}
407
408impl From<&MouseButton> for egui::PointerButton {
409 fn from(button: &MouseButton) -> Self {
410 match button {
411 MouseButton::Left => egui::PointerButton::Primary,
412 MouseButton::Right => egui::PointerButton::Secondary,
413 MouseButton::Middle => egui::PointerButton::Middle,
414 }
415 }
416}