nc_ui/
input.rs

1//
2// Copyright 2021-Present (c) Raja Lehtihet & Wael El Oraiby
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are met:
6//
7// 1. Redistributions of source code must retain the above copyright notice,
8// this list of conditions and the following disclaimer.
9//
10// 2. Redistributions in binary form must reproduce the above copyright notice,
11// this list of conditions and the following disclaimer in the documentation
12// and/or other materials provided with the distribution.
13//
14// 3. Neither the name of the copyright holder nor the names of its contributors
15// may be used to endorse or promote products derived from this software without
16// specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28// POSSIBILITY OF SUCH DAMAGE.
29//
30//
31// Copyright (c) 2021 cohaereo
32//
33// Permission is hereby granted, free of charge, to any
34// person obtaining a copy of this software and associated
35// documentation files (the "Software"), to deal in the
36// Software without restriction, including without
37// limitation the rights to use, copy, modify, merge,
38// publish, distribute, sublicense, and/or sell copies of
39// the Software, and to permit persons to whom the Software
40// is furnished to do so, subject to the following
41// conditions:
42//
43// The above copyright notice and this permission notice
44// shall be included in all copies or substantial portions
45// of the Software.
46//
47// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
48// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
49// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
50// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
51// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
52// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
53// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
54// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
55// DEALINGS IN THE SOFTWARE.
56//
57#![warn(clippy::all)]
58#![allow(clippy::single_match)]
59
60// Re-export dependencies.
61pub use egui;
62
63use crate::*;
64
65use egui::*;
66
67#[cfg(not(feature = "clipboard"))]
68
69use super::clipboard::{
70    ClipboardContext, // TODO: remove
71    ClipboardProvider,
72};
73
74pub struct EguiInputState {
75    pub pointer_pos: Pos2,
76    pub clipboard: Option<ClipboardContext>,
77    pub input: RawInput,
78    pub modifiers: Modifiers,
79}
80
81impl EguiInputState {
82    pub fn new(input: RawInput) -> Self {
83        EguiInputState {
84            pointer_pos: Pos2::new(0f32, 0f32),
85            clipboard: init_clipboard(),
86            input,
87            modifiers: Modifiers::default(),
88        }
89    }
90}
91
92pub fn handle_event(event: glfw::WindowEvent, state: &mut EguiInputState) {
93    use glfw::WindowEvent::*;
94
95    match event {
96        FramebufferSize(width, height) => {
97            state.input.screen_rect = Some(epaint::emath::Rect::from_min_size(
98                Pos2::new(0f32, 0f32),
99                egui::vec2(width as f32, height as f32) / state.input.pixels_per_point.unwrap(),
100            ))
101        }
102
103        MouseButton (mouse_btn, glfw::Action::Press, _) => state.input.events.push(egui::Event::PointerButton {
104            pos: state.pointer_pos,
105            button: match mouse_btn {
106                glfw::MouseButtonLeft => egui::PointerButton::Primary,
107                glfw::MouseButtonRight => egui::PointerButton::Secondary,
108                glfw::MouseButtonMiddle => egui::PointerButton::Middle,
109                _ => unreachable!(),
110            },
111            pressed: true,
112            modifiers: state.modifiers,
113        }),
114
115        MouseButton (mouse_btn, glfw::Action::Release, _) => state.input.events.push(egui::Event::PointerButton {
116            pos: state.pointer_pos,
117            button: match mouse_btn {
118                glfw::MouseButtonLeft => egui::PointerButton::Primary,
119                glfw::MouseButtonRight => egui::PointerButton::Secondary,
120                glfw::MouseButtonMiddle => egui::PointerButton::Middle,
121                _ => unreachable!(),
122            },
123            pressed: false,
124            modifiers: state.modifiers,
125        }),
126
127        CursorPos(x, y) => {
128            state.pointer_pos = pos2(
129                x as f32 / state.input.pixels_per_point.unwrap(),
130                y as f32 / state.input.pixels_per_point.unwrap(),
131            );
132            state
133                .input
134                .events
135                .push(egui::Event::PointerMoved(state.pointer_pos))
136        }
137
138        Key(keycode, _scancode, glfw::Action::Release, keymod) => {
139            use glfw::Modifiers as Mod;
140            if let Some(key) = translate_virtual_key_code(keycode) {
141                state.modifiers = Modifiers {
142                    alt: (keymod & Mod::Alt == Mod::Alt),
143                    ctrl: (keymod & Mod::Control == Mod::Control),
144                    shift: (keymod & Mod::Shift == Mod::Shift),
145
146                    // TODO: GLFW doesn't seem to support the mac command key
147                    // mac_cmd: keymod & Mod::LGUIMOD == Mod::LGUIMOD,
148                    command: (keymod & Mod::Control == Mod::Control),
149
150                    ..Default::default()
151                };
152
153                state.input.events.push(Event::Key {
154                    key,
155                    pressed: false,
156                    modifiers: state.modifiers,
157                });
158            }
159        }
160
161        Key(keycode, _scancode, glfw::Action::Press | glfw::Action::Repeat, keymod) => {
162            use glfw::Modifiers as Mod;
163            if let Some(key) = translate_virtual_key_code(keycode) {
164                state.modifiers = Modifiers {
165                    alt: (keymod & Mod::Alt == Mod::Alt),
166                    ctrl: (keymod & Mod::Control == Mod::Control),
167                    shift: (keymod & Mod::Shift == Mod::Shift),
168
169                    // TODO: GLFW doesn't seem to support the mac command key
170                    // mac_cmd: keymod & Mod::LGUIMOD == Mod::LGUIMOD,
171                    command: (keymod & Mod::Control == Mod::Control),
172
173                    ..Default::default()
174                };
175
176                if state.modifiers.command && key == egui::Key::X {
177                    state.input.events.push(egui::Event::Cut);
178                } else if state.modifiers.command && key == egui::Key::C {
179                    state.input.events.push(egui::Event::Copy);
180                } else if state.modifiers.command && key == egui::Key::V {
181                    if let Some(clipboard_ctx) = state.clipboard.as_mut() {
182                        state.input.events.push(egui::Event::Text(clipboard_ctx.get_contents().unwrap_or("".to_string())));
183                    }
184                } else {
185                    state.input.events.push(Event::Key {
186                        key,
187                        pressed: true,
188                        modifiers: state.modifiers,
189                    });
190                }
191            }
192        }
193
194        Char(c) => {
195            state.input.events.push(Event::Text(c.to_string()));
196        }
197
198        Scroll (x, y) => {
199            state.input.scroll_delta = vec2(x as f32, y as f32);
200        }
201
202        _ => {}
203    }
204}
205
206pub fn translate_virtual_key_code(key: glfw::Key) -> Option<egui::Key> {
207
208    Some(match key {
209        glfw::Key::Left  => egui::Key::ArrowLeft,
210        glfw::Key::Up    => egui::Key::ArrowUp,
211        glfw::Key::Right => egui::Key::ArrowRight,
212        glfw::Key::Down  => egui::Key::ArrowDown,
213
214        glfw::Key::Escape    => egui::Key::Escape,
215        glfw::Key::Tab       => egui::Key::Tab,
216        glfw::Key::Backspace => egui::Key::Backspace,
217        glfw::Key::Space     => egui::Key::Space,
218
219        glfw::Key::Enter     => egui::Key::Enter,
220
221        glfw::Key::Insert    => egui::Key::Insert,
222        glfw::Key::Home      => egui::Key::Home,
223        glfw::Key::Delete    => egui::Key::Delete,
224        glfw::Key::End       => egui::Key::End,
225        glfw::Key::PageDown  => egui::Key::PageDown,
226        glfw::Key::PageUp    => egui::Key::PageUp,
227
228
229        glfw::Key::A => egui::Key::A,
230        glfw::Key::B => egui::Key::B,
231        glfw::Key::C => egui::Key::C,
232        glfw::Key::D => egui::Key::D,
233        glfw::Key::E => egui::Key::E,
234        glfw::Key::F => egui::Key::F,
235        glfw::Key::G => egui::Key::G,
236        glfw::Key::H => egui::Key::H,
237        glfw::Key::I => egui::Key::I,
238        glfw::Key::J => egui::Key::J,
239        glfw::Key::K => egui::Key::K,
240        glfw::Key::L => egui::Key::L,
241        glfw::Key::M => egui::Key::M,
242        glfw::Key::N => egui::Key::N,
243        glfw::Key::O => egui::Key::O,
244        glfw::Key::P => egui::Key::P,
245        glfw::Key::Q => egui::Key::Q,
246        glfw::Key::R => egui::Key::R,
247        glfw::Key::S => egui::Key::S,
248        glfw::Key::T => egui::Key::T,
249        glfw::Key::U => egui::Key::U,
250        glfw::Key::V => egui::Key::V,
251        glfw::Key::W => egui::Key::W,
252        glfw::Key::X => egui::Key::X,
253        glfw::Key::Y => egui::Key::Y,
254        glfw::Key::Z => egui::Key::Z,
255
256        _ => {
257            return None;
258        }
259    })
260}
261
262pub fn translate_cursor(cursor_icon: egui::CursorIcon) -> glfw::StandardCursor {
263    match cursor_icon {
264        CursorIcon::Default => glfw::StandardCursor::Arrow,
265        CursorIcon::PointingHand => glfw::StandardCursor::Hand,
266        CursorIcon::ResizeHorizontal => glfw::StandardCursor::HResize,
267        CursorIcon::ResizeVertical => glfw::StandardCursor::VResize,
268        // TODO: GLFW doesnt have these specific resize cursors, so we'll just use the HResize and VResize ones instead
269        CursorIcon::ResizeNeSw => glfw::StandardCursor::HResize,
270        CursorIcon::ResizeNwSe => glfw::StandardCursor::VResize,
271        CursorIcon::Text => glfw::StandardCursor::IBeam,
272        CursorIcon::Crosshair => glfw::StandardCursor::Crosshair,
273        // TODO: Same for these
274        CursorIcon::NotAllowed | CursorIcon::NoDrop => glfw::StandardCursor::Arrow,
275        CursorIcon::Wait => glfw::StandardCursor::Arrow,
276        CursorIcon::Grab | CursorIcon::Grabbing => glfw::StandardCursor::Hand,
277
278        _ => glfw::StandardCursor::Arrow,
279    }
280}
281
282pub fn init_clipboard() -> Option<ClipboardContext> {
283    match ClipboardContext::new() {
284        Ok(clipboard) => Some(clipboard),
285        Err(err) => {
286            eprintln!("Failed to initialize clipboard: {}", err);
287            None
288        }
289    }
290}
291
292pub fn copy_to_clipboard(egui_state: &mut EguiInputState, copy_text: String) {
293    if let Some(clipboard) = egui_state.clipboard.as_mut() {
294        let result = clipboard.set_contents(copy_text);
295        if result.is_err() {
296            dbg!("Unable to set clipboard content.");
297        }
298    }
299}