egui_window_winit/
lib.rs

1use egui::{DroppedFile, Event, Key, Modifiers, Rect};
2use egui_backend::egui::RawInput;
3use egui_backend::*;
4pub use winit;
5use winit::{event::MouseButton, window::WindowBuilder, *};
6use winit::{
7    event::{ModifiersState, VirtualKeyCode},
8    event_loop::{ControlFlow, EventLoop},
9};
10
11#[cfg(target_arch = "wasm32")]
12use wasm_bindgen::JsCast;
13#[cfg(target_arch = "wasm32")]
14use winit::platform::web::WindowBuilderExtWebSys;
15
16/// config that you provide to winit backend
17#[derive(Debug)]
18pub struct WinitConfig {
19    #[cfg(target_os = "android")]
20    pub android_app: winit::platform::android::activity::AndroidApp,
21    /// window title
22    pub title: String,
23    /// on web: winit will try to get the canvas element with this id attribute and use it as the window's context
24    /// for now, it must not be empty. we can later provide options like creating a canvas ourselves and adding it to dom
25    /// defualt value is : `egui_canvas`
26    /// so, make sure there's a canvas element in html body with this id
27    pub dom_element_id: Option<String>,
28}
29impl Default for WinitConfig {
30    fn default() -> Self {
31        Self {
32            title: "egui winit window".to_string(),
33            dom_element_id: Some("egui_canvas".to_string()),
34            #[cfg(target_os = "android")]
35            android_app: unimplemented!(
36                "winit requires android 'app' struct from android_main function"
37            ),
38        }
39    }
40}
41/// This is the winit WindowBackend for egui
42pub struct WinitBackend {
43    /// we want to take out the event loop when we call the  `WindowBackend::run_event_loop` fn
44    /// so, this will always be `None` once we start the event loop
45    /// TODO: If we want the EventLoop to be generic, then, we have to make the whole WinitBackend struct to be generic too. don't know if its worth the complexity.
46    pub event_loop: Option<EventLoop<()>>,
47    /// the winit window. on android, this might be None when suspended. and recreated when resumed.
48    /// on other platforms, we just create the window before entering event loop.
49    pub window: Option<winit::window::Window>,
50    /// current modifiers state
51    pub modifiers: egui::Modifiers,
52    pub pointer_touch_id: Option<u64>,
53    /// frame buffer size in physical pixels
54    pub framebuffer_size: [u32; 2],
55    /// scale
56    pub scale: f32,
57    /// cusor position in logical pixels
58    pub cursor_pos_logical: [f32; 2],
59    /// input for egui's begin_frame
60    pub raw_input: RawInput,
61    /// all current frame's events will be stored in this vec
62    pub frame_events: Vec<winit::event::Event<'static, ()>>,
63    /// should be true if there's been a resize event
64    /// should be set to false once the renderer takes the latest size during `GfxBackend::prepare_frame`
65    pub latest_resize_event: bool,
66    /// ???
67    pub should_close: bool,
68    pub backend_config: BackendConfig,
69    pub window_builder: WindowBuilder,
70}
71impl Drop for WinitBackend {
72    fn drop(&mut self) {
73        tracing::warn!("winit backend is being dropped");
74    }
75}
76impl WindowBackend for WinitBackend {
77    type Configuration = WinitConfig;
78    type WindowType = winit::window::Window;
79
80    fn new(config: Self::Configuration, backend_config: BackendConfig) -> Self {
81        let mut event_loop = winit::event_loop::EventLoopBuilder::with_user_event();
82        #[cfg(target_os = "android")]
83        use winit::platform::android::EventLoopBuilderExtAndroid;
84        #[cfg(target_os = "android")]
85        let event_loop = event_loop.with_android_app(config.android_app);
86
87        let el = event_loop.build();
88
89        #[allow(unused_mut)]
90        let mut window_builder = WindowBuilder::new()
91            .with_resizable(true)
92            .with_title(config.title);
93        #[cfg(target_arch = "wasm32")]
94        let window = {
95            let document = web_sys::window()
96                .expect("failed ot get websys window")
97                .document()
98                .expect("failed to get websys doc");
99            let canvas = config.dom_element_id.map(|canvas_id| {
100                    document
101                        .get_element_by_id(&canvas_id)
102                        .expect("config doesn't contain canvas and DOM doesn't have a canvas element either")
103                        .dyn_into::<web_sys::HtmlCanvasElement>().expect("failed to get canvas converted into html canvas element")
104                });
105            window_builder = window_builder.with_canvas(canvas);
106            // create winit window
107            let window = window_builder
108                .clone()
109                .build(&el)
110                .expect("failed to create winit window");
111
112            Some(window)
113        };
114        #[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
115        let window = Some(
116            window_builder
117                .clone()
118                .build(&el)
119                .expect("failed to create winit window"),
120        );
121
122        #[cfg(target_os = "android")]
123        let window = None;
124
125        let framebuffer_size = [0, 0];
126        let scale = 1.0;
127
128        let raw_input = RawInput::default();
129        Self {
130            event_loop: Some(el),
131            window,
132            modifiers: Modifiers::default(),
133            framebuffer_size,
134            scale,
135            cursor_pos_logical: [0.0, 0.0],
136            raw_input,
137            frame_events: Vec::new(),
138            latest_resize_event: true,
139            should_close: false,
140            backend_config,
141            window_builder,
142            pointer_touch_id: None,
143        }
144    }
145
146    fn take_raw_input(&mut self) -> egui::RawInput {
147        self.raw_input.take()
148    }
149
150    fn get_window(&mut self) -> Option<&mut Self::WindowType> {
151        self.window.as_mut()
152    }
153
154    fn get_live_physical_size_framebuffer(&mut self) -> Option<[u32; 2]> {
155        if let Some(window) = self.window.as_ref() {
156            let size = window.inner_size();
157            Some([size.width, size.height])
158        } else {
159            None
160        }
161    }
162
163    fn run_event_loop<U: UserApp<UserWindowBackend = Self> + 'static>(mut user_app: U) {
164        let el = user_app
165            .get_all()
166            .0
167            .event_loop
168            .take()
169            .expect("event loop missing");
170
171        let mut suspended = true;
172        let mut events_wait_duration = std::time::Duration::ZERO;
173        el.run(move |event, _event_loop, control_flow| {
174            match event {
175                event::Event::Suspended => {
176                    suspended = true;
177                    tracing::warn!("suspend event received");
178                    #[cfg(not(target_os = "android"))]
179                    panic!("suspend on non-android platforms is not supported at the moment");
180                    #[cfg(target_os = "android")]
181                    {
182                        user_app.suspend(window_backend);
183                        window_backend.window = None;
184                    }
185                }
186                event::Event::Resumed => {
187                    suspended = false;
188                    tracing::warn!("resume event received");
189                    #[cfg(target_os = "android")]
190                    {
191                        window_backend.window = Some(
192                            window_backend
193                                .window_builder
194                                .clone()
195                                .build(_event_loop)
196                                .expect("failed to create window"),
197                        );
198                        user_app.resume(window_backend);
199                    }
200                    let framebuffer_size_physical = user_app
201                        .get_all()
202                        .0
203                        .window
204                        .as_ref()
205                        .expect("failed to get size of window after resume event")
206                        .inner_size();
207
208                    user_app.get_all().0.framebuffer_size = [
209                        framebuffer_size_physical.width,
210                        framebuffer_size_physical.height,
211                    ];
212                    user_app.resize_framebuffer();
213                    user_app.get_all().0.scale = user_app
214                        .get_all()
215                        .0
216                        .window
217                        .as_ref()
218                        .expect("failed to get scale of window after resume event")
219                        .scale_factor() as f32;
220                    let window_size = framebuffer_size_physical
221                        .to_logical::<f32>(user_app.get_all().0.scale as f64);
222                    user_app.get_all().0.raw_input = RawInput {
223                        screen_rect: Some(Rect::from_two_pos(
224                            [0.0, 0.0].into(),
225                            [window_size.width, window_size.height].into(),
226                        )),
227                        pixels_per_point: Some(user_app.get_all().0.scale),
228                        ..Default::default()
229                    };
230                }
231                event::Event::MainEventsCleared => {
232                    // no point in redrawing if we are suspended.
233                    if !suspended {
234                        if let Some(window) = user_app.get_all().0.window.as_ref() {
235                            window.request_redraw()
236                        }
237                    }
238                }
239                // assume single window, so no need to check window id.
240                event::Event::RedrawRequested(_) => {
241                    if !suspended {
242                        // take egui input
243                        if user_app.get_all().0.latest_resize_event {
244                            user_app.resize_framebuffer();
245                            user_app.get_all().0.latest_resize_event = false;
246                        }
247                        // begin egui with input
248                        let logical_size = [
249                            user_app.get_all().0.framebuffer_size[0] as f32
250                                / user_app.get_all().0.scale,
251                            user_app.get_all().0.framebuffer_size[1] as f32
252                                / user_app.get_all().0.scale,
253                        ];
254                        // run userapp gui function. let user do anything he wants with window or gfx backends
255                        if let Some((_platform_output, timeout)) = user_app.run(logical_size) {
256                            events_wait_duration = timeout;
257                        }
258                    }
259                }
260                rest => user_app.get_all().0.handle_event(rest),
261            }
262            if user_app.get_all().0.should_close {
263                *control_flow = ControlFlow::Exit;
264            } else {
265                control_flow.set_wait_timeout(events_wait_duration);
266                events_wait_duration = std::time::Duration::ZERO;
267            }
268        })
269    }
270
271    fn get_config(&self) -> &BackendConfig {
272        &self.backend_config
273    }
274
275    fn swap_buffers(&mut self) {
276        unimplemented!("winit backend doesn't support swapping buffers")
277    }
278
279    fn get_proc_address(&mut self, _: &str) -> *const core::ffi::c_void {
280        unimplemented!("winit backend doesn't support loading opengl function pointers")
281    }
282
283    fn is_opengl(&self) -> bool {
284        false
285    }
286
287    fn set_window_title(&mut self, title: &str) {
288        if let Some(w) = self.window.as_mut() {
289            w.set_title(title)
290        }
291    }
292
293    fn get_window_position(&mut self) -> Option<[f32; 2]> {
294        self.window.as_mut().map(|w| {
295            w.inner_position()
296                .unwrap()
297                .to_logical::<f32>(w.scale_factor())
298                .into()
299        })
300    }
301
302    fn set_window_position(&mut self, _pos: [f32; 2]) {
303        unimplemented!()
304    }
305
306    fn get_window_size(&mut self) -> Option<[f32; 2]> {
307        self.window
308            .as_mut()
309            .map(|w| w.inner_size().to_logical::<f32>(w.scale_factor()).into())
310    }
311
312    fn set_window_size(&mut self, size: [f32; 2]) {
313        if let Some(w) = self.window.as_mut() {
314            w.set_inner_size(winit::dpi::LogicalSize::new(size[0], size[1]))
315        }
316    }
317
318    fn get_window_minimized(&mut self) -> Option<bool> {
319        self.window.as_mut().and_then(|w| w.is_minimized())
320    }
321
322    fn set_minimize_window(&mut self, min: bool) {
323        if let Some(w) = self.window.as_mut() {
324            w.set_minimized(min)
325        }
326    }
327
328    fn get_window_maximized(&mut self) -> Option<bool> {
329        self.window.as_mut().map(|w| w.is_maximized())
330    }
331
332    fn set_maximize_window(&mut self, max: bool) {
333        if let Some(w) = self.window.as_mut() {
334            w.set_maximized(max)
335        }
336    }
337
338    fn get_window_visibility(&mut self) -> Option<bool> {
339        self.window.as_mut().and_then(|w| w.is_visible())
340    }
341
342    fn set_window_visibility(&mut self, vis: bool) {
343        if let Some(w) = self.window.as_mut() {
344            w.set_visible(vis)
345        }
346    }
347
348    fn get_always_on_top(&mut self) -> Option<bool> {
349        unimplemented!()
350    }
351
352    fn set_always_on_top(&mut self, always_on_top: bool) {
353        if let Some(w) = self.window.as_mut() {
354            w.set_window_level(if always_on_top {
355                window::WindowLevel::AlwaysOnTop
356            } else {
357                window::WindowLevel::Normal
358            })
359        };
360    }
361
362    fn get_passthrough(&mut self) -> Option<bool> {
363        unimplemented!()
364    }
365
366    fn set_passthrough(&mut self, passthrough: bool) {
367        self.window
368            .as_mut()
369            .map(|w| w.set_cursor_hittest(passthrough));
370    }
371}
372
373impl WinitBackend {
374    fn handle_event(&mut self, event: winit::event::Event<()>) {
375        if let Some(egui_event) = match event {
376            event::Event::WindowEvent { event, .. } => match event {
377                event::WindowEvent::Resized(size) => {
378                    let logical_size = size.to_logical::<f32>(self.scale as f64);
379                    self.raw_input.screen_rect = Some(Rect::from_two_pos(
380                        Default::default(),
381                        [logical_size.width, logical_size.height].into(),
382                    ));
383                    self.latest_resize_event = true;
384                    self.framebuffer_size = size.into();
385                    None
386                }
387                event::WindowEvent::CloseRequested => {
388                    self.should_close = true;
389                    None
390                }
391                event::WindowEvent::DroppedFile(df) => {
392                    self.raw_input.dropped_files.push(DroppedFile {
393                        path: Some(df.clone()),
394                        name: df
395                            .file_name()
396                            .unwrap_or_default()
397                            .to_str()
398                            .unwrap_or_default()
399                            .to_string(),
400                        last_modified: None,
401                        bytes: None,
402                    });
403                    None
404                }
405
406                event::WindowEvent::ReceivedCharacter(c) => Some(Event::Text(c.to_string())),
407
408                event::WindowEvent::KeyboardInput { input, .. } => {
409                    let pressed = match input.state {
410                        event::ElementState::Pressed => true,
411                        event::ElementState::Released => false,
412                    };
413
414                    if let Some(key_code) = input.virtual_keycode {
415                        if let Some(egui_key) = winit_key_to_egui(key_code) {
416                            Some(Event::Key {
417                                key: egui_key,
418                                pressed,
419                                modifiers: self.modifiers,
420                                repeat: false,
421                            })
422                        } else {
423                            None
424                        }
425                    } else {
426                        None
427                    }
428                }
429                event::WindowEvent::ModifiersChanged(modifiers) => {
430                    self.modifiers = winit_modifiers_to_egui(modifiers);
431                    None
432                }
433                event::WindowEvent::CursorMoved { position, .. } => {
434                    let logical = position.to_logical::<f32>(self.scale as f64);
435                    self.cursor_pos_logical = [logical.x, logical.y];
436                    Some(Event::PointerMoved([logical.x, logical.y].into()))
437                }
438                event::WindowEvent::CursorLeft { .. } => Some(Event::PointerGone),
439                event::WindowEvent::MouseWheel { delta, .. } => match delta {
440                    event::MouseScrollDelta::LineDelta(x, y) => Some(Event::Scroll([x, y].into())),
441                    event::MouseScrollDelta::PixelDelta(pos) => {
442                        let lpos = pos.to_logical::<f32>(self.scale as f64);
443                        Some(Event::Scroll([lpos.x, lpos.y].into()))
444                    }
445                },
446                event::WindowEvent::MouseInput { state, button, .. } => {
447                    let pressed = match state {
448                        event::ElementState::Pressed => true,
449                        event::ElementState::Released => false,
450                    };
451                    Some(Event::PointerButton {
452                        pos: self.cursor_pos_logical.into(),
453                        button: winit_mouse_button_to_egui(button),
454                        pressed,
455                        modifiers: self.modifiers,
456                    })
457                }
458                event::WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
459                    self.scale = scale_factor as f32;
460                    self.raw_input.pixels_per_point = Some(scale_factor as f32);
461                    self.latest_resize_event = true;
462                    None
463                }
464
465                event::WindowEvent::Destroyed => {
466                    tracing::warn!("window destroyed");
467                    None
468                }
469                event::WindowEvent::Touch(touch) => {
470                    // code stolen from eframe(egui-winit).
471                    let pos = egui::pos2(
472                        touch.location.x as f32 / self.scale,
473                        touch.location.y as f32 / self.scale,
474                    );
475                    self.cursor_pos_logical = [pos.x, pos.y];
476                    if self.pointer_touch_id.is_none() || self.pointer_touch_id.unwrap() == touch.id
477                    {
478                        // … emit PointerButton resp. PointerMoved events to emulate mouse
479                        match touch.phase {
480                            winit::event::TouchPhase::Started => {
481                                self.pointer_touch_id = Some(touch.id);
482                                // First move the pointer to the right location
483
484                                self.raw_input.events.push(Event::PointerMoved(pos));
485                                self.raw_input.events.push(Event::PointerButton {
486                                    pos,
487                                    button: egui::PointerButton::Primary,
488                                    pressed: true,
489                                    modifiers: self.modifiers,
490                                });
491                            }
492                            winit::event::TouchPhase::Moved => {
493                                self.raw_input.events.push(Event::PointerMoved(pos));
494                            }
495                            winit::event::TouchPhase::Ended => {
496                                self.pointer_touch_id = None;
497                                self.raw_input.events.push(Event::PointerButton {
498                                    pos,
499                                    button: egui::PointerButton::Primary,
500                                    pressed: false,
501                                    modifiers: self.modifiers,
502                                });
503                                self.raw_input.events.push(egui::Event::PointerGone);
504                            }
505                            winit::event::TouchPhase::Cancelled => {
506                                self.pointer_touch_id = None;
507
508                                self.raw_input.events.push(egui::Event::PointerGone);
509                            }
510                        }
511                    }
512                    Some(Event::Touch {
513                        device_id: egui::TouchDeviceId(egui::epaint::util::hash(touch.device_id)),
514                        id: egui::TouchId::from(touch.id),
515                        phase: match touch.phase {
516                            winit::event::TouchPhase::Started => egui::TouchPhase::Start,
517                            winit::event::TouchPhase::Moved => egui::TouchPhase::Move,
518                            winit::event::TouchPhase::Ended => egui::TouchPhase::End,
519                            winit::event::TouchPhase::Cancelled => egui::TouchPhase::Cancel,
520                        },
521                        pos,
522                        force: match touch.force {
523                            Some(winit::event::Force::Normalized(force)) => force as f32,
524                            Some(winit::event::Force::Calibrated {
525                                force,
526                                max_possible_force,
527                                ..
528                            }) => (force / max_possible_force) as f32,
529                            None => 0_f32,
530                        },
531                    })
532                }
533                _ => None,
534            },
535            _ => None,
536        } {
537            self.raw_input.events.push(egui_event);
538        }
539    }
540}
541
542fn winit_modifiers_to_egui(modifiers: ModifiersState) -> Modifiers {
543    Modifiers {
544        alt: modifiers.alt(),
545        ctrl: modifiers.ctrl(),
546        shift: modifiers.shift(),
547        // i have no idea what a mac_cmd key is
548        mac_cmd: false,
549        command: modifiers.logo(),
550    }
551}
552fn winit_mouse_button_to_egui(mb: winit::event::MouseButton) -> egui::PointerButton {
553    match mb {
554        MouseButton::Left => egui::PointerButton::Primary,
555        MouseButton::Right => egui::PointerButton::Secondary,
556        MouseButton::Middle => egui::PointerButton::Middle,
557        MouseButton::Other(_) => egui::PointerButton::Extra1,
558    }
559}
560fn winit_key_to_egui(key_code: VirtualKeyCode) -> Option<Key> {
561    let key = match key_code {
562        VirtualKeyCode::Down => Key::ArrowDown,
563        VirtualKeyCode::Left => Key::ArrowLeft,
564        VirtualKeyCode::Right => Key::ArrowRight,
565        VirtualKeyCode::Up => Key::ArrowUp,
566
567        VirtualKeyCode::Escape => Key::Escape,
568        VirtualKeyCode::Tab => Key::Tab,
569        VirtualKeyCode::Back => Key::Backspace,
570        VirtualKeyCode::Return => Key::Enter,
571        VirtualKeyCode::Space => Key::Space,
572
573        VirtualKeyCode::Insert => Key::Insert,
574        VirtualKeyCode::Delete => Key::Delete,
575        VirtualKeyCode::Home => Key::Home,
576        VirtualKeyCode::End => Key::End,
577        VirtualKeyCode::PageUp => Key::PageUp,
578        VirtualKeyCode::PageDown => Key::PageDown,
579
580        VirtualKeyCode::Key0 | VirtualKeyCode::Numpad0 => Key::Num0,
581        VirtualKeyCode::Key1 | VirtualKeyCode::Numpad1 => Key::Num1,
582        VirtualKeyCode::Key2 | VirtualKeyCode::Numpad2 => Key::Num2,
583        VirtualKeyCode::Key3 | VirtualKeyCode::Numpad3 => Key::Num3,
584        VirtualKeyCode::Key4 | VirtualKeyCode::Numpad4 => Key::Num4,
585        VirtualKeyCode::Key5 | VirtualKeyCode::Numpad5 => Key::Num5,
586        VirtualKeyCode::Key6 | VirtualKeyCode::Numpad6 => Key::Num6,
587        VirtualKeyCode::Key7 | VirtualKeyCode::Numpad7 => Key::Num7,
588        VirtualKeyCode::Key8 | VirtualKeyCode::Numpad8 => Key::Num8,
589        VirtualKeyCode::Key9 | VirtualKeyCode::Numpad9 => Key::Num9,
590
591        VirtualKeyCode::A => Key::A,
592        VirtualKeyCode::B => Key::B,
593        VirtualKeyCode::C => Key::C,
594        VirtualKeyCode::D => Key::D,
595        VirtualKeyCode::E => Key::E,
596        VirtualKeyCode::F => Key::F,
597        VirtualKeyCode::G => Key::G,
598        VirtualKeyCode::H => Key::H,
599        VirtualKeyCode::I => Key::I,
600        VirtualKeyCode::J => Key::J,
601        VirtualKeyCode::K => Key::K,
602        VirtualKeyCode::L => Key::L,
603        VirtualKeyCode::M => Key::M,
604        VirtualKeyCode::N => Key::N,
605        VirtualKeyCode::O => Key::O,
606        VirtualKeyCode::P => Key::P,
607        VirtualKeyCode::Q => Key::Q,
608        VirtualKeyCode::R => Key::R,
609        VirtualKeyCode::S => Key::S,
610        VirtualKeyCode::T => Key::T,
611        VirtualKeyCode::U => Key::U,
612        VirtualKeyCode::V => Key::V,
613        VirtualKeyCode::W => Key::W,
614        VirtualKeyCode::X => Key::X,
615        VirtualKeyCode::Y => Key::Y,
616        VirtualKeyCode::Z => Key::Z,
617
618        VirtualKeyCode::F1 => Key::F1,
619        VirtualKeyCode::F2 => Key::F2,
620        VirtualKeyCode::F3 => Key::F3,
621        VirtualKeyCode::F4 => Key::F4,
622        VirtualKeyCode::F5 => Key::F5,
623        VirtualKeyCode::F6 => Key::F6,
624        VirtualKeyCode::F7 => Key::F7,
625        VirtualKeyCode::F8 => Key::F8,
626        VirtualKeyCode::F9 => Key::F9,
627        VirtualKeyCode::F10 => Key::F10,
628        VirtualKeyCode::F11 => Key::F11,
629        VirtualKeyCode::F12 => Key::F12,
630        VirtualKeyCode::F13 => Key::F13,
631        VirtualKeyCode::F14 => Key::F14,
632        VirtualKeyCode::F15 => Key::F15,
633        VirtualKeyCode::F16 => Key::F16,
634        VirtualKeyCode::F17 => Key::F17,
635        VirtualKeyCode::F18 => Key::F18,
636        VirtualKeyCode::F19 => Key::F19,
637        VirtualKeyCode::F20 => Key::F20,
638        _ => return None,
639    };
640    Some(key)
641}