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 FnOnce(&egui::Context),
61    ) -> bool {
62        self.egui_context
63            .set_pixels_per_point(device_pixel_ratio as f32);
64        self.viewport = viewport;
65        let egui_input = egui::RawInput {
66            screen_rect: Some(egui::Rect {
67                min: egui::Pos2 {
68                    x: viewport.x as f32 / device_pixel_ratio as f32,
69                    y: viewport.y as f32 / device_pixel_ratio as f32,
70                },
71                max: egui::Pos2 {
72                    x: viewport.x as f32 / device_pixel_ratio as f32
73                        + viewport.width as f32 / device_pixel_ratio as f32,
74                    y: viewport.y as f32 / device_pixel_ratio as f32
75                        + viewport.height as f32 / device_pixel_ratio as f32,
76                },
77            }),
78            time: Some(accumulated_time_in_ms * 0.001),
79            modifiers: (&self.modifiers).into(),
80            events: events
81                .iter()
82                .filter_map(|event| match event {
83                    Event::KeyPress {
84                        kind,
85                        modifiers,
86                        handled,
87                    } => {
88                        if !handled {
89                            Some(egui::Event::Key {
90                                key: kind.into(),
91                                pressed: true,
92                                modifiers: modifiers.into(),
93                                repeat: false,
94                                physical_key: None,
95                            })
96                        } else {
97                            None
98                        }
99                    }
100                    Event::KeyRelease {
101                        kind,
102                        modifiers,
103                        handled,
104                    } => {
105                        if !handled {
106                            Some(egui::Event::Key {
107                                key: kind.into(),
108                                pressed: false,
109                                modifiers: modifiers.into(),
110                                repeat: false,
111                                physical_key: None,
112                            })
113                        } else {
114                            None
115                        }
116                    }
117                    Event::MousePress {
118                        button,
119                        position,
120                        modifiers,
121                        handled,
122                    } => {
123                        if !handled {
124                            Some(egui::Event::PointerButton {
125                                pos: egui::Pos2 {
126                                    x: position.x / device_pixel_ratio as f32,
127                                    y: (viewport.height as f32 - position.y)
128                                        / device_pixel_ratio as f32,
129                                },
130                                button: button.into(),
131                                pressed: true,
132                                modifiers: modifiers.into(),
133                            })
134                        } else {
135                            None
136                        }
137                    }
138                    Event::MouseRelease {
139                        button,
140                        position,
141                        modifiers,
142                        handled,
143                    } => {
144                        if !handled {
145                            Some(egui::Event::PointerButton {
146                                pos: egui::Pos2 {
147                                    x: position.x / device_pixel_ratio as f32,
148                                    y: (viewport.height as f32 - position.y)
149                                        / device_pixel_ratio as f32,
150                                },
151                                button: button.into(),
152                                pressed: false,
153                                modifiers: modifiers.into(),
154                            })
155                        } else {
156                            None
157                        }
158                    }
159                    Event::MouseMotion {
160                        position, handled, ..
161                    } => {
162                        if !handled {
163                            Some(egui::Event::PointerMoved(egui::Pos2 {
164                                x: position.x / device_pixel_ratio as f32,
165                                y: (viewport.height as f32 - position.y)
166                                    / device_pixel_ratio as f32,
167                            }))
168                        } else {
169                            None
170                        }
171                    }
172                    Event::Text(text) => Some(egui::Event::Text(text.clone())),
173                    Event::MouseLeave => Some(egui::Event::PointerGone),
174                    Event::MouseWheel {
175                        delta,
176                        handled,
177                        modifiers,
178                        ..
179                    } => {
180                        if !handled {
181                            Some(egui::Event::MouseWheel {
182                                delta: egui::Vec2::new(delta.0 as f32, delta.1 as f32),
183                                unit: egui::MouseWheelUnit::Point,
184                                modifiers: modifiers.into(),
185                            })
186                        } else {
187                            None
188                        }
189                    }
190                    Event::PinchGesture { delta, handled, .. } => {
191                        if !handled {
192                            Some(egui::Event::Zoom(delta.exp()))
193                        } else {
194                            None
195                        }
196                    }
197                    _ => None,
198                })
199                .collect::<Vec<_>>(),
200            ..Default::default()
201        };
202
203        self.egui_context.begin_pass(egui_input);
204        callback(&self.egui_context);
205        *self.output.borrow_mut() = Some(self.egui_context.end_pass());
206
207        for event in events.iter_mut() {
208            if let Event::ModifiersChange { modifiers } = event {
209                self.modifiers = *modifiers;
210            }
211            if self.egui_context.wants_pointer_input() {
212                match event {
213                    Event::MousePress {
214                        ref mut handled, ..
215                    } => {
216                        *handled = true;
217                    }
218                    Event::MouseRelease {
219                        ref mut handled, ..
220                    } => {
221                        *handled = true;
222                    }
223                    Event::MouseWheel {
224                        ref mut handled, ..
225                    } => {
226                        *handled = true;
227                    }
228                    Event::MouseMotion {
229                        ref mut handled, ..
230                    } => {
231                        *handled = true;
232                    }
233                    Event::PinchGesture {
234                        ref mut handled, ..
235                    } => {
236                        *handled = true;
237                    }
238                    Event::RotationGesture {
239                        ref mut handled, ..
240                    } => {
241                        *handled = true;
242                    }
243                    _ => {}
244                }
245            }
246
247            if self.egui_context.wants_keyboard_input() {
248                match event {
249                    Event::KeyRelease {
250                        ref mut handled, ..
251                    } => {
252                        *handled = true;
253                    }
254                    Event::KeyPress {
255                        ref mut handled, ..
256                    } => {
257                        *handled = true;
258                    }
259                    _ => {}
260                }
261            }
262        }
263        self.egui_context.wants_pointer_input() || self.egui_context.wants_keyboard_input()
264    }
265
266    ///
267    /// Render the GUI defined in the [update](Self::update) function.
268    /// Must be called in the callback given as input to a [RenderTarget], [ColorTarget] or [DepthTarget] write method.
269    ///
270    pub fn render(&self) -> Result<(), crate::CoreError> {
271        let output = self
272            .output
273            .borrow_mut()
274            .take()
275            .expect("need to call GUI::update before GUI::render");
276        let scale = self.egui_context.pixels_per_point();
277        let clipped_meshes = self.egui_context.tessellate(output.shapes, scale);
278        self.painter.borrow_mut().paint_and_update_textures(
279            [self.viewport.width, self.viewport.height],
280            scale,
281            &clipped_meshes,
282            &output.textures_delta,
283        );
284        #[cfg(not(target_arch = "wasm32"))]
285        #[allow(unsafe_code)]
286        unsafe {
287            use glow::HasContext as _;
288            self.painter.borrow().gl().disable(glow::FRAMEBUFFER_SRGB);
289        }
290        Ok(())
291    }
292}
293
294impl Drop for GUI {
295    fn drop(&mut self) {
296        self.painter.borrow_mut().destroy();
297    }
298}
299
300impl From<&Key> for egui::Key {
301    fn from(key: &Key) -> Self {
302        use crate::control::Key::*;
303        use egui::Key;
304        match key {
305            ArrowDown => Key::ArrowDown,
306            ArrowLeft => Key::ArrowLeft,
307            ArrowRight => Key::ArrowRight,
308            ArrowUp => Key::ArrowUp,
309            Escape => Key::Escape,
310            Tab => Key::Tab,
311            Backspace => Key::Backspace,
312            Enter => Key::Enter,
313            Space => Key::Space,
314            Insert => Key::Insert,
315            Delete => Key::Delete,
316            Home => Key::Home,
317            End => Key::End,
318            PageUp => Key::PageUp,
319            PageDown => Key::PageDown,
320            Num0 => Key::Num0,
321            Num1 => Key::Num1,
322            Num2 => Key::Num2,
323            Num3 => Key::Num3,
324            Num4 => Key::Num4,
325            Num5 => Key::Num5,
326            Num6 => Key::Num6,
327            Num7 => Key::Num7,
328            Num8 => Key::Num8,
329            Num9 => Key::Num9,
330            A => Key::A,
331            B => Key::B,
332            C => Key::C,
333            D => Key::D,
334            E => Key::E,
335            F => Key::F,
336            G => Key::G,
337            H => Key::H,
338            I => Key::I,
339            J => Key::J,
340            K => Key::K,
341            L => Key::L,
342            M => Key::M,
343            N => Key::N,
344            O => Key::O,
345            P => Key::P,
346            Q => Key::Q,
347            R => Key::R,
348            S => Key::S,
349            T => Key::T,
350            U => Key::U,
351            V => Key::V,
352            W => Key::W,
353            X => Key::X,
354            Y => Key::Y,
355            Z => Key::Z,
356        }
357    }
358}
359
360impl From<&Modifiers> for egui::Modifiers {
361    fn from(modifiers: &Modifiers) -> Self {
362        Self {
363            alt: modifiers.alt,
364            ctrl: modifiers.ctrl,
365            shift: modifiers.shift,
366            command: modifiers.command,
367            mac_cmd: cfg!(target_os = "macos") && modifiers.command,
368        }
369    }
370}
371
372impl From<&MouseButton> for egui::PointerButton {
373    fn from(button: &MouseButton) -> Self {
374        match button {
375            MouseButton::Left => egui::PointerButton::Primary,
376            MouseButton::Right => egui::PointerButton::Secondary,
377            MouseButton::Middle => egui::PointerButton::Middle,
378        }
379    }
380}