Skip to main content

egui_gl_glfw/
lib.rs

1#![warn(clippy::all)]
2
3// Re-export dependencies.
4pub use egui;
5pub use gl;
6pub use glfw;
7
8pub mod painter;
9
10pub use painter::Painter;
11
12use egui::*;
13
14#[cfg(not(feature = "clipboard"))]
15mod clipboard;
16
17#[cfg(not(feature = "clipboard"))]
18use clipboard::{ClipboardContext, ClipboardProvider};
19
20#[cfg(feature = "clipboard")]
21use clipboard::Clipboard as ClipboardContext;
22
23pub struct EguiInputState {
24    pub pointer_pos: Pos2,
25    pub clipboard: Option<ClipboardContext>,
26    pub input: RawInput,
27    pub modifiers: Modifiers,
28    pub pixels_per_point: f32,
29}
30
31impl EguiInputState {
32    pub fn new(input: RawInput, pixels_per_point: f32) -> Self {
33        EguiInputState {
34            pointer_pos: Pos2::new(0f32, 0f32),
35            clipboard: init_clipboard(),
36            input,
37            modifiers: Modifiers::default(),
38            pixels_per_point,
39        }
40    }
41}
42
43pub fn handle_event(event: glfw::WindowEvent, state: &mut EguiInputState) {
44    use glfw::WindowEvent::*;
45
46    match event {
47        FramebufferSize(width, height) => {
48            state.input.screen_rect = Some(Rect::from_min_size(
49                Pos2::new(0f32, 0f32),
50                egui::vec2(width as f32, height as f32) / state.pixels_per_point,
51            ));
52        }
53
54        MouseButton(mouse_btn, glfw::Action::Press, _) => {
55            state.input.events.push(egui::Event::PointerButton {
56                pos: state.pointer_pos,
57                button: match mouse_btn {
58                    glfw::MouseButtonLeft => egui::PointerButton::Primary,
59                    glfw::MouseButtonRight => egui::PointerButton::Secondary,
60                    glfw::MouseButtonMiddle => egui::PointerButton::Middle,
61                    glfw::MouseButton::Button4 => egui::PointerButton::Extra2,
62                    glfw::MouseButton::Button5 => egui::PointerButton::Extra1,
63                    _ => egui::PointerButton::Primary,
64                },
65                pressed: true,
66                modifiers: state.modifiers,
67            })
68        }
69
70        MouseButton(mouse_btn, glfw::Action::Release, _) => {
71            state.input.events.push(egui::Event::PointerButton {
72                pos: state.pointer_pos,
73                button: match mouse_btn {
74                    glfw::MouseButtonLeft => egui::PointerButton::Primary,
75                    glfw::MouseButtonRight => egui::PointerButton::Secondary,
76                    glfw::MouseButtonMiddle => egui::PointerButton::Middle,
77                    glfw::MouseButton::Button4 => egui::PointerButton::Extra2,
78                    glfw::MouseButton::Button5 => egui::PointerButton::Extra1,
79                    _ => egui::PointerButton::Primary,
80                },
81                pressed: false,
82                modifiers: state.modifiers,
83            })
84        }
85
86        CursorPos(x, y) => {
87            state.pointer_pos = pos2(
88                x as f32 / state.pixels_per_point,
89                y as f32 / state.pixels_per_point,
90            );
91            state
92                .input
93                .events
94                .push(egui::Event::PointerMoved(state.pointer_pos))
95        }
96
97        Key(keycode, _scancode, glfw::Action::Release, keymod) => {
98            use glfw::Modifiers as Mod;
99            if let Some(key) = translate_virtual_key_code(keycode) {
100                state.modifiers = Modifiers {
101                    alt: (keymod & Mod::Alt == Mod::Alt),
102                    ctrl: (keymod & Mod::Control == Mod::Control),
103                    shift: (keymod & Mod::Shift == Mod::Shift),
104
105                    // TODO: GLFW doesn't seem to support the mac command key
106                    //       mac_cmd: keymod & Mod::LGUIMOD == Mod::LGUIMOD,
107                    command: (keymod & Mod::Control == Mod::Control),
108
109                    ..Default::default()
110                };
111
112                state.input.events.push(Event::Key {
113                    key,
114                    physical_key: None,
115                    pressed: false,
116                    modifiers: state.modifiers,
117                    repeat: false,
118                });
119            }
120        }
121
122        Key(keycode, _scancode, glfw::Action::Press, keymod) => {
123            use glfw::Modifiers as Mod;
124            if let Some(key) = translate_virtual_key_code(keycode) {
125                state.modifiers = Modifiers {
126                    alt: (keymod & Mod::Alt == Mod::Alt),
127                    ctrl: (keymod & Mod::Control == Mod::Control),
128                    shift: (keymod & Mod::Shift == Mod::Shift),
129
130                    // TODO: GLFW doesn't seem to support the mac command key
131                    //       mac_cmd: keymod & Mod::LGUIMOD == Mod::LGUIMOD,
132                    command: (keymod & Mod::Control == Mod::Control),
133
134                    ..Default::default()
135                };
136
137                if state.modifiers.command && key == egui::Key::X {
138                    state.input.events.push(egui::Event::Cut);
139                } else if state.modifiers.command && key == egui::Key::C {
140                    state.input.events.push(egui::Event::Copy);
141                } else if state.modifiers.command && key == egui::Key::V {
142                    if let Some(clipboard_ctx) = state.clipboard.as_mut() {
143                        state.input.events.push(egui::Event::Text(
144                            clipboard_ctx.get_text().unwrap_or_else(|_| "".to_string()),
145                        ));
146                    }
147                } else {
148                    state.input.events.push(Event::Key {
149                        key,
150                        physical_key: None,
151                        pressed: true,
152                        modifiers: state.modifiers,
153                        repeat: false,
154                    });
155                }
156            }
157        }
158
159        Key(keycode, _scancode, glfw::Action::Repeat, keymod) => {
160            use glfw::Modifiers as Mod;
161            if let Some(key) = translate_virtual_key_code(keycode) {
162                state.modifiers = Modifiers {
163                    alt: (keymod & Mod::Alt == Mod::Alt),
164                    ctrl: (keymod & Mod::Control == Mod::Control),
165                    shift: (keymod & Mod::Shift == Mod::Shift),
166
167                    // TODO: GLFW doesn't seem to support the mac command key
168                    //       mac_cmd: keymod & Mod::LGUIMOD == Mod::LGUIMOD,
169                    command: (keymod & Mod::Control == Mod::Control),
170
171                    ..Default::default()
172                };
173
174                if state.modifiers.command && key == egui::Key::X {
175                    state.input.events.push(egui::Event::Cut);
176                } else if state.modifiers.command && key == egui::Key::C {
177                    state.input.events.push(egui::Event::Copy);
178                } else if state.modifiers.command && key == egui::Key::V {
179                    if let Some(clipboard_ctx) = state.clipboard.as_mut() {
180                        state.input.events.push(egui::Event::Text(
181                            clipboard_ctx.get_text().unwrap_or_else(|_| "".to_string()),
182                        ));
183                    }
184                } else {
185                    state.input.events.push(Event::Key {
186                        key,
187                        physical_key: None,
188                        pressed: true,
189                        modifiers: state.modifiers,
190                        repeat: true,
191                    });
192                }
193            }
194        }
195
196        Char(c) => {
197            state.input.events.push(Event::Text(c.to_string()));
198        }
199
200        Scroll(x, y) => {
201            state.input.events.push(Event::MouseWheel {
202                unit: MouseWheelUnit::Point,
203                delta: vec2(x as f32, y as f32),
204                modifiers: state.modifiers,
205            });
206        }
207
208        _ => {}
209    }
210}
211
212pub fn translate_virtual_key_code(key: glfw::Key) -> Option<egui::Key> {
213    use glfw::Key::*;
214
215    Some(match key {
216        Left => Key::ArrowLeft,
217        Up => Key::ArrowUp,
218        Right => Key::ArrowRight,
219        Down => Key::ArrowDown,
220
221        Escape => Key::Escape,
222        Tab => Key::Tab,
223        Backspace => Key::Backspace,
224        Space => Key::Space,
225
226        Enter => Key::Enter,
227
228        Insert => Key::Insert,
229        Home => Key::Home,
230        Delete => Key::Delete,
231        End => Key::End,
232        PageDown => Key::PageDown,
233        PageUp => Key::PageUp,
234
235        A => Key::A,
236        B => Key::B,
237        C => Key::C,
238        D => Key::D,
239        E => Key::E,
240        F => Key::F,
241        G => Key::G,
242        H => Key::H,
243        I => Key::I,
244        J => Key::J,
245        K => Key::K,
246        L => Key::L,
247        M => Key::M,
248        N => Key::N,
249        O => Key::O,
250        P => Key::P,
251        Q => Key::Q,
252        R => Key::R,
253        S => Key::S,
254        T => Key::T,
255        U => Key::U,
256        V => Key::V,
257        W => Key::W,
258        X => Key::X,
259        Y => Key::Y,
260        Z => Key::Z,
261
262        _ => {
263            return None;
264        }
265    })
266}
267
268pub fn translate_cursor(cursor_icon: egui::CursorIcon) -> glfw::StandardCursor {
269    match cursor_icon {
270        CursorIcon::Default => glfw::StandardCursor::Arrow,
271
272        CursorIcon::PointingHand => glfw::StandardCursor::Hand,
273
274        CursorIcon::ResizeHorizontal => glfw::StandardCursor::HResize,
275        CursorIcon::ResizeVertical => glfw::StandardCursor::VResize,
276        // TODO: GLFW doesnt have these specific resize cursors, so we'll just use the HResize and VResize ones instead
277        CursorIcon::ResizeNeSw => glfw::StandardCursor::HResize,
278        CursorIcon::ResizeNwSe => glfw::StandardCursor::VResize,
279
280        CursorIcon::Text => glfw::StandardCursor::IBeam,
281        CursorIcon::Crosshair => glfw::StandardCursor::Crosshair,
282
283        CursorIcon::Grab | CursorIcon::Grabbing => glfw::StandardCursor::Hand,
284
285        // TODO: Same for these
286        CursorIcon::NotAllowed | CursorIcon::NoDrop => glfw::StandardCursor::Arrow,
287        CursorIcon::Wait => glfw::StandardCursor::Arrow,
288        _ => glfw::StandardCursor::Arrow,
289    }
290}
291
292pub fn init_clipboard() -> Option<ClipboardContext> {
293    match ClipboardContext::new() {
294        Ok(clipboard) => Some(clipboard),
295        Err(err) => {
296            eprintln!("Failed to initialize clipboard: {}", err);
297            None
298        }
299    }
300}
301
302pub fn copy_to_clipboard(egui_state: &mut EguiInputState, copy_text: String) {
303    if let Some(clipboard) = egui_state.clipboard.as_mut() {
304        let result = clipboard.set_text(copy_text);
305        if result.is_err() {
306            dbg!("Unable to set clipboard content.");
307        }
308    }
309}