application/
application.rs

1//! Simple winit application.
2
3use std::collections::HashMap;
4use std::error::Error;
5use std::fmt::Debug;
6#[cfg(not(android_platform))]
7use std::num::NonZeroU32;
8use std::sync::mpsc::{self, Receiver, Sender};
9use std::sync::Arc;
10use std::{fmt, mem};
11
12use ::tracing::{error, info};
13use cursor_icon::CursorIcon;
14#[cfg(not(android_platform))]
15use rwh_06::{DisplayHandle, HasDisplayHandle};
16#[cfg(not(android_platform))]
17use softbuffer::{Context, Surface};
18use winit::application::ApplicationHandler;
19use winit::cursor::{Cursor, CustomCursor, CustomCursorSource};
20use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
21use winit::error::RequestError;
22use winit::event::{DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, WindowEvent};
23use winit::event_loop::{ActiveEventLoop, EventLoop};
24use winit::icon::{Icon, RgbaIcon};
25use winit::keyboard::{Key, ModifiersState};
26use winit::monitor::Fullscreen;
27#[cfg(macos_platform)]
28use winit::platform::macos::{OptionAsAlt, WindowAttributesMacOS, WindowExtMacOS};
29#[cfg(any(x11_platform, wayland_platform))]
30use winit::platform::startup_notify::{self, EventLoopExtStartupNotify, WindowExtStartupNotify};
31#[cfg(wayland_platform)]
32use winit::platform::wayland::{ActiveEventLoopExtWayland, WindowAttributesWayland};
33#[cfg(web_platform)]
34use winit::platform::web::{ActiveEventLoopExtWeb, WindowAttributesWeb};
35#[cfg(x11_platform)]
36use winit::platform::x11::{ActiveEventLoopExtX11, WindowAttributesX11};
37use winit::window::{CursorGrabMode, ResizeDirection, Theme, Window, WindowAttributes, WindowId};
38use winit_core::application::macos::ApplicationHandlerExtMacOS;
39
40#[path = "util/tracing.rs"]
41mod tracing;
42
43#[path = "util/fill.rs"]
44mod fill;
45
46/// The amount of points to around the window for drag resize direction calculations.
47const BORDER_SIZE: f64 = 20.;
48
49fn main() -> Result<(), Box<dyn Error>> {
50    #[cfg(web_platform)]
51    console_error_panic_hook::set_once();
52
53    tracing::init();
54
55    let event_loop = EventLoop::new()?;
56    let (sender, receiver) = mpsc::channel();
57
58    // Wire the user event from another thread.
59    #[cfg(not(web_platform))]
60    {
61        let event_loop_proxy = event_loop.create_proxy();
62        let sender = sender.clone();
63        std::thread::spawn(move || {
64            // Wake up the `event_loop` once every second and dispatch a custom event
65            // from a different thread.
66            info!("Starting to send user event every second");
67            loop {
68                let _ = sender.send(Action::Message);
69                event_loop_proxy.wake_up();
70                std::thread::sleep(std::time::Duration::from_secs(1));
71            }
72        });
73    }
74
75    let app = Application::new(&event_loop, receiver, sender);
76    Ok(event_loop.run_app(app)?)
77}
78
79/// Application state and event handling.
80struct Application {
81    /// Trigger actions through proxy wake up.
82    receiver: Receiver<Action>,
83    sender: Sender<Action>,
84    /// Custom cursors assets.
85    custom_cursors: Result<Vec<CustomCursor>, RequestError>,
86    /// Application icon.
87    icon: Icon,
88    windows: HashMap<WindowId, WindowState>,
89    /// Drawing context.
90    ///
91    /// With OpenGL it could be EGLDisplay.
92    #[cfg(not(android_platform))]
93    context: Option<Context<DisplayHandle<'static>>>,
94}
95
96impl Application {
97    fn new(event_loop: &EventLoop, receiver: Receiver<Action>, sender: Sender<Action>) -> Self {
98        // SAFETY: we drop the context right before the event loop is stopped, thus making it safe.
99        #[cfg(not(android_platform))]
100        let context = Some(
101            Context::new(unsafe {
102                std::mem::transmute::<DisplayHandle<'_>, DisplayHandle<'static>>(
103                    event_loop.display_handle().unwrap(),
104                )
105            })
106            .unwrap(),
107        );
108
109        // You'll have to choose an icon size at your own discretion. On X11, the desired size
110        // varies by WM, and on Windows, you still have to account for screen scaling. Here
111        // we use 32px, since it seems to work well enough in most cases. Be careful about
112        // going too high, or you'll be bitten by the low-quality downscaling built into the
113        // WM.
114        let icon = load_icon(include_bytes!("data/icon.png"));
115
116        info!("Loading cursor assets");
117        let custom_cursors = [
118            event_loop.create_custom_cursor(decode_cursor(include_bytes!("data/cross.png"))),
119            event_loop.create_custom_cursor(decode_cursor(include_bytes!("data/cross2.png"))),
120            event_loop.create_custom_cursor(decode_cursor(include_bytes!("data/gradient.png"))),
121        ]
122        .into_iter()
123        .collect();
124
125        Self {
126            receiver,
127            sender,
128            #[cfg(not(android_platform))]
129            context,
130            custom_cursors,
131            icon,
132            windows: Default::default(),
133        }
134    }
135
136    fn create_window(
137        &mut self,
138        event_loop: &dyn ActiveEventLoop,
139        _tab_id: Option<String>,
140    ) -> Result<WindowId, Box<dyn Error>> {
141        // TODO read-out activation token.
142
143        #[allow(unused_mut)]
144        let mut window_attributes = WindowAttributes::default()
145            .with_title("Winit window")
146            .with_transparent(true)
147            .with_window_icon(Some(self.icon.clone()));
148
149        #[cfg(x11_platform)]
150        if event_loop.is_x11() {
151            window_attributes = window_attributes
152                .with_platform_attributes(Box::new(window_attributes_x11(event_loop)?));
153        }
154
155        #[cfg(wayland_platform)]
156        if event_loop.is_wayland() {
157            window_attributes = window_attributes
158                .with_platform_attributes(Box::new(window_attributes_wayland(event_loop)));
159        }
160
161        #[cfg(macos_platform)]
162        if let Some(tab_id) = _tab_id {
163            let window_attributes_macos =
164                Box::new(WindowAttributesMacOS::default().with_tabbing_identifier(&tab_id));
165            window_attributes = window_attributes.with_platform_attributes(window_attributes_macos);
166        }
167
168        #[cfg(web_platform)]
169        {
170            window_attributes =
171                window_attributes.with_platform_attributes(Box::new(window_attributes_web()));
172        }
173
174        let window = event_loop.create_window(window_attributes)?;
175
176        #[cfg(ios_platform)]
177        {
178            use winit::platform::ios::WindowExtIOS;
179            window.recognize_doubletap_gesture(true);
180            window.recognize_pinch_gesture(true);
181            window.recognize_rotation_gesture(true);
182            window.recognize_pan_gesture(true, 2, 2);
183        }
184
185        let window_state = WindowState::new(self, window)?;
186        let window_id = window_state.window.id();
187        info!("Created new window with id={window_id:?}");
188        self.windows.insert(window_id, window_state);
189        Ok(window_id)
190    }
191
192    fn handle_action_from_proxy(&mut self, _event_loop: &dyn ActiveEventLoop, action: Action) {
193        match action {
194            #[cfg(web_platform)]
195            Action::DumpMonitors => self.dump_monitors(_event_loop),
196            Action::Message => {
197                info!("User wake up");
198            },
199            _ => unreachable!("Tried to execute invalid action without `WindowId`"),
200        }
201    }
202
203    fn handle_action_with_window(
204        &mut self,
205        event_loop: &dyn ActiveEventLoop,
206        window_id: WindowId,
207        action: Action,
208    ) {
209        // let cursor_position = self.cursor_position;
210        let window = self.windows.get_mut(&window_id).unwrap();
211        info!("Executing action: {action:?}");
212        match action {
213            Action::CloseWindow => {
214                let _ = self.windows.remove(&window_id);
215            },
216            Action::CreateNewWindow => {
217                #[cfg(any(x11_platform, wayland_platform))]
218                if let Err(err) = window.window.request_activation_token() {
219                    info!("Failed to get activation token: {err}");
220                } else {
221                    return;
222                }
223
224                if let Err(err) = self.create_window(event_loop, None) {
225                    error!("Error creating new window: {err}");
226                }
227            },
228            Action::ToggleResizeIncrements => window.toggle_resize_increments(),
229            Action::ToggleCursorVisibility => window.toggle_cursor_visibility(),
230            Action::ToggleResizable => window.toggle_resizable(),
231            Action::ToggleDecorations => window.toggle_decorations(),
232            Action::ToggleFullscreen => window.toggle_fullscreen(),
233            #[cfg(macos_platform)]
234            Action::ToggleSimpleFullscreen => {
235                window.window.set_simple_fullscreen(!window.window.simple_fullscreen());
236            },
237            Action::ToggleMaximize => window.toggle_maximize(),
238            Action::ToggleImeInput => window.toggle_ime(),
239            Action::Minimize => window.minimize(),
240            Action::NextCursor => window.next_cursor(),
241            Action::NextCustomCursor => {
242                if let Err(err) = self.custom_cursors.as_ref().map(|c| window.next_custom_cursor(c))
243                {
244                    error!("Error creating custom cursor: {err}");
245                }
246            },
247            #[cfg(web_platform)]
248            Action::UrlCustomCursor => {
249                if let Err(err) = window.url_custom_cursor(event_loop) {
250                    error!("Error creating custom cursor from URL: {err}");
251                }
252            },
253            #[cfg(web_platform)]
254            Action::AnimationCustomCursor => {
255                if let Err(err) = self
256                    .custom_cursors
257                    .as_ref()
258                    .map(|c| window.animation_custom_cursor(event_loop, c))
259                {
260                    error!("Error creating animated custom cursor: {err}");
261                }
262            },
263            Action::CycleCursorGrab => window.cycle_cursor_grab(),
264            Action::DragWindow => window.drag_window(),
265            Action::DragResizeWindow => window.drag_resize_window(),
266            Action::ShowWindowMenu => window.show_menu(),
267            Action::PrintHelp => self.print_help(),
268            #[cfg(macos_platform)]
269            Action::CycleOptionAsAlt => window.cycle_option_as_alt(),
270            Action::SetTheme(theme) => {
271                window.window.set_theme(theme);
272                // Get the resulting current theme to draw with
273                let actual_theme = theme.or_else(|| window.window.theme()).unwrap_or(Theme::Dark);
274                window.set_draw_theme(actual_theme);
275            },
276            #[cfg(macos_platform)]
277            Action::CreateNewTab => {
278                let tab_id = window.window.tabbing_identifier();
279                if let Err(err) = self.create_window(event_loop, Some(tab_id)) {
280                    error!("Error creating new window: {err}");
281                }
282            },
283            Action::RequestResize => window.swap_dimensions(),
284            #[cfg(web_platform)]
285            Action::DumpMonitors => {
286                let future = event_loop.request_detailed_monitor_permission();
287                let proxy = event_loop.create_proxy();
288                let sender = self.sender.clone();
289                wasm_bindgen_futures::spawn_local(async move {
290                    if let Err(error) = future.await {
291                        error!("{error}")
292                    }
293
294                    let _ = sender.send(Action::DumpMonitors);
295                    proxy.wake_up();
296                });
297            },
298            #[cfg(not(web_platform))]
299            Action::DumpMonitors => self.dump_monitors(event_loop),
300            Action::Message => {
301                self.sender.send(Action::Message).unwrap();
302                event_loop.create_proxy().wake_up();
303            },
304            Action::ToggleAnimatedFillColor => {
305                window.animated_fill_color = !window.animated_fill_color;
306            },
307            Action::ToggleContinuousRedraw => {
308                window.continuous_redraw = !window.continuous_redraw;
309                window.window.request_redraw();
310            },
311        }
312    }
313
314    fn dump_monitors(&self, event_loop: &dyn ActiveEventLoop) {
315        info!("Monitors information");
316        let primary_monitor = event_loop.primary_monitor();
317        for monitor in event_loop.available_monitors() {
318            let intro = if primary_monitor.as_ref() == Some(&monitor) {
319                "Primary monitor"
320            } else {
321                "Monitor"
322            };
323
324            if let Some(name) = monitor.name() {
325                info!("{intro}: {name}");
326            } else {
327                info!("{intro}: [no name]");
328            }
329
330            if let Some(current_mode) = monitor.current_video_mode() {
331                let PhysicalSize { width, height } = current_mode.size();
332                let bits =
333                    current_mode.bit_depth().map(|bits| format!("x{bits}")).unwrap_or_default();
334                let m_hz = current_mode
335                    .refresh_rate_millihertz()
336                    .map(|m_hz| format!(" @ {}.{} Hz", m_hz.get() / 1000, m_hz.get() % 1000))
337                    .unwrap_or_default();
338                info!("  {width}x{height}{bits}{m_hz}");
339            }
340
341            if let Some(PhysicalPosition { x, y }) = monitor.position() {
342                info!("  Position: {x},{y}");
343            }
344
345            info!("  Scale factor: {}", monitor.scale_factor());
346
347            info!("  Available modes (width x height x bit-depth):");
348            for mode in monitor.video_modes() {
349                let PhysicalSize { width, height } = mode.size();
350                let bits = mode.bit_depth().map(|bits| format!("x{bits}")).unwrap_or_default();
351                let m_hz = mode
352                    .refresh_rate_millihertz()
353                    .map(|m_hz| format!(" @ {}.{} Hz", m_hz.get() / 1000, m_hz.get() % 1000))
354                    .unwrap_or_default();
355                info!("    {width}x{height}{bits}{m_hz}");
356            }
357        }
358    }
359
360    /// Process the key binding.
361    fn process_key_binding(key: &str, mods: &ModifiersState) -> Option<Action> {
362        KEY_BINDINGS
363            .iter()
364            .find_map(|binding| binding.is_triggered_by(&key, mods).then_some(binding.action))
365    }
366
367    /// Process mouse binding.
368    fn process_mouse_binding(button: MouseButton, mods: &ModifiersState) -> Option<Action> {
369        MOUSE_BINDINGS
370            .iter()
371            .find_map(|binding| binding.is_triggered_by(&button, mods).then_some(binding.action))
372    }
373
374    fn print_help(&self) {
375        info!("Keyboard bindings:");
376        for binding in KEY_BINDINGS {
377            info!(
378                "{}{:<10} - {} ({})",
379                modifiers_to_string(binding.mods),
380                binding.trigger,
381                binding.action,
382                binding.action.help(),
383            );
384        }
385        info!("Mouse bindings:");
386        for binding in MOUSE_BINDINGS {
387            info!(
388                "{}{:<10} - {} ({})",
389                modifiers_to_string(binding.mods),
390                mouse_button_to_string(binding.trigger),
391                binding.action,
392                binding.action.help(),
393            );
394        }
395    }
396}
397
398impl ApplicationHandler for Application {
399    fn proxy_wake_up(&mut self, event_loop: &dyn ActiveEventLoop) {
400        while let Ok(action) = self.receiver.try_recv() {
401            self.handle_action_from_proxy(event_loop, action)
402        }
403    }
404
405    fn window_event(
406        &mut self,
407        event_loop: &dyn ActiveEventLoop,
408        window_id: WindowId,
409        event: WindowEvent,
410    ) {
411        let window = match self.windows.get_mut(&window_id) {
412            Some(window) => window,
413            None => return,
414        };
415
416        match event {
417            WindowEvent::SurfaceResized(size) => {
418                window.resize(size);
419            },
420            WindowEvent::Focused(focused) => {
421                if focused {
422                    info!("Window={window_id:?} focused");
423                } else {
424                    info!("Window={window_id:?} unfocused");
425                }
426            },
427            WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
428                info!("Window={window_id:?} changed scale to {scale_factor}");
429            },
430            WindowEvent::ThemeChanged(theme) => {
431                info!("Theme changed to {theme:?}");
432                window.set_draw_theme(theme);
433            },
434            WindowEvent::RedrawRequested => {
435                if let Err(err) = window.draw() {
436                    error!("Error drawing window: {err}");
437                }
438                if window.continuous_redraw {
439                    window.window.request_redraw();
440                }
441            },
442            WindowEvent::Occluded(occluded) => {
443                window.set_occluded(occluded);
444            },
445            WindowEvent::CloseRequested => {
446                info!("Closing Window={window_id:?}");
447                self.windows.remove(&window_id);
448            },
449            WindowEvent::ModifiersChanged(modifiers) => {
450                window.modifiers = modifiers.state();
451                info!("Modifiers changed to {:?}", window.modifiers);
452            },
453            WindowEvent::MouseWheel { delta, .. } => match delta {
454                MouseScrollDelta::LineDelta(x, y) => {
455                    info!("Mouse wheel Line Delta: ({x},{y})");
456                },
457                MouseScrollDelta::PixelDelta(px) => {
458                    info!("Mouse wheel Pixel Delta: ({},{})", px.x, px.y);
459                },
460            },
461            WindowEvent::KeyboardInput { event, is_synthetic: false, .. } => {
462                let mods = window.modifiers;
463
464                // Dispatch actions only on press.
465                if event.state.is_pressed() {
466                    let action = if let Key::Character(ch) = event.key_without_modifiers.as_ref() {
467                        Self::process_key_binding(&ch.to_uppercase(), &mods)
468                    } else {
469                        None
470                    };
471
472                    if let Some(action) = action {
473                        self.handle_action_with_window(event_loop, window_id, action);
474                    }
475                }
476            },
477            WindowEvent::PointerButton { button, state, .. } => {
478                info!("Pointer button {button:?} {state:?}");
479                let mods = window.modifiers;
480                if let Some(action) = state
481                    .is_pressed()
482                    .then(|| Self::process_mouse_binding(button.mouse_button(), &mods))
483                    .flatten()
484                {
485                    self.handle_action_with_window(event_loop, window_id, action);
486                }
487            },
488            WindowEvent::PointerLeft { .. } => {
489                info!("Pointer left Window={window_id:?}");
490                window.cursor_left();
491            },
492            WindowEvent::PointerMoved { position, .. } => {
493                info!("Moved pointer to {position:?}");
494                window.cursor_moved(position);
495            },
496            WindowEvent::ActivationTokenDone { token: _token, .. } => {
497                #[cfg(any(x11_platform, wayland_platform))]
498                {
499                    startup_notify::set_activation_token_env(_token);
500                    if let Err(err) = self.create_window(event_loop, None) {
501                        error!("Error creating new window: {err}");
502                    }
503                }
504            },
505            WindowEvent::Ime(event) => match event {
506                Ime::Enabled => info!("IME enabled for Window={window_id:?}"),
507                Ime::Preedit(text, caret_pos) => {
508                    info!("Preedit: {}, with caret at {:?}", text, caret_pos);
509                },
510                Ime::Commit(text) => {
511                    info!("Committed: {}", text);
512                },
513                Ime::Disabled => info!("IME disabled for Window={window_id:?}"),
514            },
515            WindowEvent::PinchGesture { delta, .. } => {
516                window.zoom += delta;
517                let zoom = window.zoom;
518                if delta > 0.0 {
519                    info!("Zoomed in {delta:.5} (now: {zoom:.5})");
520                } else {
521                    info!("Zoomed out {delta:.5} (now: {zoom:.5})");
522                }
523            },
524            WindowEvent::RotationGesture { delta, .. } => {
525                window.rotated += delta;
526                let rotated = window.rotated;
527                if delta > 0.0 {
528                    info!("Rotated counterclockwise {delta:.5} (now: {rotated:.5})");
529                } else {
530                    info!("Rotated clockwise {delta:.5} (now: {rotated:.5})");
531                }
532            },
533            WindowEvent::PanGesture { delta, phase, .. } => {
534                window.panned.x += delta.x;
535                window.panned.y += delta.y;
536                info!("Panned ({delta:?})) (now: {:?}), {phase:?}", window.panned);
537            },
538            WindowEvent::DoubleTapGesture { .. } => {
539                info!("Smart zoom");
540            },
541            WindowEvent::TouchpadPressure { .. }
542            | WindowEvent::DragLeft { .. }
543            | WindowEvent::KeyboardInput { .. }
544            | WindowEvent::PointerEntered { .. }
545            | WindowEvent::DragEntered { .. }
546            | WindowEvent::DragMoved { .. }
547            | WindowEvent::DragDropped { .. }
548            | WindowEvent::Destroyed
549            | WindowEvent::Moved(_) => (),
550        }
551    }
552
553    fn device_event(
554        &mut self,
555        _event_loop: &dyn ActiveEventLoop,
556        device_id: Option<DeviceId>,
557        event: DeviceEvent,
558    ) {
559        info!("Device {device_id:?} event: {event:?}");
560    }
561
562    fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
563        info!("Ready to create surfaces");
564        self.dump_monitors(event_loop);
565
566        // Create initial window.
567        self.create_window(event_loop, None).expect("failed to create initial window");
568
569        self.print_help();
570    }
571
572    fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) {
573        if self.windows.is_empty() {
574            info!("No windows left, exiting...");
575            event_loop.exit();
576        }
577    }
578
579    fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
580        Some(self)
581    }
582}
583
584impl Drop for Application {
585    fn drop(&mut self) {
586        info!("Application exited");
587    }
588}
589
590impl ApplicationHandlerExtMacOS for Application {
591    fn standard_key_binding(
592        &mut self,
593        _event_loop: &dyn ActiveEventLoop,
594        window_id: WindowId,
595        action: &str,
596    ) {
597        info!(?window_id, ?action, "macOS standard key binding");
598    }
599}
600
601/// State of the window.
602struct WindowState {
603    /// IME input.
604    ime: bool,
605    /// Render surface.
606    ///
607    /// NOTE: This surface must be dropped before the `Window`.
608    #[cfg(not(android_platform))]
609    surface: Surface<DisplayHandle<'static>, Arc<dyn Window>>,
610    /// The actual winit Window.
611    window: Arc<dyn Window>,
612    /// The window theme we're drawing with.
613    theme: Theme,
614    /// Fill the window with animated color
615    animated_fill_color: bool,
616    /// The application start time. Used for color fill animation
617    #[cfg(not(android_platform))]
618    start_time: std::time::Instant,
619    /// Redraw continuously
620    continuous_redraw: bool,
621    /// Cursor position over the window.
622    cursor_position: Option<PhysicalPosition<f64>>,
623    /// Window modifiers state.
624    modifiers: ModifiersState,
625    /// Occlusion state of the window.
626    occluded: bool,
627    /// Current cursor grab mode.
628    cursor_grab: CursorGrabMode,
629    /// The amount of zoom into window.
630    zoom: f64,
631    /// The amount of rotation of the window.
632    rotated: f32,
633    /// The amount of pan of the window.
634    panned: PhysicalPosition<f32>,
635
636    #[cfg(macos_platform)]
637    option_as_alt: OptionAsAlt,
638
639    // Cursor states.
640    named_idx: usize,
641    custom_idx: usize,
642    cursor_hidden: bool,
643}
644
645impl WindowState {
646    fn new(app: &Application, window: Box<dyn Window>) -> Result<Self, Box<dyn Error>> {
647        let window: Arc<dyn Window> = Arc::from(window);
648
649        // SAFETY: the surface is dropped before the `window` which provided it with handle, thus
650        // it doesn't outlive it.
651        #[cfg(not(android_platform))]
652        let surface = Surface::new(app.context.as_ref().unwrap(), Arc::clone(&window))?;
653
654        let theme = window.theme().unwrap_or(Theme::Dark);
655        info!("Theme: {theme:?}");
656        let named_idx = 0;
657        window.set_cursor(CURSORS[named_idx].into());
658
659        // Allow IME out of the box.
660        let ime = true;
661        window.set_ime_allowed(ime);
662
663        let size = window.surface_size();
664        let mut state = Self {
665            #[cfg(macos_platform)]
666            option_as_alt: window.option_as_alt(),
667            custom_idx: app.custom_cursors.as_ref().map(Vec::len).unwrap_or(1) - 1,
668            cursor_grab: CursorGrabMode::None,
669            named_idx,
670            #[cfg(not(android_platform))]
671            surface,
672            window,
673            theme,
674            animated_fill_color: false,
675            continuous_redraw: false,
676            #[cfg(not(android_platform))]
677            start_time: std::time::Instant::now(),
678            ime,
679            cursor_position: Default::default(),
680            cursor_hidden: Default::default(),
681            modifiers: Default::default(),
682            occluded: Default::default(),
683            rotated: Default::default(),
684            panned: Default::default(),
685            zoom: Default::default(),
686        };
687
688        state.resize(size);
689        Ok(state)
690    }
691
692    pub fn toggle_ime(&mut self) {
693        self.ime = !self.ime;
694        self.window.set_ime_allowed(self.ime);
695        if let Some(position) = self.ime.then_some(self.cursor_position).flatten() {
696            self.window.set_ime_cursor_area(position.into(), PhysicalSize::new(20, 20).into());
697        }
698    }
699
700    pub fn minimize(&mut self) {
701        self.window.set_minimized(true);
702    }
703
704    pub fn cursor_moved(&mut self, position: PhysicalPosition<f64>) {
705        self.cursor_position = Some(position);
706        if self.ime {
707            self.window.set_ime_cursor_area(position.into(), PhysicalSize::new(20, 20).into());
708        }
709    }
710
711    pub fn cursor_left(&mut self) {
712        self.cursor_position = None;
713    }
714
715    /// Toggle maximized.
716    fn toggle_maximize(&self) {
717        let maximized = self.window.is_maximized();
718        self.window.set_maximized(!maximized);
719    }
720
721    /// Toggle window decorations.
722    fn toggle_decorations(&self) {
723        let decorated = self.window.is_decorated();
724        self.window.set_decorations(!decorated);
725    }
726
727    /// Toggle window resizable state.
728    fn toggle_resizable(&self) {
729        let resizable = self.window.is_resizable();
730        self.window.set_resizable(!resizable);
731    }
732
733    /// Toggle cursor visibility
734    fn toggle_cursor_visibility(&mut self) {
735        self.cursor_hidden = !self.cursor_hidden;
736        self.window.set_cursor_visible(!self.cursor_hidden);
737    }
738
739    /// Toggle resize increments on a window.
740    fn toggle_resize_increments(&mut self) {
741        let new_increments = match self.window.surface_resize_increments() {
742            Some(_) => None,
743            None => Some(LogicalSize::new(25.0, 25.0).into()),
744        };
745        info!("Had increments: {}", new_increments.is_none());
746        self.window.set_surface_resize_increments(new_increments);
747    }
748
749    /// Toggle fullscreen.
750    fn toggle_fullscreen(&self) {
751        let fullscreen = if self.window.fullscreen().is_some() {
752            None
753        } else {
754            Some(Fullscreen::Borderless(None))
755        };
756
757        self.window.set_fullscreen(fullscreen);
758    }
759
760    /// Cycle through the grab modes ignoring errors.
761    fn cycle_cursor_grab(&mut self) {
762        self.cursor_grab = match self.cursor_grab {
763            CursorGrabMode::None => CursorGrabMode::Confined,
764            CursorGrabMode::Confined => CursorGrabMode::Locked,
765            CursorGrabMode::Locked => CursorGrabMode::None,
766        };
767        info!("Changing cursor grab mode to {:?}", self.cursor_grab);
768        if let Err(err) = self.window.set_cursor_grab(self.cursor_grab) {
769            error!("Error setting cursor grab: {err}");
770        }
771    }
772
773    #[cfg(macos_platform)]
774    fn cycle_option_as_alt(&mut self) {
775        self.option_as_alt = match self.option_as_alt {
776            OptionAsAlt::None => OptionAsAlt::OnlyLeft,
777            OptionAsAlt::OnlyLeft => OptionAsAlt::OnlyRight,
778            OptionAsAlt::OnlyRight => OptionAsAlt::Both,
779            OptionAsAlt::Both => OptionAsAlt::None,
780        };
781        info!("Setting option as alt {:?}", self.option_as_alt);
782        self.window.set_option_as_alt(self.option_as_alt);
783    }
784
785    /// Swap the window dimensions with `request_surface_size`.
786    fn swap_dimensions(&mut self) {
787        let old_surface_size = self.window.surface_size();
788        let mut surface_size = old_surface_size;
789
790        mem::swap(&mut surface_size.width, &mut surface_size.height);
791        info!("Requesting resize from {old_surface_size:?} to {surface_size:?}");
792
793        if let Some(new_surface_size) = self.window.request_surface_size(surface_size.into()) {
794            if old_surface_size == new_surface_size {
795                info!("Inner size change got ignored");
796            } else {
797                self.resize(new_surface_size);
798            }
799        } else {
800            info!("Requesting surface size is asynchronous");
801        }
802    }
803
804    /// Pick the next cursor.
805    fn next_cursor(&mut self) {
806        self.named_idx = (self.named_idx + 1) % CURSORS.len();
807        info!("Setting cursor to \"{:?}\"", CURSORS[self.named_idx]);
808        self.window.set_cursor(Cursor::Icon(CURSORS[self.named_idx]));
809    }
810
811    /// Pick the next custom cursor.
812    fn next_custom_cursor(&mut self, custom_cursors: &[CustomCursor]) {
813        self.custom_idx = (self.custom_idx + 1) % custom_cursors.len();
814        let cursor = Cursor::Custom(custom_cursors[self.custom_idx].clone());
815        self.window.set_cursor(cursor);
816    }
817
818    /// Custom cursor from an URL.
819    #[cfg(web_platform)]
820    fn url_custom_cursor(
821        &mut self,
822        event_loop: &dyn ActiveEventLoop,
823    ) -> Result<(), Box<dyn Error>> {
824        let cursor = event_loop.create_custom_cursor(url_custom_cursor())?;
825
826        self.window.set_cursor(cursor.into());
827
828        Ok(())
829    }
830
831    /// Custom cursor from a URL.
832    #[cfg(web_platform)]
833    fn animation_custom_cursor(
834        &mut self,
835        event_loop: &dyn ActiveEventLoop,
836        custom_cursors: &[CustomCursor],
837    ) -> Result<(), Box<dyn Error>> {
838        use std::time::Duration;
839
840        let cursors = vec![
841            custom_cursors[0].clone(),
842            custom_cursors[1].clone(),
843            event_loop.create_custom_cursor(url_custom_cursor())?,
844        ];
845        let cursor = CustomCursorSource::from_animation(Duration::from_secs(3), cursors).unwrap();
846        let cursor = event_loop.create_custom_cursor(cursor)?;
847
848        self.window.set_cursor(cursor.into());
849
850        Ok(())
851    }
852
853    /// Resize the surface to the new size.
854    fn resize(&mut self, size: PhysicalSize<u32>) {
855        info!("Surface resized to {size:?}");
856        #[cfg(not(android_platform))]
857        {
858            let (width, height) = match (NonZeroU32::new(size.width), NonZeroU32::new(size.height))
859            {
860                (Some(width), Some(height)) => (width, height),
861                _ => return,
862            };
863            self.surface.resize(width, height).expect("failed to resize inner buffer");
864        }
865        self.window.request_redraw();
866    }
867
868    /// Change the theme that things are drawn in.
869    fn set_draw_theme(&mut self, theme: Theme) {
870        self.theme = theme;
871        self.window.request_redraw();
872    }
873
874    /// Show window menu.
875    fn show_menu(&self) {
876        if let Some(position) = self.cursor_position {
877            self.window.show_window_menu(position.into());
878        }
879    }
880
881    /// Drag the window.
882    fn drag_window(&self) {
883        if let Err(err) = self.window.drag_window() {
884            info!("Error starting window drag: {err}");
885        } else {
886            info!("Dragging window Window={:?}", self.window.id());
887        }
888    }
889
890    /// Drag-resize the window.
891    fn drag_resize_window(&self) {
892        let position = match self.cursor_position {
893            Some(position) => position,
894            None => {
895                info!("Drag-resize requires cursor to be inside the window");
896                return;
897            },
898        };
899
900        let win_size = self.window.surface_size();
901        let border_size = BORDER_SIZE * self.window.scale_factor();
902
903        let x_direction = if position.x < border_size {
904            ResizeDirection::West
905        } else if position.x > (win_size.width as f64 - border_size) {
906            ResizeDirection::East
907        } else {
908            // Use arbitrary direction instead of None for simplicity.
909            ResizeDirection::SouthEast
910        };
911
912        let y_direction = if position.y < border_size {
913            ResizeDirection::North
914        } else if position.y > (win_size.height as f64 - border_size) {
915            ResizeDirection::South
916        } else {
917            // Use arbitrary direction instead of None for simplicity.
918            ResizeDirection::SouthEast
919        };
920
921        let direction = match (x_direction, y_direction) {
922            (ResizeDirection::West, ResizeDirection::North) => ResizeDirection::NorthWest,
923            (ResizeDirection::West, ResizeDirection::South) => ResizeDirection::SouthWest,
924            (ResizeDirection::West, _) => ResizeDirection::West,
925            (ResizeDirection::East, ResizeDirection::North) => ResizeDirection::NorthEast,
926            (ResizeDirection::East, ResizeDirection::South) => ResizeDirection::SouthEast,
927            (ResizeDirection::East, _) => ResizeDirection::East,
928            (_, ResizeDirection::South) => ResizeDirection::South,
929            (_, ResizeDirection::North) => ResizeDirection::North,
930            _ => return,
931        };
932
933        if let Err(err) = self.window.drag_resize_window(direction) {
934            info!("Error starting window drag-resize: {err}");
935        } else {
936            info!("Drag-resizing window Window={:?}", self.window.id());
937        }
938    }
939
940    /// Change window occlusion state.
941    fn set_occluded(&mut self, occluded: bool) {
942        self.occluded = occluded;
943        if !occluded {
944            self.window.request_redraw();
945        }
946    }
947
948    /// Draw the window contents.
949    #[cfg(not(android_platform))]
950    fn draw(&mut self) -> Result<(), Box<dyn Error>> {
951        if self.occluded {
952            info!("Skipping drawing occluded window={:?}", self.window.id());
953            return Ok(());
954        }
955
956        if self.animated_fill_color {
957            fill::fill_window_with_animated_color(&*self.window, self.start_time);
958            return Ok(());
959        }
960
961        let mut buffer = self.surface.buffer_mut()?;
962
963        // Draw a different color inside the safe area
964        let surface_size = self.window.surface_size();
965        let insets = self.window.safe_area();
966        for y in 0..surface_size.height {
967            for x in 0..surface_size.width {
968                let index = y as usize * surface_size.width as usize + x as usize;
969                if insets.left <= x
970                    && x <= (surface_size.width - insets.right)
971                    && insets.top <= y
972                    && y <= (surface_size.height - insets.bottom)
973                {
974                    // In safe area
975                    buffer[index] = match self.theme {
976                        Theme::Light => 0xffe8e8e8, // Light gray
977                        Theme::Dark => 0xff525252,  // Medium gray
978                    };
979                } else {
980                    // Outside safe area
981                    buffer[index] = match self.theme {
982                        Theme::Light => 0xffffffff, // White
983                        Theme::Dark => 0xff181818,  // Dark gray
984                    };
985                }
986            }
987        }
988
989        // Present the buffer
990        self.window.pre_present_notify();
991        buffer.present()?;
992
993        Ok(())
994    }
995
996    #[cfg(android_platform)]
997    fn draw(&mut self) -> Result<(), Box<dyn Error>> {
998        info!("Drawing but without rendering...");
999        Ok(())
1000    }
1001}
1002
1003struct Binding<T: Eq> {
1004    trigger: T,
1005    mods: ModifiersState,
1006    action: Action,
1007}
1008
1009impl<T: Eq> Binding<T> {
1010    const fn new(trigger: T, mods: ModifiersState, action: Action) -> Self {
1011        Self { trigger, mods, action }
1012    }
1013
1014    fn is_triggered_by(&self, trigger: &T, mods: &ModifiersState) -> bool {
1015        &self.trigger == trigger && &self.mods == mods
1016    }
1017}
1018
1019#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1020enum Action {
1021    CloseWindow,
1022    ToggleCursorVisibility,
1023    CreateNewWindow,
1024    ToggleResizeIncrements,
1025    ToggleImeInput,
1026    ToggleDecorations,
1027    ToggleResizable,
1028    ToggleFullscreen,
1029    #[cfg(macos_platform)]
1030    ToggleSimpleFullscreen,
1031    ToggleMaximize,
1032    Minimize,
1033    NextCursor,
1034    NextCustomCursor,
1035    #[cfg(web_platform)]
1036    UrlCustomCursor,
1037    #[cfg(web_platform)]
1038    AnimationCustomCursor,
1039    CycleCursorGrab,
1040    PrintHelp,
1041    DragWindow,
1042    DragResizeWindow,
1043    ShowWindowMenu,
1044    #[cfg(macos_platform)]
1045    CycleOptionAsAlt,
1046    SetTheme(Option<Theme>),
1047    #[cfg(macos_platform)]
1048    CreateNewTab,
1049    RequestResize,
1050    DumpMonitors,
1051    Message,
1052    ToggleAnimatedFillColor,
1053    ToggleContinuousRedraw,
1054}
1055
1056impl Action {
1057    fn help(&self) -> &'static str {
1058        match self {
1059            Action::CloseWindow => "Close window",
1060            Action::ToggleCursorVisibility => "Hide cursor",
1061            Action::CreateNewWindow => "Create new window",
1062            Action::ToggleImeInput => "Toggle IME input",
1063            Action::ToggleDecorations => "Toggle decorations",
1064            Action::ToggleResizable => "Toggle window resizable state",
1065            Action::ToggleFullscreen => "Toggle fullscreen",
1066            #[cfg(macos_platform)]
1067            Action::ToggleSimpleFullscreen => "Toggle simple fullscreen",
1068            Action::ToggleMaximize => "Maximize",
1069            Action::Minimize => "Minimize",
1070            Action::ToggleResizeIncrements => "Use resize increments when resizing window",
1071            Action::NextCursor => "Advance the cursor to the next value",
1072            Action::NextCustomCursor => "Advance custom cursor to the next value",
1073            #[cfg(web_platform)]
1074            Action::UrlCustomCursor => "Custom cursor from an URL",
1075            #[cfg(web_platform)]
1076            Action::AnimationCustomCursor => "Custom cursor from an animation",
1077            Action::CycleCursorGrab => "Cycle through cursor grab mode",
1078            Action::PrintHelp => "Print help",
1079            Action::DragWindow => "Start window drag",
1080            Action::DragResizeWindow => "Start window drag-resize",
1081            Action::ShowWindowMenu => "Show window menu",
1082            #[cfg(macos_platform)]
1083            Action::CycleOptionAsAlt => "Cycle option as alt mode",
1084            Action::SetTheme(None) => "Change to the system theme",
1085            Action::SetTheme(Some(Theme::Light)) => "Change to a light theme",
1086            Action::SetTheme(Some(Theme::Dark)) => "Change to a dark theme",
1087            #[cfg(macos_platform)]
1088            Action::CreateNewTab => "Create new tab",
1089            Action::RequestResize => "Request a resize",
1090            #[cfg(not(web_platform))]
1091            Action::DumpMonitors => "Dump monitor information",
1092            #[cfg(web_platform)]
1093            Action::DumpMonitors => {
1094                "Request permission to query detailed monitor information and dump monitor \
1095                 information"
1096            },
1097            Action::Message => "Prints a message through a user wake up",
1098            Action::ToggleAnimatedFillColor => "Toggle animated fill color",
1099            Action::ToggleContinuousRedraw => "Toggle continuous redraw",
1100        }
1101    }
1102}
1103
1104impl fmt::Display for Action {
1105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1106        Debug::fmt(&self, f)
1107    }
1108}
1109
1110fn decode_cursor(bytes: &[u8]) -> CustomCursorSource {
1111    let img = image::load_from_memory(bytes).unwrap().to_rgba8();
1112    let samples = img.into_flat_samples();
1113    let (_, w, h) = samples.extents();
1114    let (w, h) = (w as u16, h as u16);
1115    CustomCursorSource::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap()
1116}
1117
1118#[cfg(web_platform)]
1119fn url_custom_cursor() -> CustomCursorSource {
1120    use std::sync::atomic::{AtomicU64, Ordering};
1121
1122    static URL_COUNTER: AtomicU64 = AtomicU64::new(0);
1123
1124    CustomCursorSource::Url {
1125        hotspot_x: 64,
1126        hotspot_y: 64,
1127        url: format!(
1128            "https://picsum.photos/128?random={}",
1129            URL_COUNTER.fetch_add(1, Ordering::Relaxed)
1130        ),
1131    }
1132}
1133
1134fn load_icon(bytes: &[u8]) -> Icon {
1135    let (icon_rgba, icon_width, icon_height) = {
1136        let image = image::load_from_memory(bytes).unwrap().into_rgba8();
1137        let (width, height) = image.dimensions();
1138        let rgba = image.into_raw();
1139        (rgba, width, height)
1140    };
1141    RgbaIcon::new(icon_rgba, icon_width, icon_height).expect("Failed to open icon").into()
1142}
1143
1144fn modifiers_to_string(mods: ModifiersState) -> String {
1145    let mut mods_line = String::new();
1146    // Always add + since it's printed as a part of the bindings.
1147    for (modifier, desc) in [
1148        (
1149            ModifiersState::META,
1150            if cfg!(target_os = "windows") {
1151                "Win+"
1152            } else if cfg!(target_vendor = "apple") {
1153                "Cmd+"
1154            } else {
1155                "Super+"
1156            },
1157        ),
1158        (ModifiersState::ALT, "Alt+"),
1159        (ModifiersState::CONTROL, "Ctrl+"),
1160        (ModifiersState::SHIFT, "Shift+"),
1161    ] {
1162        if !mods.contains(modifier) {
1163            continue;
1164        }
1165
1166        mods_line.push_str(desc);
1167    }
1168    mods_line
1169}
1170
1171fn mouse_button_to_string(button: MouseButton) -> &'static str {
1172    match button {
1173        MouseButton::Left => "LMB",
1174        MouseButton::Right => "RMB",
1175        MouseButton::Middle => "MMB",
1176        MouseButton::Back => "Back",
1177        MouseButton::Forward => "Forward",
1178        MouseButton::Other(_) => "",
1179    }
1180}
1181
1182#[cfg(web_platform)]
1183fn window_attributes_web() -> WindowAttributesWeb {
1184    WindowAttributesWeb::default().with_append(true)
1185}
1186
1187#[cfg(wayland_platform)]
1188fn window_attributes_wayland(event_loop: &dyn ActiveEventLoop) -> WindowAttributesWayland {
1189    let mut window_attributes = WindowAttributesWayland::default();
1190
1191    if let Some(token) = event_loop.read_token_from_env() {
1192        startup_notify::reset_activation_token_env();
1193        info!("Using token {:?} to activate a window", token);
1194        window_attributes = window_attributes.with_activation_token(token);
1195    }
1196
1197    window_attributes
1198}
1199
1200#[cfg(x11_platform)]
1201fn window_attributes_x11(
1202    event_loop: &dyn ActiveEventLoop,
1203) -> Result<WindowAttributesX11, Box<dyn Error>> {
1204    let mut window_attributes = WindowAttributesX11::default();
1205
1206    #[cfg(any(x11_platform, wayland_platform))]
1207    if let Some(token) = event_loop.read_token_from_env() {
1208        startup_notify::reset_activation_token_env();
1209        info!("Using token {:?} to activate a window", token);
1210        window_attributes = window_attributes.with_activation_token(token);
1211    }
1212
1213    match std::env::var("X11_VISUAL_ID") {
1214        Ok(visual_id_str) => {
1215            info!("Using X11 visual id {visual_id_str}");
1216            let visual_id = visual_id_str.parse()?;
1217            window_attributes = window_attributes.with_x11_visual(visual_id);
1218        },
1219        Err(_) => info!("Set the X11_VISUAL_ID env variable to request specific X11 visual"),
1220    }
1221
1222    match std::env::var("X11_SCREEN_ID") {
1223        Ok(screen_id_str) => {
1224            info!("Placing the window on X11 screen {screen_id_str}");
1225            let screen_id = screen_id_str.parse()?;
1226            window_attributes = window_attributes.with_x11_screen(screen_id);
1227        },
1228        Err(_) => {
1229            info!("Set the X11_SCREEN_ID env variable to place the window on non-default screen")
1230        },
1231    }
1232
1233    Ok(window_attributes)
1234}
1235
1236/// Cursor list to cycle through.
1237const CURSORS: &[CursorIcon] = &[
1238    CursorIcon::Default,
1239    CursorIcon::Crosshair,
1240    CursorIcon::Pointer,
1241    CursorIcon::Move,
1242    CursorIcon::Text,
1243    CursorIcon::Wait,
1244    CursorIcon::Help,
1245    CursorIcon::Progress,
1246    CursorIcon::NotAllowed,
1247    CursorIcon::ContextMenu,
1248    CursorIcon::Cell,
1249    CursorIcon::VerticalText,
1250    CursorIcon::Alias,
1251    CursorIcon::Copy,
1252    CursorIcon::NoDrop,
1253    CursorIcon::Grab,
1254    CursorIcon::Grabbing,
1255    CursorIcon::AllScroll,
1256    CursorIcon::ZoomIn,
1257    CursorIcon::ZoomOut,
1258    CursorIcon::EResize,
1259    CursorIcon::NResize,
1260    CursorIcon::NeResize,
1261    CursorIcon::NwResize,
1262    CursorIcon::SResize,
1263    CursorIcon::SeResize,
1264    CursorIcon::SwResize,
1265    CursorIcon::WResize,
1266    CursorIcon::EwResize,
1267    CursorIcon::NsResize,
1268    CursorIcon::NeswResize,
1269    CursorIcon::NwseResize,
1270    CursorIcon::ColResize,
1271    CursorIcon::RowResize,
1272];
1273
1274const KEY_BINDINGS: &[Binding<&'static str>] = &[
1275    Binding::new("Q", ModifiersState::CONTROL, Action::CloseWindow),
1276    Binding::new("H", ModifiersState::CONTROL, Action::PrintHelp),
1277    Binding::new("F", ModifiersState::SHIFT, Action::ToggleAnimatedFillColor),
1278    Binding::new("F", ModifiersState::CONTROL, Action::ToggleFullscreen),
1279    #[cfg(macos_platform)]
1280    Binding::new("F", ModifiersState::ALT, Action::ToggleSimpleFullscreen),
1281    Binding::new("D", ModifiersState::CONTROL, Action::ToggleDecorations),
1282    Binding::new("I", ModifiersState::CONTROL, Action::ToggleImeInput),
1283    Binding::new("L", ModifiersState::CONTROL, Action::CycleCursorGrab),
1284    Binding::new("P", ModifiersState::CONTROL, Action::ToggleResizeIncrements),
1285    Binding::new("R", ModifiersState::CONTROL, Action::ToggleResizable),
1286    Binding::new("R", ModifiersState::ALT, Action::RequestResize),
1287    Binding::new("R", ModifiersState::SHIFT, Action::ToggleContinuousRedraw),
1288    // M.
1289    Binding::new("M", ModifiersState::CONTROL.union(ModifiersState::ALT), Action::DumpMonitors),
1290    Binding::new("M", ModifiersState::CONTROL, Action::ToggleMaximize),
1291    Binding::new("M", ModifiersState::ALT, Action::Minimize),
1292    // N.
1293    Binding::new("N", ModifiersState::CONTROL, Action::CreateNewWindow),
1294    // C.
1295    Binding::new("C", ModifiersState::CONTROL, Action::NextCursor),
1296    Binding::new("C", ModifiersState::ALT, Action::NextCustomCursor),
1297    #[cfg(web_platform)]
1298    Binding::new(
1299        "C",
1300        ModifiersState::CONTROL.union(ModifiersState::SHIFT),
1301        Action::UrlCustomCursor,
1302    ),
1303    #[cfg(web_platform)]
1304    Binding::new(
1305        "C",
1306        ModifiersState::ALT.union(ModifiersState::SHIFT),
1307        Action::AnimationCustomCursor,
1308    ),
1309    Binding::new("Z", ModifiersState::CONTROL, Action::ToggleCursorVisibility),
1310    // K.
1311    Binding::new("K", ModifiersState::empty(), Action::SetTheme(None)),
1312    Binding::new("K", ModifiersState::META, Action::SetTheme(Some(Theme::Light))),
1313    Binding::new("K", ModifiersState::CONTROL, Action::SetTheme(Some(Theme::Dark))),
1314    #[cfg(macos_platform)]
1315    Binding::new("T", ModifiersState::META, Action::CreateNewTab),
1316    #[cfg(macos_platform)]
1317    Binding::new("O", ModifiersState::CONTROL, Action::CycleOptionAsAlt),
1318    Binding::new("S", ModifiersState::CONTROL, Action::Message),
1319];
1320
1321const MOUSE_BINDINGS: &[Binding<MouseButton>] = &[
1322    Binding::new(MouseButton::Left, ModifiersState::ALT, Action::DragResizeWindow),
1323    Binding::new(MouseButton::Left, ModifiersState::CONTROL, Action::DragWindow),
1324    Binding::new(MouseButton::Right, ModifiersState::CONTROL, Action::ShowWindowMenu),
1325];