mirror of
https://github.com/emilk/egui.git
synced 2026-06-28 07:23:13 -04:00
Additionally to emmiting egui::Event::Touch events, on_touch() now also emits egui::Event::PointerButton, egui::event::PointerMoved and egui::event::PointerGone facilitating UI usage on touchscreen devices. If there are multiple touches, only the first one will be translated into pointer events. This prevents situations with two pointers.
725 lines
28 KiB
Rust
725 lines
28 KiB
Rust
//! [`egui`] bindings for [`winit`](https://github.com/rust-windowing/winit).
|
|
//!
|
|
//! The library translates winit events to egui, handled copy/paste,
|
|
//! updates the cursor, open links clicked in egui, etc.
|
|
|
|
#![forbid(unsafe_code)]
|
|
#![warn(
|
|
clippy::all,
|
|
clippy::await_holding_lock,
|
|
clippy::char_lit_as_u8,
|
|
clippy::checked_conversions,
|
|
clippy::dbg_macro,
|
|
clippy::debug_assert_with_mut_call,
|
|
clippy::doc_markdown,
|
|
clippy::empty_enum,
|
|
clippy::enum_glob_use,
|
|
clippy::exit,
|
|
clippy::expl_impl_clone_on_copy,
|
|
clippy::explicit_deref_methods,
|
|
clippy::explicit_into_iter_loop,
|
|
clippy::fallible_impl_from,
|
|
clippy::filter_map_next,
|
|
clippy::float_cmp_const,
|
|
clippy::fn_params_excessive_bools,
|
|
clippy::if_let_mutex,
|
|
clippy::imprecise_flops,
|
|
clippy::inefficient_to_string,
|
|
clippy::invalid_upcast_comparisons,
|
|
clippy::large_types_passed_by_value,
|
|
clippy::let_unit_value,
|
|
clippy::linkedlist,
|
|
clippy::lossy_float_literal,
|
|
clippy::macro_use_imports,
|
|
clippy::manual_ok_or,
|
|
clippy::map_err_ignore,
|
|
clippy::map_flatten,
|
|
clippy::match_on_vec_items,
|
|
clippy::match_same_arms,
|
|
clippy::match_wildcard_for_single_variants,
|
|
clippy::mem_forget,
|
|
clippy::mismatched_target_os,
|
|
clippy::missing_errors_doc,
|
|
clippy::missing_safety_doc,
|
|
clippy::mut_mut,
|
|
clippy::mutex_integer,
|
|
clippy::needless_borrow,
|
|
clippy::needless_continue,
|
|
clippy::needless_pass_by_value,
|
|
clippy::option_option,
|
|
clippy::path_buf_push_overwrite,
|
|
clippy::ptr_as_ptr,
|
|
clippy::ref_option_ref,
|
|
clippy::rest_pat_in_fully_bound_structs,
|
|
clippy::same_functions_in_if_condition,
|
|
clippy::string_add_assign,
|
|
clippy::string_add,
|
|
clippy::string_lit_as_bytes,
|
|
clippy::string_to_string,
|
|
clippy::todo,
|
|
clippy::trait_duplication_in_bounds,
|
|
clippy::unimplemented,
|
|
clippy::unnested_or_patterns,
|
|
clippy::unused_self,
|
|
clippy::useless_transmute,
|
|
clippy::verbose_file_reads,
|
|
clippy::zero_sized_map_values,
|
|
future_incompatible,
|
|
missing_crate_level_docs,
|
|
nonstandard_style,
|
|
rust_2018_idioms
|
|
)]
|
|
#![allow(clippy::float_cmp)]
|
|
#![allow(clippy::manual_range_contains)]
|
|
|
|
pub use winit;
|
|
|
|
pub mod clipboard;
|
|
pub mod screen_reader;
|
|
mod window_settings;
|
|
|
|
pub use window_settings::WindowSettings;
|
|
|
|
pub fn native_pixels_per_point(window: &winit::window::Window) -> f32 {
|
|
window.scale_factor() as f32
|
|
}
|
|
|
|
pub fn screen_size_in_pixels(window: &winit::window::Window) -> egui::Vec2 {
|
|
// let (width_in_pixels, height_in_pixels) = display.get_framebuffer_dimensions();
|
|
// egui::vec2(width_in_pixels as f32, height_in_pixels as f32)
|
|
let size = window.inner_size();
|
|
egui::vec2(size.width as f32, size.height as f32)
|
|
}
|
|
|
|
/// Handles the integration between egui and winit.
|
|
pub struct State {
|
|
start_time: std::time::Instant,
|
|
egui_input: egui::RawInput,
|
|
pointer_pos_in_points: Option<egui::Pos2>,
|
|
any_pointer_button_down: bool,
|
|
current_cursor_icon: egui::CursorIcon,
|
|
/// What egui uses.
|
|
current_pixels_per_point: f32,
|
|
|
|
clipboard: clipboard::Clipboard,
|
|
screen_reader: screen_reader::ScreenReader,
|
|
|
|
/// If `true`, mouse inputs will be treated as touches.
|
|
/// Useful for debugging touch support in egui.
|
|
///
|
|
/// Creates duplicate touches, if real touch inputs are coming.
|
|
simulate_touch_screen: bool,
|
|
|
|
/// Is Some(…) when a touch is being translated to a pointer.
|
|
///
|
|
/// Only one touch will be interpreted as pointer at any time.
|
|
pointer_touch_id: Option<u64>,
|
|
}
|
|
|
|
impl State {
|
|
/// Initialize with the native `pixels_per_point` (dpi scaling).
|
|
pub fn new(window: &winit::window::Window) -> Self {
|
|
Self::from_pixels_per_point(native_pixels_per_point(window))
|
|
}
|
|
|
|
/// Initialize with a given dpi scaling.
|
|
pub fn from_pixels_per_point(pixels_per_point: f32) -> Self {
|
|
Self {
|
|
start_time: std::time::Instant::now(),
|
|
egui_input: egui::RawInput {
|
|
pixels_per_point: Some(pixels_per_point),
|
|
..Default::default()
|
|
},
|
|
pointer_pos_in_points: None,
|
|
any_pointer_button_down: false,
|
|
current_cursor_icon: egui::CursorIcon::Default,
|
|
current_pixels_per_point: pixels_per_point,
|
|
|
|
clipboard: Default::default(),
|
|
screen_reader: screen_reader::ScreenReader::default(),
|
|
|
|
simulate_touch_screen: false,
|
|
pointer_touch_id: None,
|
|
}
|
|
}
|
|
|
|
/// The number of physical pixels per logical point,
|
|
/// as configured on the current egui context (see [`egui::Context::pixels_per_point`]).
|
|
#[inline]
|
|
pub fn pixels_per_point(&self) -> f32 {
|
|
self.current_pixels_per_point
|
|
}
|
|
|
|
/// The current input state.
|
|
/// This is changed by [`Self::on_event`] and cleared by [`Self::take_egui_input`].
|
|
#[inline]
|
|
pub fn egui_input(&self) -> &egui::RawInput {
|
|
&self.egui_input
|
|
}
|
|
|
|
/// Prepare for a new frame by extracting the accumulated input,
|
|
/// as well as setting [the time](egui::RawInput::time) and [screen rectangle](egui::RawInput::screen_rect).
|
|
pub fn take_egui_input(&mut self, display: &winit::window::Window) -> egui::RawInput {
|
|
let pixels_per_point = self.pixels_per_point();
|
|
|
|
self.egui_input.time = Some(self.start_time.elapsed().as_secs_f64());
|
|
|
|
// On Windows, a minimized window will have 0 width and height.
|
|
// See: https://github.com/rust-windowing/winit/issues/208
|
|
// This solves an issue where egui window positions would be changed when minimizing on Windows.
|
|
let screen_size_in_pixels = screen_size_in_pixels(display);
|
|
let screen_size_in_points = screen_size_in_pixels / pixels_per_point;
|
|
self.egui_input.screen_rect =
|
|
if screen_size_in_points.x > 0.0 && screen_size_in_points.y > 0.0 {
|
|
Some(egui::Rect::from_min_size(
|
|
egui::Pos2::ZERO,
|
|
screen_size_in_points,
|
|
))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
self.egui_input.take()
|
|
}
|
|
|
|
/// Call this when there is a new event.
|
|
///
|
|
/// The result can be found in [`Self::egui_input`] and be extracted with [`Self::take_egui_input`].
|
|
///
|
|
/// Returns `true` if egui wants exclusive use of this event
|
|
/// (e.g. a mouse click on an egui window, or entering text into a text field).
|
|
/// For instance, if you use egui for a game, you want to first call this
|
|
/// and only when this returns `false` pass on the events to your game.
|
|
///
|
|
/// Note that egui uses `tab` to move focus between elements, so this will always return `true` for tabs.
|
|
pub fn on_event(
|
|
&mut self,
|
|
egui_ctx: &egui::Context,
|
|
event: &winit::event::WindowEvent<'_>,
|
|
) -> bool {
|
|
use winit::event::WindowEvent;
|
|
match event {
|
|
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
|
|
let pixels_per_point = *scale_factor as f32;
|
|
self.egui_input.pixels_per_point = Some(pixels_per_point);
|
|
self.current_pixels_per_point = pixels_per_point;
|
|
false
|
|
}
|
|
WindowEvent::MouseInput { state, button, .. } => {
|
|
self.on_mouse_button_input(*state, *button);
|
|
egui_ctx.wants_pointer_input()
|
|
}
|
|
WindowEvent::MouseWheel { delta, .. } => {
|
|
self.on_mouse_wheel(*delta);
|
|
egui_ctx.wants_pointer_input()
|
|
}
|
|
WindowEvent::CursorMoved { position, .. } => {
|
|
self.on_cursor_moved(*position);
|
|
egui_ctx.is_using_pointer()
|
|
}
|
|
WindowEvent::CursorLeft { .. } => {
|
|
self.pointer_pos_in_points = None;
|
|
self.egui_input.events.push(egui::Event::PointerGone);
|
|
false
|
|
}
|
|
// WindowEvent::TouchpadPressure {device_id, pressure, stage, .. } => {} // TODO
|
|
WindowEvent::Touch(touch) => {
|
|
self.on_touch(touch);
|
|
match touch.phase {
|
|
winit::event::TouchPhase::Started
|
|
| winit::event::TouchPhase::Ended
|
|
| winit::event::TouchPhase::Cancelled => egui_ctx.wants_pointer_input(),
|
|
winit::event::TouchPhase::Moved => egui_ctx.is_using_pointer(),
|
|
}
|
|
}
|
|
WindowEvent::ReceivedCharacter(ch) => {
|
|
// On Mac we get here when the user presses Cmd-C (copy), ctrl-W, etc.
|
|
// We need to ignore these characters that are side-effects of commands.
|
|
let is_mac_cmd = cfg!(target_os = "macos")
|
|
&& (self.egui_input.modifiers.ctrl || self.egui_input.modifiers.mac_cmd);
|
|
|
|
if is_printable_char(*ch) && !is_mac_cmd {
|
|
self.egui_input
|
|
.events
|
|
.push(egui::Event::Text(ch.to_string()));
|
|
egui_ctx.wants_keyboard_input()
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
WindowEvent::KeyboardInput { input, .. } => {
|
|
self.on_keyboard_input(input);
|
|
egui_ctx.wants_keyboard_input()
|
|
|| input.virtual_keycode == Some(winit::event::VirtualKeyCode::Tab)
|
|
}
|
|
WindowEvent::Focused(_) => {
|
|
// We will not be given a KeyboardInput event when the modifiers are released while
|
|
// the window does not have focus. Unset all modifier state to be safe.
|
|
self.egui_input.modifiers = egui::Modifiers::default();
|
|
false
|
|
}
|
|
WindowEvent::HoveredFile(path) => {
|
|
self.egui_input.hovered_files.push(egui::HoveredFile {
|
|
path: Some(path.clone()),
|
|
..Default::default()
|
|
});
|
|
false
|
|
}
|
|
WindowEvent::HoveredFileCancelled => {
|
|
self.egui_input.hovered_files.clear();
|
|
false
|
|
}
|
|
WindowEvent::DroppedFile(path) => {
|
|
self.egui_input.hovered_files.clear();
|
|
self.egui_input.dropped_files.push(egui::DroppedFile {
|
|
path: Some(path.clone()),
|
|
..Default::default()
|
|
});
|
|
false
|
|
}
|
|
_ => {
|
|
// dbg!(event);
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
fn on_mouse_button_input(
|
|
&mut self,
|
|
state: winit::event::ElementState,
|
|
button: winit::event::MouseButton,
|
|
) {
|
|
if let Some(pos) = self.pointer_pos_in_points {
|
|
if let Some(button) = translate_mouse_button(button) {
|
|
let pressed = state == winit::event::ElementState::Pressed;
|
|
|
|
self.egui_input.events.push(egui::Event::PointerButton {
|
|
pos,
|
|
button,
|
|
pressed,
|
|
modifiers: self.egui_input.modifiers,
|
|
});
|
|
|
|
if self.simulate_touch_screen {
|
|
if pressed {
|
|
self.any_pointer_button_down = true;
|
|
|
|
self.egui_input.events.push(egui::Event::Touch {
|
|
device_id: egui::TouchDeviceId(0),
|
|
id: egui::TouchId(0),
|
|
phase: egui::TouchPhase::Start,
|
|
pos,
|
|
force: 0.0,
|
|
});
|
|
} else {
|
|
self.any_pointer_button_down = false;
|
|
|
|
self.egui_input.events.push(egui::Event::PointerGone);
|
|
|
|
self.egui_input.events.push(egui::Event::Touch {
|
|
device_id: egui::TouchDeviceId(0),
|
|
id: egui::TouchId(0),
|
|
phase: egui::TouchPhase::End,
|
|
pos,
|
|
force: 0.0,
|
|
});
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn on_cursor_moved(&mut self, pos_in_pixels: winit::dpi::PhysicalPosition<f64>) {
|
|
let pos_in_points = egui::pos2(
|
|
pos_in_pixels.x as f32 / self.pixels_per_point(),
|
|
pos_in_pixels.y as f32 / self.pixels_per_point(),
|
|
);
|
|
self.pointer_pos_in_points = Some(pos_in_points);
|
|
|
|
if self.simulate_touch_screen {
|
|
if self.any_pointer_button_down {
|
|
self.egui_input
|
|
.events
|
|
.push(egui::Event::PointerMoved(pos_in_points));
|
|
|
|
self.egui_input.events.push(egui::Event::Touch {
|
|
device_id: egui::TouchDeviceId(0),
|
|
id: egui::TouchId(0),
|
|
phase: egui::TouchPhase::Move,
|
|
pos: pos_in_points,
|
|
force: 0.0,
|
|
});
|
|
}
|
|
} else {
|
|
self.egui_input
|
|
.events
|
|
.push(egui::Event::PointerMoved(pos_in_points));
|
|
}
|
|
}
|
|
|
|
fn on_touch(&mut self, touch: &winit::event::Touch) {
|
|
// Emit touch event
|
|
self.egui_input.events.push(egui::Event::Touch {
|
|
device_id: egui::TouchDeviceId(egui::epaint::util::hash(touch.device_id)),
|
|
id: egui::TouchId::from(touch.id),
|
|
phase: match touch.phase {
|
|
winit::event::TouchPhase::Started => egui::TouchPhase::Start,
|
|
winit::event::TouchPhase::Moved => egui::TouchPhase::Move,
|
|
winit::event::TouchPhase::Ended => egui::TouchPhase::End,
|
|
winit::event::TouchPhase::Cancelled => egui::TouchPhase::Cancel,
|
|
},
|
|
pos: egui::pos2(
|
|
touch.location.x as f32 / self.pixels_per_point(),
|
|
touch.location.y as f32 / self.pixels_per_point(),
|
|
),
|
|
force: match touch.force {
|
|
Some(winit::event::Force::Normalized(force)) => force as f32,
|
|
Some(winit::event::Force::Calibrated {
|
|
force,
|
|
max_possible_force,
|
|
..
|
|
}) => (force / max_possible_force) as f32,
|
|
None => 0_f32,
|
|
},
|
|
});
|
|
// If we're not yet tanslating a touch or we're translating this very
|
|
// touch …
|
|
if self.pointer_touch_id.is_none() || self.pointer_touch_id.unwrap() == touch.id {
|
|
// … emit PointerButton resp. PointerMoved events to emulate mouse
|
|
match touch.phase {
|
|
winit::event::TouchPhase::Started => {
|
|
self.pointer_touch_id = Some(touch.id);
|
|
// First move the pointer to the right location
|
|
self.on_cursor_moved(touch.location);
|
|
self.on_mouse_button_input(
|
|
winit::event::ElementState::Pressed,
|
|
winit::event::MouseButton::Left,
|
|
);
|
|
}
|
|
winit::event::TouchPhase::Moved => {
|
|
self.on_cursor_moved(touch.location);
|
|
}
|
|
winit::event::TouchPhase::Ended => {
|
|
self.pointer_touch_id = None;
|
|
self.on_mouse_button_input(
|
|
winit::event::ElementState::Released,
|
|
winit::event::MouseButton::Left,
|
|
);
|
|
// The pointer should vanish completely to not get any
|
|
// hover effects
|
|
self.pointer_pos_in_points = None;
|
|
self.egui_input.events.push(egui::Event::PointerGone);
|
|
}
|
|
winit::event::TouchPhase::Cancelled => {
|
|
self.pointer_touch_id = None;
|
|
self.pointer_pos_in_points = None;
|
|
self.egui_input.events.push(egui::Event::PointerGone);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn on_mouse_wheel(&mut self, delta: winit::event::MouseScrollDelta) {
|
|
let mut delta = match delta {
|
|
winit::event::MouseScrollDelta::LineDelta(x, y) => {
|
|
let points_per_scroll_line = 50.0; // Scroll speed decided by consensus: https://github.com/emilk/egui/issues/461
|
|
egui::vec2(x, y) * points_per_scroll_line
|
|
}
|
|
winit::event::MouseScrollDelta::PixelDelta(delta) => {
|
|
egui::vec2(delta.x as f32, delta.y as f32) / self.pixels_per_point()
|
|
}
|
|
};
|
|
if cfg!(target_os = "macos") {
|
|
// This is still buggy in winit despite
|
|
// https://github.com/rust-windowing/winit/issues/1695 being closed
|
|
delta.x *= -1.0;
|
|
}
|
|
|
|
if self.egui_input.modifiers.ctrl || self.egui_input.modifiers.command {
|
|
// Treat as zoom instead:
|
|
self.egui_input.zoom_delta *= (delta.y / 200.0).exp();
|
|
} else {
|
|
self.egui_input.scroll_delta += delta;
|
|
}
|
|
}
|
|
|
|
fn on_keyboard_input(&mut self, input: &winit::event::KeyboardInput) {
|
|
if let Some(keycode) = input.virtual_keycode {
|
|
use winit::event::VirtualKeyCode;
|
|
|
|
let pressed = input.state == winit::event::ElementState::Pressed;
|
|
|
|
// We could also use `WindowEvent::ModifiersChanged` instead, I guess.
|
|
if matches!(keycode, VirtualKeyCode::LAlt | VirtualKeyCode::RAlt) {
|
|
self.egui_input.modifiers.alt = pressed;
|
|
}
|
|
if matches!(keycode, VirtualKeyCode::LControl | VirtualKeyCode::RControl) {
|
|
self.egui_input.modifiers.ctrl = pressed;
|
|
if !cfg!(target_os = "macos") {
|
|
self.egui_input.modifiers.command = pressed;
|
|
}
|
|
}
|
|
if matches!(keycode, VirtualKeyCode::LShift | VirtualKeyCode::RShift) {
|
|
self.egui_input.modifiers.shift = pressed;
|
|
}
|
|
if cfg!(target_os = "macos")
|
|
&& matches!(keycode, VirtualKeyCode::LWin | VirtualKeyCode::RWin)
|
|
{
|
|
self.egui_input.modifiers.mac_cmd = pressed;
|
|
self.egui_input.modifiers.command = pressed;
|
|
}
|
|
|
|
if pressed {
|
|
// VirtualKeyCode::Paste etc in winit are broken/untrustworthy,
|
|
// so we detect these things manually:
|
|
if is_cut_command(self.egui_input.modifiers, keycode) {
|
|
self.egui_input.events.push(egui::Event::Cut);
|
|
} else if is_copy_command(self.egui_input.modifiers, keycode) {
|
|
self.egui_input.events.push(egui::Event::Copy);
|
|
} else if is_paste_command(self.egui_input.modifiers, keycode) {
|
|
if let Some(contents) = self.clipboard.get() {
|
|
self.egui_input.events.push(egui::Event::Text(contents));
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(key) = translate_virtual_key_code(keycode) {
|
|
self.egui_input.events.push(egui::Event::Key {
|
|
key,
|
|
pressed,
|
|
modifiers: self.egui_input.modifiers,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Call with the output given by `egui`.
|
|
///
|
|
/// This will, if needed:
|
|
/// * update the cursor
|
|
/// * copy text to the clipboard
|
|
/// * open any clicked urls
|
|
/// * update the IME
|
|
/// *
|
|
pub fn handle_output(
|
|
&mut self,
|
|
window: &winit::window::Window,
|
|
egui_ctx: &egui::Context,
|
|
output: egui::Output,
|
|
) {
|
|
self.current_pixels_per_point = egui_ctx.pixels_per_point(); // someone can have changed it to scale the UI
|
|
|
|
if egui_ctx.memory().options.screen_reader {
|
|
self.screen_reader.speak(&output.events_description());
|
|
}
|
|
|
|
self.set_cursor_icon(window, output.cursor_icon);
|
|
|
|
if let Some(open) = output.open_url {
|
|
open_url(&open.url);
|
|
}
|
|
|
|
if !output.copied_text.is_empty() {
|
|
self.clipboard.set(output.copied_text);
|
|
}
|
|
|
|
if let Some(egui::Pos2 { x, y }) = output.text_cursor_pos {
|
|
window.set_ime_position(winit::dpi::LogicalPosition { x, y })
|
|
}
|
|
}
|
|
|
|
/// Returns `true` if Alt-F4 (windows/linux) or Cmd-Q (Mac)
|
|
pub fn is_quit_shortcut(&self, input: &winit::event::KeyboardInput) -> bool {
|
|
if cfg!(target_os = "macos") {
|
|
input.state == winit::event::ElementState::Pressed
|
|
&& self.egui_input.modifiers.mac_cmd
|
|
&& input.virtual_keycode == Some(winit::event::VirtualKeyCode::Q)
|
|
} else {
|
|
input.state == winit::event::ElementState::Pressed
|
|
&& self.egui_input.modifiers.alt
|
|
&& input.virtual_keycode == Some(winit::event::VirtualKeyCode::F4)
|
|
}
|
|
}
|
|
|
|
/// Returns `true` if this a close event or a Cmd-Q/Alt-F4 keyboard command.
|
|
pub fn is_quit_event(&self, event: &winit::event::WindowEvent<'_>) -> bool {
|
|
use winit::event::WindowEvent;
|
|
match event {
|
|
WindowEvent::CloseRequested | WindowEvent::Destroyed => true,
|
|
WindowEvent::KeyboardInput { input, .. } => self.is_quit_shortcut(input),
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
fn set_cursor_icon(&mut self, window: &winit::window::Window, cursor_icon: egui::CursorIcon) {
|
|
// prevent flickering near frame boundary when Windows OS tries to control cursor icon for window resizing
|
|
if self.current_cursor_icon == cursor_icon {
|
|
return;
|
|
}
|
|
self.current_cursor_icon = cursor_icon;
|
|
|
|
if let Some(cursor_icon) = translate_cursor(cursor_icon) {
|
|
window.set_cursor_visible(true);
|
|
|
|
let is_pointer_in_window = self.pointer_pos_in_points.is_some();
|
|
if is_pointer_in_window {
|
|
window.set_cursor_icon(cursor_icon);
|
|
}
|
|
} else {
|
|
window.set_cursor_visible(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn open_url(_url: &str) {
|
|
#[cfg(feature = "webbrowser")]
|
|
if let Err(err) = webbrowser::open(_url) {
|
|
eprintln!("Failed to open url: {}", err);
|
|
}
|
|
|
|
#[cfg(not(feature = "webbrowser"))]
|
|
{
|
|
eprintln!("Cannot open url - feature \"links\" not enabled.");
|
|
}
|
|
}
|
|
|
|
/// Winit sends special keys (backspace, delete, F1, …) as characters.
|
|
/// Ignore those.
|
|
/// We also ignore '\r', '\n', '\t'.
|
|
/// Newlines are handled by the `Key::Enter` event.
|
|
fn is_printable_char(chr: char) -> bool {
|
|
let is_in_private_use_area = '\u{e000}' <= chr && chr <= '\u{f8ff}'
|
|
|| '\u{f0000}' <= chr && chr <= '\u{ffffd}'
|
|
|| '\u{100000}' <= chr && chr <= '\u{10fffd}';
|
|
|
|
!is_in_private_use_area && !chr.is_ascii_control()
|
|
}
|
|
|
|
fn is_cut_command(modifiers: egui::Modifiers, keycode: winit::event::VirtualKeyCode) -> bool {
|
|
(modifiers.command && keycode == winit::event::VirtualKeyCode::X)
|
|
|| (cfg!(target_os = "windows")
|
|
&& modifiers.shift
|
|
&& keycode == winit::event::VirtualKeyCode::Delete)
|
|
}
|
|
|
|
fn is_copy_command(modifiers: egui::Modifiers, keycode: winit::event::VirtualKeyCode) -> bool {
|
|
(modifiers.command && keycode == winit::event::VirtualKeyCode::C)
|
|
|| (cfg!(target_os = "windows")
|
|
&& modifiers.ctrl
|
|
&& keycode == winit::event::VirtualKeyCode::Insert)
|
|
}
|
|
|
|
fn is_paste_command(modifiers: egui::Modifiers, keycode: winit::event::VirtualKeyCode) -> bool {
|
|
(modifiers.command && keycode == winit::event::VirtualKeyCode::V)
|
|
|| (cfg!(target_os = "windows")
|
|
&& modifiers.shift
|
|
&& keycode == winit::event::VirtualKeyCode::Insert)
|
|
}
|
|
|
|
fn translate_mouse_button(button: winit::event::MouseButton) -> Option<egui::PointerButton> {
|
|
match button {
|
|
winit::event::MouseButton::Left => Some(egui::PointerButton::Primary),
|
|
winit::event::MouseButton::Right => Some(egui::PointerButton::Secondary),
|
|
winit::event::MouseButton::Middle => Some(egui::PointerButton::Middle),
|
|
winit::event::MouseButton::Other(_) => None,
|
|
}
|
|
}
|
|
|
|
fn translate_virtual_key_code(key: winit::event::VirtualKeyCode) -> Option<egui::Key> {
|
|
use egui::Key;
|
|
use winit::event::VirtualKeyCode;
|
|
|
|
Some(match key {
|
|
VirtualKeyCode::Down => Key::ArrowDown,
|
|
VirtualKeyCode::Left => Key::ArrowLeft,
|
|
VirtualKeyCode::Right => Key::ArrowRight,
|
|
VirtualKeyCode::Up => Key::ArrowUp,
|
|
|
|
VirtualKeyCode::Escape => Key::Escape,
|
|
VirtualKeyCode::Tab => Key::Tab,
|
|
VirtualKeyCode::Back => Key::Backspace,
|
|
VirtualKeyCode::Return => Key::Enter,
|
|
VirtualKeyCode::Space => Key::Space,
|
|
|
|
VirtualKeyCode::Insert => Key::Insert,
|
|
VirtualKeyCode::Delete => Key::Delete,
|
|
VirtualKeyCode::Home => Key::Home,
|
|
VirtualKeyCode::End => Key::End,
|
|
VirtualKeyCode::PageUp => Key::PageUp,
|
|
VirtualKeyCode::PageDown => Key::PageDown,
|
|
|
|
VirtualKeyCode::Key0 | VirtualKeyCode::Numpad0 => Key::Num0,
|
|
VirtualKeyCode::Key1 | VirtualKeyCode::Numpad1 => Key::Num1,
|
|
VirtualKeyCode::Key2 | VirtualKeyCode::Numpad2 => Key::Num2,
|
|
VirtualKeyCode::Key3 | VirtualKeyCode::Numpad3 => Key::Num3,
|
|
VirtualKeyCode::Key4 | VirtualKeyCode::Numpad4 => Key::Num4,
|
|
VirtualKeyCode::Key5 | VirtualKeyCode::Numpad5 => Key::Num5,
|
|
VirtualKeyCode::Key6 | VirtualKeyCode::Numpad6 => Key::Num6,
|
|
VirtualKeyCode::Key7 | VirtualKeyCode::Numpad7 => Key::Num7,
|
|
VirtualKeyCode::Key8 | VirtualKeyCode::Numpad8 => Key::Num8,
|
|
VirtualKeyCode::Key9 | VirtualKeyCode::Numpad9 => Key::Num9,
|
|
|
|
VirtualKeyCode::A => Key::A,
|
|
VirtualKeyCode::B => Key::B,
|
|
VirtualKeyCode::C => Key::C,
|
|
VirtualKeyCode::D => Key::D,
|
|
VirtualKeyCode::E => Key::E,
|
|
VirtualKeyCode::F => Key::F,
|
|
VirtualKeyCode::G => Key::G,
|
|
VirtualKeyCode::H => Key::H,
|
|
VirtualKeyCode::I => Key::I,
|
|
VirtualKeyCode::J => Key::J,
|
|
VirtualKeyCode::K => Key::K,
|
|
VirtualKeyCode::L => Key::L,
|
|
VirtualKeyCode::M => Key::M,
|
|
VirtualKeyCode::N => Key::N,
|
|
VirtualKeyCode::O => Key::O,
|
|
VirtualKeyCode::P => Key::P,
|
|
VirtualKeyCode::Q => Key::Q,
|
|
VirtualKeyCode::R => Key::R,
|
|
VirtualKeyCode::S => Key::S,
|
|
VirtualKeyCode::T => Key::T,
|
|
VirtualKeyCode::U => Key::U,
|
|
VirtualKeyCode::V => Key::V,
|
|
VirtualKeyCode::W => Key::W,
|
|
VirtualKeyCode::X => Key::X,
|
|
VirtualKeyCode::Y => Key::Y,
|
|
VirtualKeyCode::Z => Key::Z,
|
|
|
|
_ => {
|
|
return None;
|
|
}
|
|
})
|
|
}
|
|
|
|
fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option<winit::window::CursorIcon> {
|
|
match cursor_icon {
|
|
egui::CursorIcon::None => None,
|
|
|
|
egui::CursorIcon::Alias => Some(winit::window::CursorIcon::Alias),
|
|
egui::CursorIcon::AllScroll => Some(winit::window::CursorIcon::AllScroll),
|
|
egui::CursorIcon::Cell => Some(winit::window::CursorIcon::Cell),
|
|
egui::CursorIcon::ContextMenu => Some(winit::window::CursorIcon::ContextMenu),
|
|
egui::CursorIcon::Copy => Some(winit::window::CursorIcon::Copy),
|
|
egui::CursorIcon::Crosshair => Some(winit::window::CursorIcon::Crosshair),
|
|
egui::CursorIcon::Default => Some(winit::window::CursorIcon::Default),
|
|
egui::CursorIcon::Grab => Some(winit::window::CursorIcon::Grab),
|
|
egui::CursorIcon::Grabbing => Some(winit::window::CursorIcon::Grabbing),
|
|
egui::CursorIcon::Help => Some(winit::window::CursorIcon::Help),
|
|
egui::CursorIcon::Move => Some(winit::window::CursorIcon::Move),
|
|
egui::CursorIcon::NoDrop => Some(winit::window::CursorIcon::NoDrop),
|
|
egui::CursorIcon::NotAllowed => Some(winit::window::CursorIcon::NotAllowed),
|
|
egui::CursorIcon::PointingHand => Some(winit::window::CursorIcon::Hand),
|
|
egui::CursorIcon::Progress => Some(winit::window::CursorIcon::Progress),
|
|
egui::CursorIcon::ResizeHorizontal => Some(winit::window::CursorIcon::EwResize),
|
|
egui::CursorIcon::ResizeNeSw => Some(winit::window::CursorIcon::NeswResize),
|
|
egui::CursorIcon::ResizeNwSe => Some(winit::window::CursorIcon::NwseResize),
|
|
egui::CursorIcon::ResizeVertical => Some(winit::window::CursorIcon::NsResize),
|
|
egui::CursorIcon::Text => Some(winit::window::CursorIcon::Text),
|
|
egui::CursorIcon::VerticalText => Some(winit::window::CursorIcon::VerticalText),
|
|
egui::CursorIcon::Wait => Some(winit::window::CursorIcon::Wait),
|
|
egui::CursorIcon::ZoomIn => Some(winit::window::CursorIcon::ZoomIn),
|
|
egui::CursorIcon::ZoomOut => Some(winit::window::CursorIcon::ZoomOut),
|
|
}
|
|
}
|