Skip to main content

three_d/gui/
egui_gui.rs

1use crate::control::*;
2use crate::core::*;
3use egui_glow::Painter;
4use std::cell::RefCell;
5
6#[doc(hidden)]
7pub use egui;
8
9///
10/// Integration of [egui](https://crates.io/crates/egui), an immediate mode GUI.
11///
12pub 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    ///
22    /// Creates a new GUI from a mid-level [Context].
23    ///
24    pub fn new(context: &Context) -> Self {
25        use std::ops::Deref;
26        Self::from_gl_context(context.deref().clone())
27    }
28
29    ///
30    /// Creates a new GUI from a low-level graphics [Context](crate::context::Context).
31    ///
32    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    ///
43    /// Get the egui context.
44    ///
45    pub fn context(&self) -> &egui::Context {
46        &self.egui_context
47    }
48
49    ///
50    /// Initialises a new frame of the GUI and handles events.
51    /// Construct the GUI (Add panels, widgets etc.) using the [egui::Context] in the callback function.
52    /// This function returns whether or not the GUI has changed, ie. if it consumes any events, and therefore needs to be rendered again.
53    ///
54    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    ///
263    /// Render the GUI defined in the [update](Self::update) function.
264    /// Must be called in the callback given as input to a [RenderTarget], [ColorTarget] or [DepthTarget] write method.
265    ///
266    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}