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#[derive(Debug)]
18pub struct WinitConfig {
19 #[cfg(target_os = "android")]
20 pub android_app: winit::platform::android::activity::AndroidApp,
21 pub title: String,
23 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}
41pub struct WinitBackend {
43 pub event_loop: Option<EventLoop<()>>,
47 pub window: Option<winit::window::Window>,
50 pub modifiers: egui::Modifiers,
52 pub pointer_touch_id: Option<u64>,
53 pub framebuffer_size: [u32; 2],
55 pub scale: f32,
57 pub cursor_pos_logical: [f32; 2],
59 pub raw_input: RawInput,
61 pub frame_events: Vec<winit::event::Event<'static, ()>>,
63 pub latest_resize_event: bool,
66 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 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 if !suspended {
234 if let Some(window) = user_app.get_all().0.window.as_ref() {
235 window.request_redraw()
236 }
237 }
238 }
239 event::Event::RedrawRequested(_) => {
241 if !suspended {
242 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 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 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 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 match touch.phase {
480 winit::event::TouchPhase::Started => {
481 self.pointer_touch_id = Some(touch.id);
482 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 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}