2024-01-31 17:29:59 +04:00
|
|
|
//! Simple winit application.
|
2022-06-10 13:43:33 +03:00
|
|
|
|
2024-01-31 17:29:59 +04:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
use std::error::Error;
|
|
|
|
|
use std::fmt::Debug;
|
|
|
|
|
#[cfg(not(any(android_platform, ios_platform)))]
|
|
|
|
|
use std::num::NonZeroU32;
|
2024-07-23 20:33:10 +02:00
|
|
|
use std::sync::mpsc::{self, Receiver, Sender};
|
2024-04-18 21:52:38 +02:00
|
|
|
use std::sync::Arc;
|
2024-04-18 19:43:39 +02:00
|
|
|
use std::{fmt, mem};
|
2024-01-31 17:29:59 +04:00
|
|
|
|
2024-04-18 19:43:39 +02:00
|
|
|
use ::tracing::{error, info};
|
2024-01-31 17:29:59 +04:00
|
|
|
use cursor_icon::CursorIcon;
|
|
|
|
|
#[cfg(not(any(android_platform, ios_platform)))]
|
2024-04-18 21:52:38 +02:00
|
|
|
use rwh_06::{DisplayHandle, HasDisplayHandle};
|
2024-01-31 17:29:59 +04:00
|
|
|
#[cfg(not(any(android_platform, ios_platform)))]
|
|
|
|
|
use softbuffer::{Context, Surface};
|
2024-02-23 14:37:21 +04:00
|
|
|
use winit::application::ApplicationHandler;
|
2024-01-31 17:29:59 +04:00
|
|
|
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
|
2024-09-06 17:20:11 +03:00
|
|
|
use winit::error::RequestError;
|
2024-02-23 14:37:21 +04:00
|
|
|
use winit::event::{DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, WindowEvent};
|
2024-01-31 17:29:59 +04:00
|
|
|
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
|
|
|
|
use winit::keyboard::{Key, ModifiersState};
|
|
|
|
|
#[cfg(macos_platform)]
|
|
|
|
|
use winit::platform::macos::{OptionAsAlt, WindowAttributesExtMacOS, WindowExtMacOS};
|
|
|
|
|
#[cfg(any(x11_platform, wayland_platform))]
|
|
|
|
|
use winit::platform::startup_notify::{
|
|
|
|
|
self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify, WindowExtStartupNotify,
|
2019-06-21 11:33:15 -04:00
|
|
|
};
|
2024-07-23 20:33:10 +02:00
|
|
|
#[cfg(web_platform)]
|
|
|
|
|
use winit::platform::web::{ActiveEventLoopExtWeb, CustomCursorExtWeb, WindowAttributesExtWeb};
|
2024-01-31 17:29:59 +04:00
|
|
|
use winit::window::{
|
2024-02-03 07:27:17 +04:00
|
|
|
Cursor, CursorGrabMode, CustomCursor, CustomCursorSource, Fullscreen, Icon, ResizeDirection,
|
2024-08-23 23:40:27 +03:00
|
|
|
Theme, Window, WindowAttributes, WindowId,
|
2024-07-07 18:38:50 +02:00
|
|
|
};
|
2014-07-27 10:55:37 +02:00
|
|
|
|
2024-04-18 19:43:39 +02:00
|
|
|
#[path = "util/tracing.rs"]
|
|
|
|
|
mod tracing;
|
|
|
|
|
|
2024-01-31 17:29:59 +04:00
|
|
|
/// The amount of points to around the window for drag resize direction calculations.
|
|
|
|
|
const BORDER_SIZE: f64 = 20.;
|
|
|
|
|
|
|
|
|
|
fn main() -> Result<(), Box<dyn Error>> {
|
2024-04-18 19:43:39 +02:00
|
|
|
#[cfg(web_platform)]
|
|
|
|
|
console_error_panic_hook::set_once();
|
|
|
|
|
|
|
|
|
|
tracing::init();
|
|
|
|
|
|
2024-06-24 13:04:55 +03:00
|
|
|
let event_loop = EventLoop::new()?;
|
2024-07-23 20:33:10 +02:00
|
|
|
let (sender, receiver) = mpsc::channel();
|
2024-01-31 17:29:59 +04:00
|
|
|
|
|
|
|
|
// Wire the user event from another thread.
|
|
|
|
|
#[cfg(not(web_platform))]
|
2024-07-23 20:33:10 +02:00
|
|
|
{
|
|
|
|
|
let event_loop_proxy = event_loop.create_proxy();
|
|
|
|
|
let sender = sender.clone();
|
|
|
|
|
std::thread::spawn(move || {
|
|
|
|
|
// Wake up the `event_loop` once every second and dispatch a custom event
|
|
|
|
|
// from a different thread.
|
|
|
|
|
info!("Starting to send user event every second");
|
|
|
|
|
loop {
|
|
|
|
|
let _ = sender.send(Action::Message);
|
|
|
|
|
event_loop_proxy.wake_up();
|
|
|
|
|
std::thread::sleep(std::time::Duration::from_secs(1));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-01-31 17:29:59 +04:00
|
|
|
|
2024-07-23 20:33:10 +02:00
|
|
|
let app = Application::new(&event_loop, receiver, sender);
|
2024-07-11 15:38:09 +02:00
|
|
|
Ok(event_loop.run_app(app)?)
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Application state and event handling.
|
|
|
|
|
struct Application {
|
2024-07-23 20:33:10 +02:00
|
|
|
/// Trigger actions through proxy wake up.
|
|
|
|
|
receiver: Receiver<Action>,
|
|
|
|
|
sender: Sender<Action>,
|
2024-01-31 17:29:59 +04:00
|
|
|
/// Custom cursors assets.
|
2024-09-06 17:20:11 +03:00
|
|
|
custom_cursors: Result<Vec<CustomCursor>, RequestError>,
|
2024-01-31 17:29:59 +04:00
|
|
|
/// Application icon.
|
|
|
|
|
icon: Icon,
|
|
|
|
|
windows: HashMap<WindowId, WindowState>,
|
|
|
|
|
/// Drawing context.
|
|
|
|
|
///
|
|
|
|
|
/// With OpenGL it could be EGLDisplay.
|
|
|
|
|
#[cfg(not(any(android_platform, ios_platform)))]
|
2024-04-18 21:52:38 +02:00
|
|
|
context: Option<Context<DisplayHandle<'static>>>,
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Application {
|
2024-07-23 20:33:10 +02:00
|
|
|
fn new(event_loop: &EventLoop, receiver: Receiver<Action>, sender: Sender<Action>) -> Self {
|
2024-02-23 14:37:21 +04:00
|
|
|
// SAFETY: we drop the context right before the event loop is stopped, thus making it safe.
|
2024-01-31 17:29:59 +04:00
|
|
|
#[cfg(not(any(android_platform, ios_platform)))]
|
2024-04-18 21:52:38 +02:00
|
|
|
let context = Some(
|
|
|
|
|
Context::new(unsafe {
|
|
|
|
|
std::mem::transmute::<DisplayHandle<'_>, DisplayHandle<'static>>(
|
|
|
|
|
event_loop.display_handle().unwrap(),
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
.unwrap(),
|
|
|
|
|
);
|
2023-06-19 11:46:38 -07:00
|
|
|
|
2024-01-31 17:29:59 +04:00
|
|
|
// You'll have to choose an icon size at your own discretion. On X11, the desired size
|
|
|
|
|
// varies by WM, and on Windows, you still have to account for screen scaling. Here
|
|
|
|
|
// we use 32px, since it seems to work well enough in most cases. Be careful about
|
|
|
|
|
// going too high, or you'll be bitten by the low-quality downscaling built into the
|
|
|
|
|
// WM.
|
2024-04-18 19:43:39 +02:00
|
|
|
let icon = load_icon(include_bytes!("data/icon.png"));
|
2024-01-31 17:29:59 +04:00
|
|
|
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Loading cursor assets");
|
2024-08-06 18:57:03 +02:00
|
|
|
let custom_cursors = [
|
2024-02-03 07:27:17 +04:00
|
|
|
event_loop.create_custom_cursor(decode_cursor(include_bytes!("data/cross.png"))),
|
|
|
|
|
event_loop.create_custom_cursor(decode_cursor(include_bytes!("data/cross2.png"))),
|
|
|
|
|
event_loop.create_custom_cursor(decode_cursor(include_bytes!("data/gradient.png"))),
|
2024-08-06 18:57:03 +02:00
|
|
|
]
|
|
|
|
|
.into_iter()
|
|
|
|
|
.collect();
|
2024-02-03 07:27:17 +04:00
|
|
|
|
2024-01-31 17:29:59 +04:00
|
|
|
Self {
|
2024-07-23 20:33:10 +02:00
|
|
|
receiver,
|
|
|
|
|
sender,
|
2024-01-31 17:29:59 +04:00
|
|
|
#[cfg(not(any(android_platform, ios_platform)))]
|
|
|
|
|
context,
|
2024-02-03 07:27:17 +04:00
|
|
|
custom_cursors,
|
2024-01-31 17:29:59 +04:00
|
|
|
icon,
|
|
|
|
|
windows: Default::default(),
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-01-28 15:01:59 +01:00
|
|
|
|
2024-01-31 17:29:59 +04:00
|
|
|
fn create_window(
|
|
|
|
|
&mut self,
|
2024-08-06 21:02:53 +03:00
|
|
|
event_loop: &dyn ActiveEventLoop,
|
2024-01-31 17:29:59 +04:00
|
|
|
_tab_id: Option<String>,
|
|
|
|
|
) -> Result<WindowId, Box<dyn Error>> {
|
|
|
|
|
// TODO read-out activation token.
|
|
|
|
|
|
|
|
|
|
#[allow(unused_mut)]
|
2024-08-23 23:40:27 +03:00
|
|
|
let mut window_attributes = WindowAttributes::default()
|
2024-01-31 17:29:59 +04:00
|
|
|
.with_title("Winit window")
|
|
|
|
|
.with_transparent(true)
|
|
|
|
|
.with_window_icon(Some(self.icon.clone()));
|
|
|
|
|
|
|
|
|
|
#[cfg(any(x11_platform, wayland_platform))]
|
|
|
|
|
if let Some(token) = event_loop.read_token_from_env() {
|
|
|
|
|
startup_notify::reset_activation_token_env();
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Using token {:?} to activate a window", token);
|
2024-01-31 17:29:59 +04:00
|
|
|
window_attributes = window_attributes.with_activation_token(token);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(macos_platform)]
|
|
|
|
|
if let Some(tab_id) = _tab_id {
|
|
|
|
|
window_attributes = window_attributes.with_tabbing_identifier(&tab_id);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-18 19:43:39 +02:00
|
|
|
#[cfg(web_platform)]
|
|
|
|
|
{
|
|
|
|
|
window_attributes = window_attributes.with_append(true);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-31 17:29:59 +04:00
|
|
|
let window = event_loop.create_window(window_attributes)?;
|
|
|
|
|
|
|
|
|
|
#[cfg(ios_platform)]
|
|
|
|
|
{
|
|
|
|
|
use winit::platform::ios::WindowExtIOS;
|
|
|
|
|
window.recognize_doubletap_gesture(true);
|
|
|
|
|
window.recognize_pinch_gesture(true);
|
|
|
|
|
window.recognize_rotation_gesture(true);
|
2024-04-27 09:55:04 -04:00
|
|
|
window.recognize_pan_gesture(true, 2, 2);
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let window_state = WindowState::new(self, window)?;
|
|
|
|
|
let window_id = window_state.window.id();
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Created new window with id={window_id:?}");
|
2024-01-31 17:29:59 +04:00
|
|
|
self.windows.insert(window_id, window_state);
|
|
|
|
|
Ok(window_id)
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-06 21:02:53 +03:00
|
|
|
fn handle_action_from_proxy(&mut self, _event_loop: &dyn ActiveEventLoop, action: Action) {
|
2024-07-23 20:33:10 +02:00
|
|
|
match action {
|
|
|
|
|
#[cfg(web_platform)]
|
|
|
|
|
Action::DumpMonitors => self.dump_monitors(_event_loop),
|
|
|
|
|
Action::Message => {
|
|
|
|
|
info!("User wake up");
|
|
|
|
|
},
|
|
|
|
|
_ => unreachable!("Tried to execute invalid action without `WindowId`"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn handle_action_with_window(
|
|
|
|
|
&mut self,
|
2024-08-06 21:02:53 +03:00
|
|
|
event_loop: &dyn ActiveEventLoop,
|
2024-07-23 20:33:10 +02:00
|
|
|
window_id: WindowId,
|
|
|
|
|
action: Action,
|
|
|
|
|
) {
|
2024-01-31 17:29:59 +04:00
|
|
|
// let cursor_position = self.cursor_position;
|
|
|
|
|
let window = self.windows.get_mut(&window_id).unwrap();
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Executing action: {action:?}");
|
2024-01-31 17:29:59 +04:00
|
|
|
match action {
|
|
|
|
|
Action::CloseWindow => {
|
|
|
|
|
let _ = self.windows.remove(&window_id);
|
|
|
|
|
},
|
|
|
|
|
Action::CreateNewWindow => {
|
|
|
|
|
#[cfg(any(x11_platform, wayland_platform))]
|
|
|
|
|
if let Err(err) = window.window.request_activation_token() {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Failed to get activation token: {err}");
|
2024-01-31 17:29:59 +04:00
|
|
|
} else {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Err(err) = self.create_window(event_loop, None) {
|
2024-04-18 19:43:39 +02:00
|
|
|
error!("Error creating new window: {err}");
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
Action::ToggleResizeIncrements => window.toggle_resize_increments(),
|
|
|
|
|
Action::ToggleCursorVisibility => window.toggle_cursor_visibility(),
|
|
|
|
|
Action::ToggleResizable => window.toggle_resizable(),
|
|
|
|
|
Action::ToggleDecorations => window.toggle_decorations(),
|
|
|
|
|
Action::ToggleFullscreen => window.toggle_fullscreen(),
|
|
|
|
|
Action::ToggleMaximize => window.toggle_maximize(),
|
|
|
|
|
Action::ToggleImeInput => window.toggle_ime(),
|
|
|
|
|
Action::Minimize => window.minimize(),
|
|
|
|
|
Action::NextCursor => window.next_cursor(),
|
2024-08-06 18:57:03 +02:00
|
|
|
Action::NextCustomCursor => {
|
|
|
|
|
if let Err(err) = self.custom_cursors.as_ref().map(|c| window.next_custom_cursor(c))
|
|
|
|
|
{
|
|
|
|
|
error!("Error creating custom cursor: {err}");
|
|
|
|
|
}
|
|
|
|
|
},
|
2024-04-18 19:43:39 +02:00
|
|
|
#[cfg(web_platform)]
|
2024-08-06 18:57:03 +02:00
|
|
|
Action::UrlCustomCursor => {
|
|
|
|
|
if let Err(err) = window.url_custom_cursor(event_loop) {
|
|
|
|
|
error!("Error creating custom cursor from URL: {err}");
|
|
|
|
|
}
|
|
|
|
|
},
|
2024-04-18 19:43:39 +02:00
|
|
|
#[cfg(web_platform)]
|
|
|
|
|
Action::AnimationCustomCursor => {
|
2024-08-06 18:57:03 +02:00
|
|
|
if let Err(err) = self
|
|
|
|
|
.custom_cursors
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|c| window.animation_custom_cursor(event_loop, c))
|
|
|
|
|
{
|
|
|
|
|
error!("Error creating animated custom cursor: {err}");
|
|
|
|
|
}
|
2024-04-18 19:43:39 +02:00
|
|
|
},
|
2024-01-31 17:29:59 +04:00
|
|
|
Action::CycleCursorGrab => window.cycle_cursor_grab(),
|
|
|
|
|
Action::DragWindow => window.drag_window(),
|
|
|
|
|
Action::DragResizeWindow => window.drag_resize_window(),
|
|
|
|
|
Action::ShowWindowMenu => window.show_menu(),
|
|
|
|
|
Action::PrintHelp => self.print_help(),
|
|
|
|
|
#[cfg(macos_platform)]
|
|
|
|
|
Action::CycleOptionAsAlt => window.cycle_option_as_alt(),
|
2024-06-20 16:05:34 +02:00
|
|
|
Action::SetTheme(theme) => {
|
|
|
|
|
window.window.set_theme(theme);
|
|
|
|
|
// Get the resulting current theme to draw with
|
|
|
|
|
let actual_theme = theme.or_else(|| window.window.theme()).unwrap_or(Theme::Dark);
|
|
|
|
|
window.set_draw_theme(actual_theme);
|
|
|
|
|
},
|
2024-01-31 17:29:59 +04:00
|
|
|
#[cfg(macos_platform)]
|
|
|
|
|
Action::CreateNewTab => {
|
|
|
|
|
let tab_id = window.window.tabbing_identifier();
|
|
|
|
|
if let Err(err) = self.create_window(event_loop, Some(tab_id)) {
|
2024-04-18 19:43:39 +02:00
|
|
|
error!("Error creating new window: {err}");
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
},
|
2024-03-28 19:39:19 +00:00
|
|
|
Action::RequestResize => window.swap_dimensions(),
|
2024-07-23 20:33:10 +02:00
|
|
|
#[cfg(web_platform)]
|
|
|
|
|
Action::DumpMonitors => {
|
|
|
|
|
let future = event_loop.request_detailed_monitor_permission();
|
|
|
|
|
let proxy = event_loop.create_proxy();
|
|
|
|
|
let sender = self.sender.clone();
|
|
|
|
|
wasm_bindgen_futures::spawn_local(async move {
|
|
|
|
|
if let Err(error) = future.await {
|
|
|
|
|
error!("{error}")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let _ = sender.send(Action::DumpMonitors);
|
|
|
|
|
proxy.wake_up();
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
#[cfg(not(web_platform))]
|
|
|
|
|
Action::DumpMonitors => self.dump_monitors(event_loop),
|
|
|
|
|
Action::Message => {
|
|
|
|
|
self.sender.send(Action::Message).unwrap();
|
|
|
|
|
event_loop.create_proxy().wake_up();
|
|
|
|
|
},
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
}
|
2014-07-27 10:55:37 +02:00
|
|
|
|
2024-08-06 21:02:53 +03:00
|
|
|
fn dump_monitors(&self, event_loop: &dyn ActiveEventLoop) {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Monitors information");
|
2024-02-23 14:37:21 +04:00
|
|
|
let primary_monitor = event_loop.primary_monitor();
|
|
|
|
|
for monitor in event_loop.available_monitors() {
|
|
|
|
|
let intro = if primary_monitor.as_ref() == Some(&monitor) {
|
|
|
|
|
"Primary monitor"
|
|
|
|
|
} else {
|
|
|
|
|
"Monitor"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if let Some(name) = monitor.name() {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("{intro}: {name}");
|
2024-02-23 14:37:21 +04:00
|
|
|
} else {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("{intro}: [no name]");
|
2024-02-23 14:37:21 +04:00
|
|
|
}
|
|
|
|
|
|
2024-07-21 00:40:57 +02:00
|
|
|
if let Some(current_mode) = monitor.current_video_mode() {
|
|
|
|
|
let PhysicalSize { width, height } = current_mode.size();
|
2024-07-23 19:59:37 +02:00
|
|
|
let bits =
|
|
|
|
|
current_mode.bit_depth().map(|bits| format!("x{bits}")).unwrap_or_default();
|
|
|
|
|
let m_hz = current_mode
|
|
|
|
|
.refresh_rate_millihertz()
|
|
|
|
|
.map(|m_hz| format!(" @ {}.{} Hz", m_hz.get() / 1000, m_hz.get() % 1000))
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
info!(" {width}x{height}{bits}{m_hz}");
|
2024-07-21 00:40:57 +02:00
|
|
|
}
|
2024-02-23 14:37:21 +04:00
|
|
|
|
2024-07-23 19:59:37 +02:00
|
|
|
if let Some(PhysicalPosition { x, y }) = monitor.position() {
|
|
|
|
|
info!(" Position: {x},{y}");
|
|
|
|
|
}
|
2024-02-23 14:37:21 +04:00
|
|
|
|
2024-04-18 19:43:39 +02:00
|
|
|
info!(" Scale factor: {}", monitor.scale_factor());
|
2024-02-23 14:37:21 +04:00
|
|
|
|
2024-04-18 19:43:39 +02:00
|
|
|
info!(" Available modes (width x height x bit-depth):");
|
2024-02-23 14:37:21 +04:00
|
|
|
for mode in monitor.video_modes() {
|
|
|
|
|
let PhysicalSize { width, height } = mode.size();
|
2024-07-23 19:59:37 +02:00
|
|
|
let bits = mode.bit_depth().map(|bits| format!("x{bits}")).unwrap_or_default();
|
|
|
|
|
let m_hz = mode
|
|
|
|
|
.refresh_rate_millihertz()
|
|
|
|
|
.map(|m_hz| format!(" @ {}.{} Hz", m_hz.get() / 1000, m_hz.get() % 1000))
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
info!(" {width}x{height}{bits}{m_hz}");
|
2024-02-23 14:37:21 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Process the key binding.
|
|
|
|
|
fn process_key_binding(key: &str, mods: &ModifiersState) -> Option<Action> {
|
|
|
|
|
KEY_BINDINGS
|
|
|
|
|
.iter()
|
|
|
|
|
.find_map(|binding| binding.is_triggered_by(&key, mods).then_some(binding.action))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Process mouse binding.
|
|
|
|
|
fn process_mouse_binding(button: MouseButton, mods: &ModifiersState) -> Option<Action> {
|
|
|
|
|
MOUSE_BINDINGS
|
|
|
|
|
.iter()
|
|
|
|
|
.find_map(|binding| binding.is_triggered_by(&button, mods).then_some(binding.action))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn print_help(&self) {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Keyboard bindings:");
|
2024-02-23 14:37:21 +04:00
|
|
|
for binding in KEY_BINDINGS {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!(
|
2024-02-23 14:37:21 +04:00
|
|
|
"{}{:<10} - {} ({})",
|
|
|
|
|
modifiers_to_string(binding.mods),
|
|
|
|
|
binding.trigger,
|
|
|
|
|
binding.action,
|
|
|
|
|
binding.action.help(),
|
|
|
|
|
);
|
|
|
|
|
}
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Mouse bindings:");
|
2024-02-23 14:37:21 +04:00
|
|
|
for binding in MOUSE_BINDINGS {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!(
|
2024-02-23 14:37:21 +04:00
|
|
|
"{}{:<10} - {} ({})",
|
|
|
|
|
modifiers_to_string(binding.mods),
|
|
|
|
|
mouse_button_to_string(binding.trigger),
|
|
|
|
|
binding.action,
|
|
|
|
|
binding.action.help(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-24 13:04:55 +03:00
|
|
|
impl ApplicationHandler for Application {
|
2024-08-06 21:02:53 +03:00
|
|
|
fn proxy_wake_up(&mut self, event_loop: &dyn ActiveEventLoop) {
|
2024-07-23 20:33:10 +02:00
|
|
|
while let Ok(action) = self.receiver.try_recv() {
|
|
|
|
|
self.handle_action_from_proxy(event_loop, action)
|
|
|
|
|
}
|
2024-02-23 14:37:21 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn window_event(
|
2024-01-31 17:29:59 +04:00
|
|
|
&mut self,
|
2024-08-06 21:02:53 +03:00
|
|
|
event_loop: &dyn ActiveEventLoop,
|
2024-01-31 17:29:59 +04:00
|
|
|
window_id: WindowId,
|
|
|
|
|
event: WindowEvent,
|
|
|
|
|
) {
|
|
|
|
|
let window = match self.windows.get_mut(&window_id) {
|
|
|
|
|
Some(window) => window,
|
|
|
|
|
None => return,
|
|
|
|
|
};
|
2015-06-16 13:48:08 +02:00
|
|
|
|
|
|
|
|
match event {
|
2024-09-04 15:04:48 +02:00
|
|
|
WindowEvent::SurfaceResized(size) => {
|
2024-01-31 17:29:59 +04:00
|
|
|
window.resize(size);
|
|
|
|
|
},
|
|
|
|
|
WindowEvent::Focused(focused) => {
|
|
|
|
|
if focused {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Window={window_id:?} focused");
|
2024-01-31 17:29:59 +04:00
|
|
|
} else {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Window={window_id:?} unfocused");
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Window={window_id:?} changed scale to {scale_factor}");
|
2024-01-31 17:29:59 +04:00
|
|
|
},
|
|
|
|
|
WindowEvent::ThemeChanged(theme) => {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Theme changed to {theme:?}");
|
2024-06-20 16:05:34 +02:00
|
|
|
window.set_draw_theme(theme);
|
2024-01-31 17:29:59 +04:00
|
|
|
},
|
|
|
|
|
WindowEvent::RedrawRequested => {
|
|
|
|
|
if let Err(err) = window.draw() {
|
2024-04-18 19:43:39 +02:00
|
|
|
error!("Error drawing window: {err}");
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
WindowEvent::Occluded(occluded) => {
|
|
|
|
|
window.set_occluded(occluded);
|
|
|
|
|
},
|
|
|
|
|
WindowEvent::CloseRequested => {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Closing Window={window_id:?}");
|
2024-01-31 17:29:59 +04:00
|
|
|
self.windows.remove(&window_id);
|
|
|
|
|
},
|
|
|
|
|
WindowEvent::ModifiersChanged(modifiers) => {
|
|
|
|
|
window.modifiers = modifiers.state();
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Modifiers changed to {:?}", window.modifiers);
|
2024-01-31 17:29:59 +04:00
|
|
|
},
|
|
|
|
|
WindowEvent::MouseWheel { delta, .. } => match delta {
|
|
|
|
|
MouseScrollDelta::LineDelta(x, y) => {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Mouse wheel Line Delta: ({x},{y})");
|
2024-01-31 17:29:59 +04:00
|
|
|
},
|
|
|
|
|
MouseScrollDelta::PixelDelta(px) => {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Mouse wheel Pixel Delta: ({},{})", px.x, px.y);
|
2024-01-31 17:29:59 +04:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
WindowEvent::KeyboardInput { event, is_synthetic: false, .. } => {
|
|
|
|
|
let mods = window.modifiers;
|
|
|
|
|
|
|
|
|
|
// Dispatch actions only on press.
|
|
|
|
|
if event.state.is_pressed() {
|
|
|
|
|
let action = if let Key::Character(ch) = event.logical_key.as_ref() {
|
|
|
|
|
Self::process_key_binding(&ch.to_uppercase(), &mods)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if let Some(action) = action {
|
2024-07-23 20:33:10 +02:00
|
|
|
self.handle_action_with_window(event_loop, window_id, action);
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
WindowEvent::MouseInput { button, state, .. } => {
|
|
|
|
|
let mods = window.modifiers;
|
|
|
|
|
if let Some(action) =
|
|
|
|
|
state.is_pressed().then(|| Self::process_mouse_binding(button, &mods)).flatten()
|
|
|
|
|
{
|
2024-07-23 20:33:10 +02:00
|
|
|
self.handle_action_with_window(event_loop, window_id, action);
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
WindowEvent::CursorLeft { .. } => {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Cursor left Window={window_id:?}");
|
2024-01-31 17:29:59 +04:00
|
|
|
window.cursor_left();
|
|
|
|
|
},
|
|
|
|
|
WindowEvent::CursorMoved { position, .. } => {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Moved cursor to {position:?}");
|
2024-01-31 17:29:59 +04:00
|
|
|
window.cursor_moved(position);
|
|
|
|
|
},
|
|
|
|
|
WindowEvent::ActivationTokenDone { token: _token, .. } => {
|
|
|
|
|
#[cfg(any(x11_platform, wayland_platform))]
|
|
|
|
|
{
|
|
|
|
|
startup_notify::set_activation_token_env(_token);
|
|
|
|
|
if let Err(err) = self.create_window(event_loop, None) {
|
2024-04-18 19:43:39 +02:00
|
|
|
error!("Error creating new window: {err}");
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
WindowEvent::Ime(event) => match event {
|
2024-04-18 19:43:39 +02:00
|
|
|
Ime::Enabled => info!("IME enabled for Window={window_id:?}"),
|
2024-01-31 17:29:59 +04:00
|
|
|
Ime::Preedit(text, caret_pos) => {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Preedit: {}, with caret at {:?}", text, caret_pos);
|
2024-01-31 17:29:59 +04:00
|
|
|
},
|
|
|
|
|
Ime::Commit(text) => {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Committed: {}", text);
|
2024-01-31 17:29:59 +04:00
|
|
|
},
|
2024-04-18 19:43:39 +02:00
|
|
|
Ime::Disabled => info!("IME disabled for Window={window_id:?}"),
|
2023-08-27 16:15:09 +02:00
|
|
|
},
|
2024-01-31 17:29:59 +04:00
|
|
|
WindowEvent::PinchGesture { delta, .. } => {
|
|
|
|
|
window.zoom += delta;
|
|
|
|
|
let zoom = window.zoom;
|
|
|
|
|
if delta > 0.0 {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Zoomed in {delta:.5} (now: {zoom:.5})");
|
2024-01-31 17:29:59 +04:00
|
|
|
} else {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Zoomed out {delta:.5} (now: {zoom:.5})");
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
WindowEvent::RotationGesture { delta, .. } => {
|
|
|
|
|
window.rotated += delta;
|
|
|
|
|
let rotated = window.rotated;
|
|
|
|
|
if delta > 0.0 {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Rotated counterclockwise {delta:.5} (now: {rotated:.5})");
|
2024-01-31 17:29:59 +04:00
|
|
|
} else {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Rotated clockwise {delta:.5} (now: {rotated:.5})");
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
},
|
2024-04-27 09:55:04 -04:00
|
|
|
WindowEvent::PanGesture { delta, phase, .. } => {
|
|
|
|
|
window.panned.x += delta.x;
|
|
|
|
|
window.panned.y += delta.y;
|
|
|
|
|
info!("Panned ({delta:?})) (now: {:?}), {phase:?}", window.panned);
|
|
|
|
|
},
|
2024-01-31 17:29:59 +04:00
|
|
|
WindowEvent::DoubleTapGesture { .. } => {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Smart zoom");
|
2024-01-31 17:29:59 +04:00
|
|
|
},
|
|
|
|
|
WindowEvent::TouchpadPressure { .. }
|
|
|
|
|
| WindowEvent::HoveredFileCancelled
|
|
|
|
|
| WindowEvent::KeyboardInput { .. }
|
|
|
|
|
| WindowEvent::CursorEntered { .. }
|
|
|
|
|
| WindowEvent::DroppedFile(_)
|
|
|
|
|
| WindowEvent::HoveredFile(_)
|
|
|
|
|
| WindowEvent::Destroyed
|
|
|
|
|
| WindowEvent::Touch(_)
|
|
|
|
|
| WindowEvent::Moved(_) => (),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-23 14:37:21 +04:00
|
|
|
fn device_event(
|
|
|
|
|
&mut self,
|
2024-08-06 21:02:53 +03:00
|
|
|
_event_loop: &dyn ActiveEventLoop,
|
2024-02-23 14:37:21 +04:00
|
|
|
device_id: DeviceId,
|
|
|
|
|
event: DeviceEvent,
|
|
|
|
|
) {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Device {device_id:?} event: {event:?}");
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
|
2024-08-06 21:02:53 +03:00
|
|
|
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
|
2024-06-30 00:41:57 +02:00
|
|
|
info!("Ready to create surfaces");
|
2024-02-23 14:37:21 +04:00
|
|
|
self.dump_monitors(event_loop);
|
2024-01-31 17:29:59 +04:00
|
|
|
|
2024-02-23 14:37:21 +04:00
|
|
|
// Create initial window.
|
|
|
|
|
self.create_window(event_loop, None).expect("failed to create initial window");
|
2024-01-31 17:29:59 +04:00
|
|
|
|
2024-02-23 14:37:21 +04:00
|
|
|
self.print_help();
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
|
2024-08-06 21:02:53 +03:00
|
|
|
fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) {
|
2024-02-23 14:37:21 +04:00
|
|
|
if self.windows.is_empty() {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("No windows left, exiting...");
|
2024-02-23 14:37:21 +04:00
|
|
|
event_loop.exit();
|
|
|
|
|
}
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
|
2024-02-23 14:37:21 +04:00
|
|
|
#[cfg(not(any(android_platform, ios_platform)))]
|
2024-08-06 21:02:53 +03:00
|
|
|
fn exiting(&mut self, _event_loop: &dyn ActiveEventLoop) {
|
2024-02-23 14:37:21 +04:00
|
|
|
// We must drop the context here.
|
|
|
|
|
self.context = None;
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// State of the window.
|
|
|
|
|
struct WindowState {
|
|
|
|
|
/// IME input.
|
|
|
|
|
ime: bool,
|
|
|
|
|
/// Render surface.
|
|
|
|
|
///
|
|
|
|
|
/// NOTE: This surface must be dropped before the `Window`.
|
|
|
|
|
#[cfg(not(any(android_platform, ios_platform)))]
|
2024-08-23 23:40:27 +03:00
|
|
|
surface: Surface<DisplayHandle<'static>, Arc<dyn Window>>,
|
2024-01-31 17:29:59 +04:00
|
|
|
/// The actual winit Window.
|
2024-08-23 23:40:27 +03:00
|
|
|
window: Arc<dyn Window>,
|
2024-01-31 17:29:59 +04:00
|
|
|
/// The window theme we're drawing with.
|
|
|
|
|
theme: Theme,
|
|
|
|
|
/// Cursor position over the window.
|
|
|
|
|
cursor_position: Option<PhysicalPosition<f64>>,
|
|
|
|
|
/// Window modifiers state.
|
|
|
|
|
modifiers: ModifiersState,
|
|
|
|
|
/// Occlusion state of the window.
|
|
|
|
|
occluded: bool,
|
|
|
|
|
/// Current cursor grab mode.
|
|
|
|
|
cursor_grab: CursorGrabMode,
|
|
|
|
|
/// The amount of zoom into window.
|
|
|
|
|
zoom: f64,
|
|
|
|
|
/// The amount of rotation of the window.
|
|
|
|
|
rotated: f32,
|
2024-04-27 09:55:04 -04:00
|
|
|
/// The amount of pan of the window.
|
|
|
|
|
panned: PhysicalPosition<f32>,
|
2024-01-31 17:29:59 +04:00
|
|
|
|
|
|
|
|
#[cfg(macos_platform)]
|
|
|
|
|
option_as_alt: OptionAsAlt,
|
|
|
|
|
|
|
|
|
|
// Cursor states.
|
|
|
|
|
named_idx: usize,
|
|
|
|
|
custom_idx: usize,
|
|
|
|
|
cursor_hidden: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl WindowState {
|
2024-08-23 23:40:27 +03:00
|
|
|
fn new(app: &Application, window: Box<dyn Window>) -> Result<Self, Box<dyn Error>> {
|
|
|
|
|
let window: Arc<dyn Window> = Arc::from(window);
|
2024-04-18 21:52:38 +02:00
|
|
|
|
2024-01-31 17:29:59 +04:00
|
|
|
// SAFETY: the surface is dropped before the `window` which provided it with handle, thus
|
|
|
|
|
// it doesn't outlive it.
|
|
|
|
|
#[cfg(not(any(android_platform, ios_platform)))]
|
2024-04-18 21:52:38 +02:00
|
|
|
let surface = Surface::new(app.context.as_ref().unwrap(), Arc::clone(&window))?;
|
2024-01-31 17:29:59 +04:00
|
|
|
|
|
|
|
|
let theme = window.theme().unwrap_or(Theme::Dark);
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Theme: {theme:?}");
|
2024-01-31 17:29:59 +04:00
|
|
|
let named_idx = 0;
|
2024-08-23 23:40:27 +03:00
|
|
|
window.set_cursor(CURSORS[named_idx].into());
|
2024-01-31 17:29:59 +04:00
|
|
|
|
|
|
|
|
// Allow IME out of the box.
|
|
|
|
|
let ime = true;
|
|
|
|
|
window.set_ime_allowed(ime);
|
|
|
|
|
|
2024-09-04 15:04:48 +02:00
|
|
|
let size = window.surface_size();
|
2024-01-31 17:29:59 +04:00
|
|
|
let mut state = Self {
|
|
|
|
|
#[cfg(macos_platform)]
|
|
|
|
|
option_as_alt: window.option_as_alt(),
|
2024-08-06 18:57:03 +02:00
|
|
|
custom_idx: app.custom_cursors.as_ref().map(Vec::len).unwrap_or(1) - 1,
|
2024-01-31 17:29:59 +04:00
|
|
|
cursor_grab: CursorGrabMode::None,
|
|
|
|
|
named_idx,
|
|
|
|
|
#[cfg(not(any(android_platform, ios_platform)))]
|
|
|
|
|
surface,
|
|
|
|
|
window,
|
|
|
|
|
theme,
|
|
|
|
|
ime,
|
|
|
|
|
cursor_position: Default::default(),
|
|
|
|
|
cursor_hidden: Default::default(),
|
|
|
|
|
modifiers: Default::default(),
|
|
|
|
|
occluded: Default::default(),
|
|
|
|
|
rotated: Default::default(),
|
2024-04-27 09:55:04 -04:00
|
|
|
panned: Default::default(),
|
2024-01-31 17:29:59 +04:00
|
|
|
zoom: Default::default(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
state.resize(size);
|
|
|
|
|
Ok(state)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn toggle_ime(&mut self) {
|
|
|
|
|
self.ime = !self.ime;
|
|
|
|
|
self.window.set_ime_allowed(self.ime);
|
|
|
|
|
if let Some(position) = self.ime.then_some(self.cursor_position).flatten() {
|
2024-08-23 23:40:27 +03:00
|
|
|
self.window.set_ime_cursor_area(position.into(), PhysicalSize::new(20, 20).into());
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn minimize(&mut self) {
|
|
|
|
|
self.window.set_minimized(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn cursor_moved(&mut self, position: PhysicalPosition<f64>) {
|
|
|
|
|
self.cursor_position = Some(position);
|
|
|
|
|
if self.ime {
|
2024-08-23 23:40:27 +03:00
|
|
|
self.window.set_ime_cursor_area(position.into(), PhysicalSize::new(20, 20).into());
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn cursor_left(&mut self) {
|
|
|
|
|
self.cursor_position = None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Toggle maximized.
|
|
|
|
|
fn toggle_maximize(&self) {
|
|
|
|
|
let maximized = self.window.is_maximized();
|
|
|
|
|
self.window.set_maximized(!maximized);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Toggle window decorations.
|
|
|
|
|
fn toggle_decorations(&self) {
|
|
|
|
|
let decorated = self.window.is_decorated();
|
|
|
|
|
self.window.set_decorations(!decorated);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Toggle window resizable state.
|
|
|
|
|
fn toggle_resizable(&self) {
|
|
|
|
|
let resizable = self.window.is_resizable();
|
|
|
|
|
self.window.set_resizable(!resizable);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Toggle cursor visibility
|
|
|
|
|
fn toggle_cursor_visibility(&mut self) {
|
|
|
|
|
self.cursor_hidden = !self.cursor_hidden;
|
|
|
|
|
self.window.set_cursor_visible(!self.cursor_hidden);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Toggle resize increments on a window.
|
|
|
|
|
fn toggle_resize_increments(&mut self) {
|
2024-09-04 15:04:48 +02:00
|
|
|
let new_increments = match self.window.surface_resize_increments() {
|
2024-01-31 17:29:59 +04:00
|
|
|
Some(_) => None,
|
2024-08-23 23:40:27 +03:00
|
|
|
None => Some(LogicalSize::new(25.0, 25.0).into()),
|
2024-01-31 17:29:59 +04:00
|
|
|
};
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Had increments: {}", new_increments.is_none());
|
2024-09-04 15:04:48 +02:00
|
|
|
self.window.set_surface_resize_increments(new_increments);
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Toggle fullscreen.
|
|
|
|
|
fn toggle_fullscreen(&self) {
|
|
|
|
|
let fullscreen = if self.window.fullscreen().is_some() {
|
|
|
|
|
None
|
|
|
|
|
} else {
|
|
|
|
|
Some(Fullscreen::Borderless(None))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
self.window.set_fullscreen(fullscreen);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Cycle through the grab modes ignoring errors.
|
|
|
|
|
fn cycle_cursor_grab(&mut self) {
|
|
|
|
|
self.cursor_grab = match self.cursor_grab {
|
|
|
|
|
CursorGrabMode::None => CursorGrabMode::Confined,
|
|
|
|
|
CursorGrabMode::Confined => CursorGrabMode::Locked,
|
|
|
|
|
CursorGrabMode::Locked => CursorGrabMode::None,
|
|
|
|
|
};
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Changing cursor grab mode to {:?}", self.cursor_grab);
|
2024-01-31 17:29:59 +04:00
|
|
|
if let Err(err) = self.window.set_cursor_grab(self.cursor_grab) {
|
2024-04-18 19:43:39 +02:00
|
|
|
error!("Error setting cursor grab: {err}");
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(macos_platform)]
|
|
|
|
|
fn cycle_option_as_alt(&mut self) {
|
|
|
|
|
self.option_as_alt = match self.option_as_alt {
|
|
|
|
|
OptionAsAlt::None => OptionAsAlt::OnlyLeft,
|
|
|
|
|
OptionAsAlt::OnlyLeft => OptionAsAlt::OnlyRight,
|
|
|
|
|
OptionAsAlt::OnlyRight => OptionAsAlt::Both,
|
|
|
|
|
OptionAsAlt::Both => OptionAsAlt::None,
|
|
|
|
|
};
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Setting option as alt {:?}", self.option_as_alt);
|
2024-01-31 17:29:59 +04:00
|
|
|
self.window.set_option_as_alt(self.option_as_alt);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-04 15:04:48 +02:00
|
|
|
/// Swap the window dimensions with `request_surface_size`.
|
2024-03-28 19:39:19 +00:00
|
|
|
fn swap_dimensions(&mut self) {
|
2024-09-04 15:04:48 +02:00
|
|
|
let old_surface_size = self.window.surface_size();
|
|
|
|
|
let mut surface_size = old_surface_size;
|
2024-03-28 19:39:19 +00:00
|
|
|
|
2024-09-04 15:04:48 +02:00
|
|
|
mem::swap(&mut surface_size.width, &mut surface_size.height);
|
|
|
|
|
info!("Requesting resize from {old_surface_size:?} to {surface_size:?}");
|
2024-03-28 19:39:19 +00:00
|
|
|
|
2024-09-04 15:04:48 +02:00
|
|
|
if let Some(new_surface_size) = self.window.request_surface_size(surface_size.into()) {
|
|
|
|
|
if old_surface_size == new_surface_size {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Inner size change got ignored");
|
2024-03-28 19:39:19 +00:00
|
|
|
} else {
|
2024-09-04 15:04:48 +02:00
|
|
|
self.resize(new_surface_size);
|
2024-03-28 19:39:19 +00:00
|
|
|
}
|
|
|
|
|
} else {
|
2024-09-04 15:04:48 +02:00
|
|
|
info!("Requesting surface size is asynchronous");
|
2024-03-28 19:39:19 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-31 17:29:59 +04:00
|
|
|
/// Pick the next cursor.
|
|
|
|
|
fn next_cursor(&mut self) {
|
|
|
|
|
self.named_idx = (self.named_idx + 1) % CURSORS.len();
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Setting cursor to \"{:?}\"", CURSORS[self.named_idx]);
|
2024-01-31 17:29:59 +04:00
|
|
|
self.window.set_cursor(Cursor::Icon(CURSORS[self.named_idx]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Pick the next custom cursor.
|
|
|
|
|
fn next_custom_cursor(&mut self, custom_cursors: &[CustomCursor]) {
|
|
|
|
|
self.custom_idx = (self.custom_idx + 1) % custom_cursors.len();
|
|
|
|
|
let cursor = Cursor::Custom(custom_cursors[self.custom_idx].clone());
|
|
|
|
|
self.window.set_cursor(cursor);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-18 19:43:39 +02:00
|
|
|
/// Custom cursor from an URL.
|
|
|
|
|
#[cfg(web_platform)]
|
2024-08-06 21:02:53 +03:00
|
|
|
fn url_custom_cursor(
|
|
|
|
|
&mut self,
|
|
|
|
|
event_loop: &dyn ActiveEventLoop,
|
|
|
|
|
) -> Result<(), Box<dyn Error>> {
|
2024-08-06 18:57:03 +02:00
|
|
|
let cursor = event_loop.create_custom_cursor(url_custom_cursor())?;
|
2024-04-18 19:43:39 +02:00
|
|
|
|
2024-08-23 23:40:27 +03:00
|
|
|
self.window.set_cursor(cursor.into());
|
2024-08-06 18:57:03 +02:00
|
|
|
|
|
|
|
|
Ok(())
|
2024-04-18 19:43:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Custom cursor from a URL.
|
|
|
|
|
#[cfg(web_platform)]
|
|
|
|
|
fn animation_custom_cursor(
|
|
|
|
|
&mut self,
|
2024-08-06 21:02:53 +03:00
|
|
|
event_loop: &dyn ActiveEventLoop,
|
2024-04-18 19:43:39 +02:00
|
|
|
custom_cursors: &[CustomCursor],
|
2024-08-06 18:57:03 +02:00
|
|
|
) -> Result<(), Box<dyn Error>> {
|
2024-04-18 19:43:39 +02:00
|
|
|
use std::time::Duration;
|
2024-07-07 18:38:50 +02:00
|
|
|
|
2024-04-18 19:43:39 +02:00
|
|
|
let cursors = vec![
|
|
|
|
|
custom_cursors[0].clone(),
|
|
|
|
|
custom_cursors[1].clone(),
|
2024-08-06 18:57:03 +02:00
|
|
|
event_loop.create_custom_cursor(url_custom_cursor())?,
|
2024-04-18 19:43:39 +02:00
|
|
|
];
|
|
|
|
|
let cursor = CustomCursor::from_animation(Duration::from_secs(3), cursors).unwrap();
|
2024-08-06 18:57:03 +02:00
|
|
|
let cursor = event_loop.create_custom_cursor(cursor)?;
|
2024-04-18 19:43:39 +02:00
|
|
|
|
2024-08-23 23:40:27 +03:00
|
|
|
self.window.set_cursor(cursor.into());
|
2024-08-06 18:57:03 +02:00
|
|
|
|
|
|
|
|
Ok(())
|
2024-04-18 19:43:39 +02:00
|
|
|
}
|
|
|
|
|
|
2024-09-04 15:04:48 +02:00
|
|
|
/// Resize the surface to the new size.
|
2024-03-28 19:39:19 +00:00
|
|
|
fn resize(&mut self, size: PhysicalSize<u32>) {
|
2024-09-04 15:04:48 +02:00
|
|
|
info!("Surface resized to {size:?}");
|
2024-01-31 17:29:59 +04:00
|
|
|
#[cfg(not(any(android_platform, ios_platform)))]
|
|
|
|
|
{
|
2024-03-28 19:39:19 +00:00
|
|
|
let (width, height) = match (NonZeroU32::new(size.width), NonZeroU32::new(size.height))
|
|
|
|
|
{
|
|
|
|
|
(Some(width), Some(height)) => (width, height),
|
|
|
|
|
_ => return,
|
|
|
|
|
};
|
2024-01-31 17:29:59 +04:00
|
|
|
self.surface.resize(width, height).expect("failed to resize inner buffer");
|
|
|
|
|
}
|
|
|
|
|
self.window.request_redraw();
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-20 16:05:34 +02:00
|
|
|
/// Change the theme that things are drawn in.
|
|
|
|
|
fn set_draw_theme(&mut self, theme: Theme) {
|
2024-01-31 17:29:59 +04:00
|
|
|
self.theme = theme;
|
|
|
|
|
self.window.request_redraw();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Show window menu.
|
|
|
|
|
fn show_menu(&self) {
|
|
|
|
|
if let Some(position) = self.cursor_position {
|
2024-08-23 23:40:27 +03:00
|
|
|
self.window.show_window_menu(position.into());
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Drag the window.
|
|
|
|
|
fn drag_window(&self) {
|
|
|
|
|
if let Err(err) = self.window.drag_window() {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Error starting window drag: {err}");
|
2024-01-31 17:29:59 +04:00
|
|
|
} else {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Dragging window Window={:?}", self.window.id());
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Drag-resize the window.
|
|
|
|
|
fn drag_resize_window(&self) {
|
|
|
|
|
let position = match self.cursor_position {
|
|
|
|
|
Some(position) => position,
|
|
|
|
|
None => {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Drag-resize requires cursor to be inside the window");
|
2024-01-31 17:29:59 +04:00
|
|
|
return;
|
2019-08-26 22:05:42 -04:00
|
|
|
},
|
2024-01-31 17:29:59 +04:00
|
|
|
};
|
|
|
|
|
|
2024-09-04 15:04:48 +02:00
|
|
|
let win_size = self.window.surface_size();
|
2024-01-31 17:29:59 +04:00
|
|
|
let border_size = BORDER_SIZE * self.window.scale_factor();
|
|
|
|
|
|
|
|
|
|
let x_direction = if position.x < border_size {
|
|
|
|
|
ResizeDirection::West
|
|
|
|
|
} else if position.x > (win_size.width as f64 - border_size) {
|
|
|
|
|
ResizeDirection::East
|
|
|
|
|
} else {
|
|
|
|
|
// Use arbitrary direction instead of None for simplicity.
|
|
|
|
|
ResizeDirection::SouthEast
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let y_direction = if position.y < border_size {
|
|
|
|
|
ResizeDirection::North
|
|
|
|
|
} else if position.y > (win_size.height as f64 - border_size) {
|
|
|
|
|
ResizeDirection::South
|
|
|
|
|
} else {
|
|
|
|
|
// Use arbitrary direction instead of None for simplicity.
|
|
|
|
|
ResizeDirection::SouthEast
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let direction = match (x_direction, y_direction) {
|
|
|
|
|
(ResizeDirection::West, ResizeDirection::North) => ResizeDirection::NorthWest,
|
|
|
|
|
(ResizeDirection::West, ResizeDirection::South) => ResizeDirection::SouthWest,
|
|
|
|
|
(ResizeDirection::West, _) => ResizeDirection::West,
|
|
|
|
|
(ResizeDirection::East, ResizeDirection::North) => ResizeDirection::NorthEast,
|
|
|
|
|
(ResizeDirection::East, ResizeDirection::South) => ResizeDirection::SouthEast,
|
|
|
|
|
(ResizeDirection::East, _) => ResizeDirection::East,
|
|
|
|
|
(_, ResizeDirection::South) => ResizeDirection::South,
|
|
|
|
|
(_, ResizeDirection::North) => ResizeDirection::North,
|
|
|
|
|
_ => return,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if let Err(err) = self.window.drag_resize_window(direction) {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Error starting window drag-resize: {err}");
|
2024-01-31 17:29:59 +04:00
|
|
|
} else {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Drag-resizing window Window={:?}", self.window.id());
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Change window occlusion state.
|
|
|
|
|
fn set_occluded(&mut self, occluded: bool) {
|
|
|
|
|
self.occluded = occluded;
|
|
|
|
|
if !occluded {
|
|
|
|
|
self.window.request_redraw();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Draw the window contents.
|
|
|
|
|
#[cfg(not(any(android_platform, ios_platform)))]
|
|
|
|
|
fn draw(&mut self) -> Result<(), Box<dyn Error>> {
|
|
|
|
|
if self.occluded {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Skipping drawing occluded window={:?}", self.window.id());
|
2024-01-31 17:29:59 +04:00
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const WHITE: u32 = 0xffffffff;
|
|
|
|
|
const DARK_GRAY: u32 = 0xff181818;
|
|
|
|
|
|
|
|
|
|
let color = match self.theme {
|
|
|
|
|
Theme::Light => WHITE,
|
|
|
|
|
Theme::Dark => DARK_GRAY,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut buffer = self.surface.buffer_mut()?;
|
|
|
|
|
buffer.fill(color);
|
|
|
|
|
self.window.pre_present_notify();
|
|
|
|
|
buffer.present()?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(any(android_platform, ios_platform))]
|
|
|
|
|
fn draw(&mut self) -> Result<(), Box<dyn Error>> {
|
2024-04-18 19:43:39 +02:00
|
|
|
info!("Drawing but without rendering...");
|
2024-01-31 17:29:59 +04:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct Binding<T: Eq> {
|
|
|
|
|
trigger: T,
|
|
|
|
|
mods: ModifiersState,
|
|
|
|
|
action: Action,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T: Eq> Binding<T> {
|
|
|
|
|
const fn new(trigger: T, mods: ModifiersState, action: Action) -> Self {
|
|
|
|
|
Self { trigger, mods, action }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn is_triggered_by(&self, trigger: &T, mods: &ModifiersState) -> bool {
|
|
|
|
|
&self.trigger == trigger && &self.mods == mods
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-27 16:15:09 +02:00
|
|
|
|
2024-01-31 17:29:59 +04:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
|
|
enum Action {
|
|
|
|
|
CloseWindow,
|
|
|
|
|
ToggleCursorVisibility,
|
|
|
|
|
CreateNewWindow,
|
|
|
|
|
ToggleResizeIncrements,
|
|
|
|
|
ToggleImeInput,
|
|
|
|
|
ToggleDecorations,
|
|
|
|
|
ToggleResizable,
|
|
|
|
|
ToggleFullscreen,
|
|
|
|
|
ToggleMaximize,
|
|
|
|
|
Minimize,
|
|
|
|
|
NextCursor,
|
|
|
|
|
NextCustomCursor,
|
2024-04-18 19:43:39 +02:00
|
|
|
#[cfg(web_platform)]
|
|
|
|
|
UrlCustomCursor,
|
|
|
|
|
#[cfg(web_platform)]
|
|
|
|
|
AnimationCustomCursor,
|
2024-01-31 17:29:59 +04:00
|
|
|
CycleCursorGrab,
|
|
|
|
|
PrintHelp,
|
|
|
|
|
DragWindow,
|
|
|
|
|
DragResizeWindow,
|
|
|
|
|
ShowWindowMenu,
|
|
|
|
|
#[cfg(macos_platform)]
|
|
|
|
|
CycleOptionAsAlt,
|
2024-06-20 16:05:34 +02:00
|
|
|
SetTheme(Option<Theme>),
|
2024-01-31 17:29:59 +04:00
|
|
|
#[cfg(macos_platform)]
|
|
|
|
|
CreateNewTab,
|
2024-03-28 19:39:19 +00:00
|
|
|
RequestResize,
|
2024-07-23 20:33:10 +02:00
|
|
|
DumpMonitors,
|
|
|
|
|
Message,
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Action {
|
|
|
|
|
fn help(&self) -> &'static str {
|
|
|
|
|
match self {
|
|
|
|
|
Action::CloseWindow => "Close window",
|
|
|
|
|
Action::ToggleCursorVisibility => "Hide cursor",
|
|
|
|
|
Action::CreateNewWindow => "Create new window",
|
|
|
|
|
Action::ToggleImeInput => "Toggle IME input",
|
|
|
|
|
Action::ToggleDecorations => "Toggle decorations",
|
|
|
|
|
Action::ToggleResizable => "Toggle window resizable state",
|
|
|
|
|
Action::ToggleFullscreen => "Toggle fullscreen",
|
|
|
|
|
Action::ToggleMaximize => "Maximize",
|
|
|
|
|
Action::Minimize => "Minimize",
|
|
|
|
|
Action::ToggleResizeIncrements => "Use resize increments when resizing window",
|
|
|
|
|
Action::NextCursor => "Advance the cursor to the next value",
|
|
|
|
|
Action::NextCustomCursor => "Advance custom cursor to the next value",
|
2024-04-18 19:43:39 +02:00
|
|
|
#[cfg(web_platform)]
|
|
|
|
|
Action::UrlCustomCursor => "Custom cursor from an URL",
|
|
|
|
|
#[cfg(web_platform)]
|
|
|
|
|
Action::AnimationCustomCursor => "Custom cursor from an animation",
|
2024-01-31 17:29:59 +04:00
|
|
|
Action::CycleCursorGrab => "Cycle through cursor grab mode",
|
|
|
|
|
Action::PrintHelp => "Print help",
|
|
|
|
|
Action::DragWindow => "Start window drag",
|
|
|
|
|
Action::DragResizeWindow => "Start window drag-resize",
|
|
|
|
|
Action::ShowWindowMenu => "Show window menu",
|
|
|
|
|
#[cfg(macos_platform)]
|
|
|
|
|
Action::CycleOptionAsAlt => "Cycle option as alt mode",
|
2024-06-20 16:05:34 +02:00
|
|
|
Action::SetTheme(None) => "Change to the system theme",
|
|
|
|
|
Action::SetTheme(Some(Theme::Light)) => "Change to a light theme",
|
|
|
|
|
Action::SetTheme(Some(Theme::Dark)) => "Change to a dark theme",
|
2024-01-31 17:29:59 +04:00
|
|
|
#[cfg(macos_platform)]
|
|
|
|
|
Action::CreateNewTab => "Create new tab",
|
2024-03-28 19:39:19 +00:00
|
|
|
Action::RequestResize => "Request a resize",
|
2024-07-23 20:33:10 +02:00
|
|
|
#[cfg(not(web_platform))]
|
|
|
|
|
Action::DumpMonitors => "Dump monitor information",
|
|
|
|
|
#[cfg(web_platform)]
|
|
|
|
|
Action::DumpMonitors => {
|
|
|
|
|
"Request permission to query detailed monitor information and dump monitor \
|
|
|
|
|
information"
|
|
|
|
|
},
|
|
|
|
|
Action::Message => "Prints a message through a user wake up",
|
2015-06-16 13:48:08 +02:00
|
|
|
}
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
2014-07-27 10:55:37 +02:00
|
|
|
}
|
2024-01-31 17:29:59 +04:00
|
|
|
|
|
|
|
|
impl fmt::Display for Action {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
|
Debug::fmt(&self, f)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-03 07:27:17 +04:00
|
|
|
fn decode_cursor(bytes: &[u8]) -> CustomCursorSource {
|
2024-01-31 17:29:59 +04:00
|
|
|
let img = image::load_from_memory(bytes).unwrap().to_rgba8();
|
|
|
|
|
let samples = img.into_flat_samples();
|
|
|
|
|
let (_, w, h) = samples.extents();
|
|
|
|
|
let (w, h) = (w as u16, h as u16);
|
2024-02-03 07:27:17 +04:00
|
|
|
CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap()
|
2024-01-31 17:29:59 +04:00
|
|
|
}
|
|
|
|
|
|
2024-04-18 19:43:39 +02:00
|
|
|
#[cfg(web_platform)]
|
|
|
|
|
fn url_custom_cursor() -> CustomCursorSource {
|
|
|
|
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
|
|
|
|
|
|
|
|
static URL_COUNTER: AtomicU64 = AtomicU64::new(0);
|
|
|
|
|
|
|
|
|
|
CustomCursor::from_url(
|
|
|
|
|
format!("https://picsum.photos/128?random={}", URL_COUNTER.fetch_add(1, Ordering::Relaxed)),
|
|
|
|
|
64,
|
|
|
|
|
64,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn load_icon(bytes: &[u8]) -> Icon {
|
2024-01-31 17:29:59 +04:00
|
|
|
let (icon_rgba, icon_width, icon_height) = {
|
2024-04-18 19:43:39 +02:00
|
|
|
let image = image::load_from_memory(bytes).unwrap().into_rgba8();
|
2024-01-31 17:29:59 +04:00
|
|
|
let (width, height) = image.dimensions();
|
|
|
|
|
let rgba = image.into_raw();
|
|
|
|
|
(rgba, width, height)
|
|
|
|
|
};
|
|
|
|
|
Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn modifiers_to_string(mods: ModifiersState) -> String {
|
|
|
|
|
let mut mods_line = String::new();
|
|
|
|
|
// Always add + since it's printed as a part of the bindings.
|
|
|
|
|
for (modifier, desc) in [
|
|
|
|
|
(ModifiersState::SUPER, "Super+"),
|
|
|
|
|
(ModifiersState::ALT, "Alt+"),
|
|
|
|
|
(ModifiersState::CONTROL, "Ctrl+"),
|
|
|
|
|
(ModifiersState::SHIFT, "Shift+"),
|
|
|
|
|
] {
|
|
|
|
|
if !mods.contains(modifier) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mods_line.push_str(desc);
|
|
|
|
|
}
|
|
|
|
|
mods_line
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn mouse_button_to_string(button: MouseButton) -> &'static str {
|
|
|
|
|
match button {
|
|
|
|
|
MouseButton::Left => "LMB",
|
|
|
|
|
MouseButton::Right => "RMB",
|
|
|
|
|
MouseButton::Middle => "MMB",
|
|
|
|
|
MouseButton::Back => "Back",
|
|
|
|
|
MouseButton::Forward => "Forward",
|
|
|
|
|
MouseButton::Other(_) => "",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Cursor list to cycle through.
|
|
|
|
|
const CURSORS: &[CursorIcon] = &[
|
|
|
|
|
CursorIcon::Default,
|
|
|
|
|
CursorIcon::Crosshair,
|
|
|
|
|
CursorIcon::Pointer,
|
|
|
|
|
CursorIcon::Move,
|
|
|
|
|
CursorIcon::Text,
|
|
|
|
|
CursorIcon::Wait,
|
|
|
|
|
CursorIcon::Help,
|
|
|
|
|
CursorIcon::Progress,
|
|
|
|
|
CursorIcon::NotAllowed,
|
|
|
|
|
CursorIcon::ContextMenu,
|
|
|
|
|
CursorIcon::Cell,
|
|
|
|
|
CursorIcon::VerticalText,
|
|
|
|
|
CursorIcon::Alias,
|
|
|
|
|
CursorIcon::Copy,
|
|
|
|
|
CursorIcon::NoDrop,
|
|
|
|
|
CursorIcon::Grab,
|
|
|
|
|
CursorIcon::Grabbing,
|
|
|
|
|
CursorIcon::AllScroll,
|
|
|
|
|
CursorIcon::ZoomIn,
|
|
|
|
|
CursorIcon::ZoomOut,
|
|
|
|
|
CursorIcon::EResize,
|
|
|
|
|
CursorIcon::NResize,
|
|
|
|
|
CursorIcon::NeResize,
|
|
|
|
|
CursorIcon::NwResize,
|
|
|
|
|
CursorIcon::SResize,
|
|
|
|
|
CursorIcon::SeResize,
|
|
|
|
|
CursorIcon::SwResize,
|
|
|
|
|
CursorIcon::WResize,
|
|
|
|
|
CursorIcon::EwResize,
|
|
|
|
|
CursorIcon::NsResize,
|
|
|
|
|
CursorIcon::NeswResize,
|
|
|
|
|
CursorIcon::NwseResize,
|
|
|
|
|
CursorIcon::ColResize,
|
|
|
|
|
CursorIcon::RowResize,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const KEY_BINDINGS: &[Binding<&'static str>] = &[
|
|
|
|
|
Binding::new("Q", ModifiersState::CONTROL, Action::CloseWindow),
|
|
|
|
|
Binding::new("H", ModifiersState::CONTROL, Action::PrintHelp),
|
|
|
|
|
Binding::new("F", ModifiersState::CONTROL, Action::ToggleFullscreen),
|
|
|
|
|
Binding::new("D", ModifiersState::CONTROL, Action::ToggleDecorations),
|
|
|
|
|
Binding::new("I", ModifiersState::CONTROL, Action::ToggleImeInput),
|
|
|
|
|
Binding::new("L", ModifiersState::CONTROL, Action::CycleCursorGrab),
|
|
|
|
|
Binding::new("P", ModifiersState::CONTROL, Action::ToggleResizeIncrements),
|
|
|
|
|
Binding::new("R", ModifiersState::CONTROL, Action::ToggleResizable),
|
2024-03-28 19:39:19 +00:00
|
|
|
Binding::new("R", ModifiersState::ALT, Action::RequestResize),
|
2024-01-31 17:29:59 +04:00
|
|
|
// M.
|
2024-07-23 20:33:10 +02:00
|
|
|
Binding::new("M", ModifiersState::CONTROL.union(ModifiersState::ALT), Action::DumpMonitors),
|
2024-01-31 17:29:59 +04:00
|
|
|
Binding::new("M", ModifiersState::CONTROL, Action::ToggleMaximize),
|
|
|
|
|
Binding::new("M", ModifiersState::ALT, Action::Minimize),
|
|
|
|
|
// N.
|
|
|
|
|
Binding::new("N", ModifiersState::CONTROL, Action::CreateNewWindow),
|
|
|
|
|
// C.
|
|
|
|
|
Binding::new("C", ModifiersState::CONTROL, Action::NextCursor),
|
|
|
|
|
Binding::new("C", ModifiersState::ALT, Action::NextCustomCursor),
|
2024-04-18 19:43:39 +02:00
|
|
|
#[cfg(web_platform)]
|
|
|
|
|
Binding::new(
|
|
|
|
|
"C",
|
|
|
|
|
ModifiersState::CONTROL.union(ModifiersState::SHIFT),
|
|
|
|
|
Action::UrlCustomCursor,
|
|
|
|
|
),
|
|
|
|
|
#[cfg(web_platform)]
|
|
|
|
|
Binding::new(
|
|
|
|
|
"C",
|
|
|
|
|
ModifiersState::ALT.union(ModifiersState::SHIFT),
|
|
|
|
|
Action::AnimationCustomCursor,
|
|
|
|
|
),
|
2024-01-31 17:29:59 +04:00
|
|
|
Binding::new("Z", ModifiersState::CONTROL, Action::ToggleCursorVisibility),
|
2024-06-20 16:05:34 +02:00
|
|
|
// K.
|
|
|
|
|
Binding::new("K", ModifiersState::empty(), Action::SetTheme(None)),
|
|
|
|
|
Binding::new("K", ModifiersState::SUPER, Action::SetTheme(Some(Theme::Light))),
|
|
|
|
|
Binding::new("K", ModifiersState::CONTROL, Action::SetTheme(Some(Theme::Dark))),
|
2024-01-31 17:29:59 +04:00
|
|
|
#[cfg(macos_platform)]
|
|
|
|
|
Binding::new("T", ModifiersState::SUPER, Action::CreateNewTab),
|
|
|
|
|
#[cfg(macos_platform)]
|
|
|
|
|
Binding::new("O", ModifiersState::CONTROL, Action::CycleOptionAsAlt),
|
2024-07-23 20:33:10 +02:00
|
|
|
Binding::new("S", ModifiersState::CONTROL, Action::Message),
|
2024-01-31 17:29:59 +04:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const MOUSE_BINDINGS: &[Binding<MouseButton>] = &[
|
|
|
|
|
Binding::new(MouseButton::Left, ModifiersState::ALT, Action::DragResizeWindow),
|
|
|
|
|
Binding::new(MouseButton::Left, ModifiersState::CONTROL, Action::DragWindow),
|
|
|
|
|
Binding::new(MouseButton::Right, ModifiersState::CONTROL, Action::ShowWindowMenu),
|
|
|
|
|
];
|