mirror of https://github.com/emilk/egui.git
Emil Ernerfeldt
3 years ago
committed by
GitHub
27 changed files with 1003 additions and 616 deletions
@ -0,0 +1,7 @@ |
|||
# Changelog for egui-winit |
|||
|
|||
All notable changes to the `egui-winit` integration will be noted in this file. |
|||
|
|||
|
|||
## Unreleased |
|||
First stand-alone release. Previously part of `egui_glium`. |
@ -0,0 +1,45 @@ |
|||
[package] |
|||
name = "egui-winit" |
|||
version = "0.14.0" |
|||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"] |
|||
description = "Bindings for using egui with winit" |
|||
edition = "2018" |
|||
homepage = "https://github.com/emilk/egui/tree/master/egui-winit" |
|||
license = "MIT OR Apache-2.0" |
|||
readme = "README.md" |
|||
repository = "https://github.com/emilk/egui/tree/master/egui-winit" |
|||
categories = ["gui", "game-development"] |
|||
keywords = ["winit", "egui", "gui", "gamedev"] |
|||
include = [ |
|||
"../LICENSE-APACHE", |
|||
"../LICENSE-MIT", |
|||
"**/*.rs", |
|||
"Cargo.toml", |
|||
] |
|||
|
|||
[package.metadata.docs.rs] |
|||
all-features = true |
|||
|
|||
[dependencies] |
|||
egui = { version = "0.14.0", path = "../egui", default-features = false } |
|||
epi = { version = "0.14.0", path = "../epi" } |
|||
winit = "0.25" |
|||
|
|||
copypasta = { version = "0.7", optional = true } |
|||
webbrowser = { version = "0.5", optional = true } |
|||
|
|||
# feature screen_reader |
|||
tts = { version = "0.17", optional = true } |
|||
|
|||
[features] |
|||
default = ["clipboard", "links"] |
|||
|
|||
# enable cut/copy/paste to OS clipboard. |
|||
# if disabled a clipboard will be simulated so you can still copy/paste within the egui app. |
|||
clipboard = ["copypasta"] |
|||
|
|||
# enable opening links in a browser when an egui hyperlink is clicked. |
|||
links = ["webbrowser"] |
|||
|
|||
# experimental support for a screen reader |
|||
screen_reader = ["tts"] |
@ -0,0 +1,11 @@ |
|||
# egui-winit |
|||
|
|||
[![Latest version](https://img.shields.io/crates/v/egui-winit.svg)](https://crates.io/crates/egui-winit) |
|||
[![Documentation](https://docs.rs/egui-winit/badge.svg)](https://docs.rs/egui-winit) |
|||
[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) |
|||
![MIT](https://img.shields.io/badge/license-MIT-blue.svg) |
|||
![Apache](https://img.shields.io/badge/license-Apache-blue.svg) |
|||
|
|||
This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [winit](https://crates.io/crates/winit). |
|||
|
|||
The library translates winit events to egui, handled copy/paste, updates the cursor, open links clicked in egui, etc. |
@ -0,0 +1,69 @@ |
|||
/// Handles interfacing either with the OS clipboard.
|
|||
/// If the "clipboard" feature is off it will instead simulate the clipboard locally.
|
|||
pub struct Clipboard { |
|||
#[cfg(feature = "copypasta")] |
|||
copypasta: Option<copypasta::ClipboardContext>, |
|||
|
|||
/// Fallback manual clipboard.
|
|||
#[cfg(not(feature = "copypasta"))] |
|||
clipboard: String, |
|||
} |
|||
|
|||
impl Default for Clipboard { |
|||
fn default() -> Self { |
|||
Self { |
|||
#[cfg(feature = "copypasta")] |
|||
copypasta: init_copypasta(), |
|||
|
|||
#[cfg(not(feature = "copypasta"))] |
|||
clipboard: String::default(), |
|||
} |
|||
} |
|||
} |
|||
|
|||
impl Clipboard { |
|||
pub fn get(&mut self) -> Option<String> { |
|||
#[cfg(feature = "copypasta")] |
|||
if let Some(clipboard) = &mut self.copypasta { |
|||
use copypasta::ClipboardProvider as _; |
|||
match clipboard.get_contents() { |
|||
Ok(contents) => Some(contents), |
|||
Err(err) => { |
|||
eprintln!("Paste error: {}", err); |
|||
None |
|||
} |
|||
} |
|||
} else { |
|||
None |
|||
} |
|||
|
|||
#[cfg(not(feature = "copypasta"))] |
|||
Some(self.clipboard.clone()) |
|||
} |
|||
|
|||
pub fn set(&mut self, text: String) { |
|||
#[cfg(feature = "copypasta")] |
|||
if let Some(clipboard) = &mut self.copypasta { |
|||
use copypasta::ClipboardProvider as _; |
|||
if let Err(err) = clipboard.set_contents(text) { |
|||
eprintln!("Copy/Cut error: {}", err); |
|||
} |
|||
} |
|||
|
|||
#[cfg(not(feature = "copypasta"))] |
|||
{ |
|||
self.clipboard = text; |
|||
} |
|||
} |
|||
} |
|||
|
|||
#[cfg(feature = "copypasta")] |
|||
fn init_copypasta() -> Option<copypasta::ClipboardContext> { |
|||
match copypasta::ClipboardContext::new() { |
|||
Ok(clipboard) => Some(clipboard), |
|||
Err(err) => { |
|||
eprintln!("Failed to initialize clipboard: {}", err); |
|||
None |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,675 @@ |
|||
//! [`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; |
|||
|
|||
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.
|
|||
simulate_touch_screen: bool, |
|||
} |
|||
|
|||
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, |
|||
} |
|||
} |
|||
|
|||
/// 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) => { |
|||
if is_printable_char(*ch) |
|||
&& !self.egui_input.modifiers.ctrl |
|||
&& !self.egui_input.modifiers.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) { |
|||
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, |
|||
}, |
|||
}); |
|||
} |
|||
|
|||
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."); |
|||
} |
|||
} |
|||
|
|||
/// Glium 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), |
|||
} |
|||
} |
Loading…
Reference in new issue