2021-01-02 10:59:20 +00:00
|
|
|
//! [`egui`] bindings for web apps (compiling to WASM).
|
|
|
|
//!
|
|
|
|
//! This library is an [`epi`] backend.
|
|
|
|
//!
|
|
|
|
//! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead.
|
|
|
|
|
2020-10-18 13:40:23 +00:00
|
|
|
#![forbid(unsafe_code)]
|
2020-12-18 21:19:56 +00:00
|
|
|
#![cfg_attr(not(debug_assertions), deny(warnings))] // Forbid warnings in release builds
|
2021-01-02 11:02:26 +00:00
|
|
|
#![warn(clippy::all, rust_2018_idioms)]
|
2019-01-12 22:07:30 +00:00
|
|
|
|
2020-07-23 16:54:16 +00:00
|
|
|
pub mod backend;
|
2021-01-04 00:44:02 +00:00
|
|
|
#[cfg(feature = "http")]
|
2020-12-30 19:56:50 +00:00
|
|
|
pub mod http;
|
2021-01-16 00:30:00 +00:00
|
|
|
mod painter;
|
|
|
|
pub mod webgl1;
|
|
|
|
pub mod webgl2;
|
2019-02-11 19:27:32 +00:00
|
|
|
|
2020-07-23 16:54:16 +00:00
|
|
|
pub use backend::*;
|
|
|
|
|
2021-01-16 00:30:00 +00:00
|
|
|
use egui::mutex::Mutex;
|
2020-12-29 14:57:13 +00:00
|
|
|
pub use wasm_bindgen;
|
|
|
|
pub use web_sys;
|
|
|
|
|
2021-01-16 00:30:00 +00:00
|
|
|
pub use painter::Painter;
|
2020-07-18 16:35:17 +00:00
|
|
|
use std::sync::Arc;
|
|
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
|
2019-02-11 19:27:32 +00:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Helpers to hide some of the verbosity of web_sys
|
|
|
|
|
2020-11-20 19:35:16 +00:00
|
|
|
/// Log some text to the developer console (`console.log(...)` in JS)
|
2020-11-17 23:43:58 +00:00
|
|
|
pub fn console_log(s: impl Into<JsValue>) {
|
2019-02-11 19:27:32 +00:00
|
|
|
web_sys::console::log_1(&s.into());
|
|
|
|
}
|
|
|
|
|
2020-11-20 19:35:16 +00:00
|
|
|
/// Log a warning to the developer console (`console.warn(...)` in JS)
|
2020-11-18 20:38:29 +00:00
|
|
|
pub fn console_warn(s: impl Into<JsValue>) {
|
|
|
|
web_sys::console::warn_1(&s.into());
|
|
|
|
}
|
|
|
|
|
2020-11-20 19:35:16 +00:00
|
|
|
/// Log an error to the developer console (`console.error(...)` in JS)
|
2020-11-17 23:43:58 +00:00
|
|
|
pub fn console_error(s: impl Into<JsValue>) {
|
|
|
|
web_sys::console::error_1(&s.into());
|
|
|
|
}
|
|
|
|
|
2020-11-20 19:35:16 +00:00
|
|
|
/// Current time in seconds (since undefined point in time)
|
2019-02-11 19:27:32 +00:00
|
|
|
pub fn now_sec() -> f64 {
|
|
|
|
web_sys::window()
|
|
|
|
.expect("should have a Window")
|
|
|
|
.performance()
|
|
|
|
.expect("should have a Performance")
|
|
|
|
.now()
|
|
|
|
/ 1000.0
|
|
|
|
}
|
|
|
|
|
2020-07-18 08:54:31 +00:00
|
|
|
pub fn seconds_since_midnight() -> f64 {
|
|
|
|
let d = js_sys::Date::new_0();
|
|
|
|
let seconds = (d.get_hours() * 60 + d.get_minutes()) * 60 + d.get_seconds();
|
2020-07-30 10:30:20 +00:00
|
|
|
seconds as f64 + 1e-3 * (d.get_milliseconds() as f64)
|
2020-07-18 08:54:31 +00:00
|
|
|
}
|
|
|
|
|
2020-10-17 21:54:46 +00:00
|
|
|
pub fn screen_size_in_native_points() -> Option<egui::Vec2> {
|
|
|
|
let window = web_sys::window()?;
|
|
|
|
Some(egui::Vec2::new(
|
|
|
|
window.inner_width().ok()?.as_f64()? as f32,
|
|
|
|
window.inner_height().ok()?.as_f64()? as f32,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn native_pixels_per_point() -> f32 {
|
2020-07-18 08:54:31 +00:00
|
|
|
let pixels_per_point = web_sys::window().unwrap().device_pixel_ratio() as f32;
|
|
|
|
if pixels_per_point > 0.0 && pixels_per_point.is_finite() {
|
|
|
|
pixels_per_point
|
|
|
|
} else {
|
|
|
|
1.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-18 16:00:05 +00:00
|
|
|
pub fn canvas_element(canvas_id: &str) -> Option<web_sys::HtmlCanvasElement> {
|
2020-07-18 08:54:31 +00:00
|
|
|
use wasm_bindgen::JsCast;
|
|
|
|
let document = web_sys::window()?.document()?;
|
|
|
|
let canvas = document.get_element_by_id(canvas_id)?;
|
2020-07-18 16:00:05 +00:00
|
|
|
canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn canvas_element_or_die(canvas_id: &str) -> web_sys::HtmlCanvasElement {
|
|
|
|
crate::canvas_element(canvas_id)
|
|
|
|
.unwrap_or_else(|| panic!("Failed to find canvas with id '{}'", canvas_id))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn pos_from_mouse_event(canvas_id: &str, event: &web_sys::MouseEvent) -> egui::Pos2 {
|
|
|
|
let canvas = canvas_element(canvas_id).unwrap();
|
|
|
|
let rect = canvas.get_bounding_client_rect();
|
|
|
|
egui::Pos2 {
|
|
|
|
x: event.client_x() as f32 - rect.left() as f32,
|
|
|
|
y: event.client_y() as f32 - rect.top() as f32,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-25 17:50:19 +00:00
|
|
|
pub fn button_from_mouse_event(event: &web_sys::MouseEvent) -> Option<egui::PointerButton> {
|
|
|
|
match event.button() {
|
|
|
|
0 => Some(egui::PointerButton::Primary),
|
|
|
|
1 => Some(egui::PointerButton::Middle),
|
|
|
|
2 => Some(egui::PointerButton::Secondary),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-18 16:00:05 +00:00
|
|
|
pub fn pos_from_touch_event(event: &web_sys::TouchEvent) -> egui::Pos2 {
|
|
|
|
let t = event.touches().get(0).unwrap();
|
|
|
|
egui::Pos2 {
|
|
|
|
x: t.page_x() as f32,
|
|
|
|
y: t.page_y() as f32,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-18 21:51:23 +00:00
|
|
|
pub fn canvas_size_in_points(canvas_id: &str) -> egui::Vec2 {
|
|
|
|
let canvas = canvas_element(canvas_id).unwrap();
|
|
|
|
let pixels_per_point = native_pixels_per_point();
|
|
|
|
egui::vec2(
|
|
|
|
canvas.width() as f32 / pixels_per_point,
|
|
|
|
canvas.height() as f32 / pixels_per_point,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-02-12 16:57:53 +00:00
|
|
|
pub fn resize_canvas_to_screen_size(canvas_id: &str, max_size_points: egui::Vec2) -> Option<()> {
|
2020-07-18 16:00:05 +00:00
|
|
|
let canvas = canvas_element(canvas_id)?;
|
2020-07-18 08:54:31 +00:00
|
|
|
|
2020-12-18 21:51:23 +00:00
|
|
|
let screen_size_points = screen_size_in_native_points()?;
|
2020-10-17 21:54:46 +00:00
|
|
|
let pixels_per_point = native_pixels_per_point();
|
2020-12-18 21:51:23 +00:00
|
|
|
|
2021-02-12 16:57:53 +00:00
|
|
|
let max_size_pixels = pixels_per_point * max_size_points;
|
|
|
|
|
2020-12-18 21:51:23 +00:00
|
|
|
let canvas_size_pixels = pixels_per_point * screen_size_points;
|
|
|
|
let canvas_size_pixels = canvas_size_pixels.min(max_size_pixels);
|
|
|
|
let canvas_size_points = canvas_size_pixels / pixels_per_point;
|
|
|
|
|
2021-02-01 19:44:39 +00:00
|
|
|
// Make sure that the height and width are always even numbers.
|
|
|
|
// otherwise, the page renders blurry on some platforms.
|
|
|
|
// See https://github.com/emilk/egui/issues/103
|
|
|
|
fn round_to_even(v: f32) -> f32 {
|
|
|
|
(v / 2.0).round() * 2.0
|
|
|
|
}
|
|
|
|
|
2020-07-18 08:54:31 +00:00
|
|
|
canvas
|
|
|
|
.style()
|
2021-02-01 19:44:39 +00:00
|
|
|
.set_property(
|
|
|
|
"width",
|
|
|
|
&format!("{}px", round_to_even(canvas_size_points.x)),
|
|
|
|
)
|
2020-07-18 08:54:31 +00:00
|
|
|
.ok()?;
|
|
|
|
canvas
|
|
|
|
.style()
|
2021-02-01 19:44:39 +00:00
|
|
|
.set_property(
|
|
|
|
"height",
|
|
|
|
&format!("{}px", round_to_even(canvas_size_points.y)),
|
|
|
|
)
|
2020-07-18 08:54:31 +00:00
|
|
|
.ok()?;
|
2021-02-01 19:44:39 +00:00
|
|
|
canvas.set_width(round_to_even(canvas_size_pixels.x) as u32);
|
|
|
|
canvas.set_height(round_to_even(canvas_size_pixels.y) as u32);
|
2020-07-18 08:54:31 +00:00
|
|
|
|
|
|
|
Some(())
|
|
|
|
}
|
|
|
|
|
2020-12-18 21:51:23 +00:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2019-02-11 19:27:32 +00:00
|
|
|
pub fn local_storage() -> Option<web_sys::Storage> {
|
|
|
|
web_sys::window()?.local_storage().ok()?
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn local_storage_get(key: &str) -> Option<String> {
|
|
|
|
local_storage().map(|storage| storage.get_item(key).ok())??
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn local_storage_set(key: &str, value: &str) {
|
|
|
|
local_storage().map(|storage| storage.set_item(key, value));
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn local_storage_remove(key: &str) {
|
|
|
|
local_storage().map(|storage| storage.remove_item(key));
|
|
|
|
}
|
2020-05-02 09:37:12 +00:00
|
|
|
|
2021-01-04 00:44:02 +00:00
|
|
|
#[cfg(feature = "persistence")]
|
2020-05-30 08:22:35 +00:00
|
|
|
pub fn load_memory(ctx: &egui::Context) {
|
|
|
|
if let Some(memory_string) = local_storage_get("egui_memory_json") {
|
2020-05-02 09:37:12 +00:00
|
|
|
match serde_json::from_str(&memory_string) {
|
|
|
|
Ok(memory) => {
|
|
|
|
*ctx.memory() = memory;
|
|
|
|
}
|
|
|
|
Err(err) => {
|
2020-12-19 19:50:00 +00:00
|
|
|
console_error(format!("Failed to parse memory json: {}", err));
|
2020-05-02 09:37:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-04 00:44:02 +00:00
|
|
|
#[cfg(not(feature = "persistence"))]
|
|
|
|
pub fn load_memory(_: &egui::Context) {}
|
|
|
|
|
|
|
|
#[cfg(feature = "persistence")]
|
2020-05-30 08:22:35 +00:00
|
|
|
pub fn save_memory(ctx: &egui::Context) {
|
2020-05-02 09:37:12 +00:00
|
|
|
match serde_json::to_string(&*ctx.memory()) {
|
|
|
|
Ok(json) => {
|
2020-05-30 08:22:35 +00:00
|
|
|
local_storage_set("egui_memory_json", &json);
|
2020-05-02 09:37:12 +00:00
|
|
|
}
|
|
|
|
Err(err) => {
|
2020-12-19 19:50:00 +00:00
|
|
|
console_error(format!("Failed to serialize memory as json: {}", err));
|
2020-05-02 09:37:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-07-18 08:54:31 +00:00
|
|
|
|
2021-01-04 00:44:02 +00:00
|
|
|
#[cfg(not(feature = "persistence"))]
|
|
|
|
pub fn save_memory(_: &egui::Context) {}
|
|
|
|
|
2020-12-19 19:50:00 +00:00
|
|
|
#[derive(Default)]
|
2020-12-19 13:58:00 +00:00
|
|
|
pub struct LocalStorage {}
|
|
|
|
|
2020-12-29 13:15:46 +00:00
|
|
|
impl epi::Storage for LocalStorage {
|
2020-12-19 13:58:00 +00:00
|
|
|
fn get_string(&self, key: &str) -> Option<String> {
|
|
|
|
local_storage_get(key)
|
|
|
|
}
|
|
|
|
fn set_string(&mut self, key: &str, value: String) {
|
|
|
|
local_storage_set(key, &value);
|
|
|
|
}
|
|
|
|
fn flush(&mut self) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2020-07-18 08:54:31 +00:00
|
|
|
pub fn handle_output(output: &egui::Output) {
|
2020-11-15 19:55:41 +00:00
|
|
|
let egui::Output {
|
|
|
|
cursor_icon,
|
|
|
|
open_url,
|
|
|
|
copied_text,
|
|
|
|
needs_repaint: _, // handled elsewhere
|
|
|
|
} = output;
|
|
|
|
|
|
|
|
set_cursor_icon(*cursor_icon);
|
|
|
|
if let Some(url) = open_url {
|
|
|
|
crate::open_url(url);
|
|
|
|
}
|
|
|
|
|
2020-12-29 11:42:15 +00:00
|
|
|
#[cfg(web_sys_unstable_apis)]
|
2020-11-15 19:55:41 +00:00
|
|
|
if !copied_text.is_empty() {
|
|
|
|
set_clipboard_text(copied_text);
|
2020-07-18 08:54:31 +00:00
|
|
|
}
|
2020-12-29 11:42:15 +00:00
|
|
|
|
|
|
|
#[cfg(not(web_sys_unstable_apis))]
|
|
|
|
let _ = copied_text;
|
2020-07-18 08:54:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_cursor_icon(cursor: egui::CursorIcon) -> Option<()> {
|
|
|
|
let document = web_sys::window()?.document()?;
|
|
|
|
document
|
|
|
|
.body()?
|
|
|
|
.style()
|
|
|
|
.set_property("cursor", cursor_web_name(cursor))
|
|
|
|
.ok()
|
|
|
|
}
|
|
|
|
|
2020-12-29 11:42:15 +00:00
|
|
|
#[cfg(web_sys_unstable_apis)]
|
2020-11-15 19:55:41 +00:00
|
|
|
pub fn set_clipboard_text(s: &str) {
|
|
|
|
if let Some(window) = web_sys::window() {
|
|
|
|
let clipboard = window.navigator().clipboard();
|
|
|
|
let promise = clipboard.write_text(s);
|
|
|
|
let future = wasm_bindgen_futures::JsFuture::from(promise);
|
|
|
|
let future = async move {
|
|
|
|
if let Err(err) = future.await {
|
2020-12-19 19:50:00 +00:00
|
|
|
console_error(format!("Copy/cut action denied: {:?}", err));
|
2020-11-15 19:55:41 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
wasm_bindgen_futures::spawn_local(future);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-17 23:43:58 +00:00
|
|
|
pub fn spawn_future<F>(future: F)
|
|
|
|
where
|
|
|
|
F: std::future::Future<Output = ()> + 'static,
|
|
|
|
{
|
|
|
|
wasm_bindgen_futures::spawn_local(future);
|
|
|
|
}
|
|
|
|
|
2020-07-18 08:54:31 +00:00
|
|
|
fn cursor_web_name(cursor: egui::CursorIcon) -> &'static str {
|
|
|
|
use egui::CursorIcon::*;
|
|
|
|
match cursor {
|
|
|
|
Default => "default",
|
|
|
|
PointingHand => "pointer",
|
|
|
|
ResizeHorizontal => "ew-resize",
|
|
|
|
ResizeNeSw => "nesw-resize",
|
|
|
|
ResizeNwSe => "nwse-resize",
|
|
|
|
ResizeVertical => "ns-resize",
|
|
|
|
Text => "text",
|
2020-11-02 16:40:05 +00:00
|
|
|
Grab => "grab",
|
|
|
|
Grabbing => "grabbing",
|
2020-07-18 08:54:31 +00:00
|
|
|
// "no-drop"
|
|
|
|
// "not-allowed"
|
2020-11-02 16:40:05 +00:00
|
|
|
// default, help, pointer, progress, wait, cell, crosshair, text, alias, copy, move
|
2020-07-18 08:54:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn open_url(url: &str) -> Option<()> {
|
|
|
|
web_sys::window()?
|
|
|
|
.open_with_url_and_target(url, "_self")
|
|
|
|
.ok()?;
|
|
|
|
Some(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// e.g. "#fragment" part of "www.example.com/index.html#fragment"
|
|
|
|
pub fn location_hash() -> Option<String> {
|
|
|
|
web_sys::window()?.location().hash().ok()
|
|
|
|
}
|
2020-07-18 16:00:05 +00:00
|
|
|
|
2020-11-14 20:01:21 +00:00
|
|
|
/// Web sends all keys as strings, so it is up to us to figure out if it is
|
2020-07-30 09:54:42 +00:00
|
|
|
/// a real text input or the name of a key.
|
|
|
|
fn should_ignore_key(key: &str) -> bool {
|
2020-07-30 10:30:20 +00:00
|
|
|
let is_function_key = key.starts_with('F') && key.len() > 1;
|
2020-07-30 09:54:42 +00:00
|
|
|
is_function_key
|
|
|
|
|| matches!(
|
|
|
|
key,
|
2020-11-14 20:01:21 +00:00
|
|
|
"Alt"
|
2020-11-15 13:21:21 +00:00
|
|
|
| "ArrowDown"
|
|
|
|
| "ArrowLeft"
|
|
|
|
| "ArrowRight"
|
|
|
|
| "ArrowUp"
|
|
|
|
| "Backspace"
|
2020-11-14 20:01:21 +00:00
|
|
|
| "CapsLock"
|
|
|
|
| "ContextMenu"
|
|
|
|
| "Control"
|
2020-11-15 13:21:21 +00:00
|
|
|
| "Delete"
|
|
|
|
| "End"
|
|
|
|
| "Enter"
|
|
|
|
| "Esc"
|
|
|
|
| "Escape"
|
|
|
|
| "Help"
|
|
|
|
| "Home"
|
|
|
|
| "Insert"
|
2020-11-14 20:01:21 +00:00
|
|
|
| "Meta"
|
|
|
|
| "NumLock"
|
2020-11-15 13:21:21 +00:00
|
|
|
| "PageDown"
|
|
|
|
| "PageUp"
|
2020-11-14 20:01:21 +00:00
|
|
|
| "Pause"
|
|
|
|
| "ScrollLock"
|
|
|
|
| "Shift"
|
2020-11-15 13:21:21 +00:00
|
|
|
| "Tab"
|
2020-07-30 09:54:42 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Web sends all all keys as strings, so it is up to us to figure out if it is
|
|
|
|
/// a real text input or the name of a key.
|
2020-07-18 16:00:05 +00:00
|
|
|
pub fn translate_key(key: &str) -> Option<egui::Key> {
|
|
|
|
match key {
|
2020-11-14 20:01:21 +00:00
|
|
|
"ArrowDown" => Some(egui::Key::ArrowDown),
|
|
|
|
"ArrowLeft" => Some(egui::Key::ArrowLeft),
|
|
|
|
"ArrowRight" => Some(egui::Key::ArrowRight),
|
|
|
|
"ArrowUp" => Some(egui::Key::ArrowUp),
|
2021-01-03 10:25:13 +00:00
|
|
|
|
|
|
|
"Esc" | "Escape" => Some(egui::Key::Escape),
|
|
|
|
"Tab" => Some(egui::Key::Tab),
|
2020-07-18 16:00:05 +00:00
|
|
|
"Backspace" => Some(egui::Key::Backspace),
|
2020-11-14 20:01:21 +00:00
|
|
|
"Enter" => Some(egui::Key::Enter),
|
2020-12-13 09:00:20 +00:00
|
|
|
"Space" => Some(egui::Key::Space),
|
2021-01-03 10:25:13 +00:00
|
|
|
|
2020-07-30 09:54:42 +00:00
|
|
|
"Help" | "Insert" => Some(egui::Key::Insert),
|
2021-01-03 10:25:13 +00:00
|
|
|
"Delete" => Some(egui::Key::Delete),
|
2020-11-14 20:01:21 +00:00
|
|
|
"Home" => Some(egui::Key::Home),
|
2021-01-03 10:25:13 +00:00
|
|
|
"End" => Some(egui::Key::End),
|
2020-07-18 16:00:05 +00:00
|
|
|
"PageUp" => Some(egui::Key::PageUp),
|
2021-01-03 10:25:13 +00:00
|
|
|
"PageDown" => Some(egui::Key::PageDown),
|
|
|
|
|
|
|
|
"0" => Some(egui::Key::Num0),
|
|
|
|
"1" => Some(egui::Key::Num1),
|
|
|
|
"2" => Some(egui::Key::Num2),
|
|
|
|
"3" => Some(egui::Key::Num3),
|
|
|
|
"4" => Some(egui::Key::Num4),
|
|
|
|
"5" => Some(egui::Key::Num5),
|
|
|
|
"6" => Some(egui::Key::Num6),
|
|
|
|
"7" => Some(egui::Key::Num7),
|
|
|
|
"8" => Some(egui::Key::Num8),
|
|
|
|
"9" => Some(egui::Key::Num9),
|
|
|
|
|
2020-11-15 13:21:21 +00:00
|
|
|
"a" | "A" => Some(egui::Key::A),
|
2021-01-03 10:25:13 +00:00
|
|
|
"b" | "B" => Some(egui::Key::B),
|
|
|
|
"c" | "C" => Some(egui::Key::C),
|
|
|
|
"d" | "D" => Some(egui::Key::D),
|
|
|
|
"e" | "E" => Some(egui::Key::E),
|
|
|
|
"f" | "F" => Some(egui::Key::F),
|
|
|
|
"g" | "G" => Some(egui::Key::G),
|
|
|
|
"h" | "H" => Some(egui::Key::H),
|
|
|
|
"i" | "I" => Some(egui::Key::I),
|
|
|
|
"j" | "J" => Some(egui::Key::J),
|
2020-11-15 13:21:21 +00:00
|
|
|
"k" | "K" => Some(egui::Key::K),
|
2021-01-03 10:25:13 +00:00
|
|
|
"l" | "L" => Some(egui::Key::L),
|
|
|
|
"m" | "M" => Some(egui::Key::M),
|
|
|
|
"n" | "N" => Some(egui::Key::N),
|
|
|
|
"o" | "O" => Some(egui::Key::O),
|
|
|
|
"p" | "P" => Some(egui::Key::P),
|
|
|
|
"q" | "Q" => Some(egui::Key::Q),
|
|
|
|
"r" | "R" => Some(egui::Key::R),
|
|
|
|
"s" | "S" => Some(egui::Key::S),
|
|
|
|
"t" | "T" => Some(egui::Key::T),
|
2020-11-15 13:21:21 +00:00
|
|
|
"u" | "U" => Some(egui::Key::U),
|
2021-01-03 10:25:13 +00:00
|
|
|
"v" | "V" => Some(egui::Key::V),
|
2020-11-15 13:21:21 +00:00
|
|
|
"w" | "W" => Some(egui::Key::W),
|
2021-01-03 10:25:13 +00:00
|
|
|
"x" | "X" => Some(egui::Key::X),
|
|
|
|
"y" | "Y" => Some(egui::Key::Y),
|
2020-11-15 13:21:21 +00:00
|
|
|
"z" | "Z" => Some(egui::Key::Z),
|
2021-01-03 10:25:13 +00:00
|
|
|
|
2020-07-18 16:00:05 +00:00
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
2020-07-18 16:35:17 +00:00
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct AppRunnerRef(Arc<Mutex<AppRunner>>);
|
|
|
|
|
2020-07-18 17:40:24 +00:00
|
|
|
fn paint_and_schedule(runner_ref: AppRunnerRef) -> Result<(), JsValue> {
|
2020-07-18 22:44:06 +00:00
|
|
|
fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
|
2020-07-18 17:40:24 +00:00
|
|
|
let mut runner_lock = runner_ref.0.lock();
|
2020-11-20 19:35:16 +00:00
|
|
|
if runner_lock.needs_repaint.fetch_and_clear() {
|
2021-01-25 20:43:17 +00:00
|
|
|
let (output, clipped_meshes) = runner_lock.logic()?;
|
|
|
|
runner_lock.paint(clipped_meshes)?;
|
2020-11-20 19:35:16 +00:00
|
|
|
if output.needs_repaint {
|
|
|
|
runner_lock.needs_repaint.set_true();
|
|
|
|
}
|
2020-12-19 19:50:00 +00:00
|
|
|
runner_lock.auto_save();
|
2020-07-18 21:56:37 +00:00
|
|
|
}
|
2020-12-19 19:50:00 +00:00
|
|
|
|
2020-07-18 21:56:37 +00:00
|
|
|
Ok(())
|
2020-07-18 16:35:17 +00:00
|
|
|
}
|
2020-07-18 17:40:24 +00:00
|
|
|
|
2020-07-18 21:56:37 +00:00
|
|
|
fn request_animation_frame(runner_ref: AppRunnerRef) -> Result<(), JsValue> {
|
2020-07-18 17:40:24 +00:00
|
|
|
use wasm_bindgen::JsCast;
|
|
|
|
let window = web_sys::window().unwrap();
|
|
|
|
let closure = Closure::once(move || paint_and_schedule(runner_ref));
|
|
|
|
window.request_animation_frame(closure.as_ref().unchecked_ref())?;
|
|
|
|
closure.forget(); // We must forget it, or else the callback is canceled on drop
|
2020-07-18 21:56:37 +00:00
|
|
|
Ok(())
|
2020-07-18 17:40:24 +00:00
|
|
|
}
|
2020-07-18 16:35:17 +00:00
|
|
|
|
2020-07-18 22:44:06 +00:00
|
|
|
paint_if_needed(&runner_ref)?;
|
2020-07-18 17:40:24 +00:00
|
|
|
request_animation_frame(runner_ref)
|
2020-07-18 16:35:17 +00:00
|
|
|
}
|
|
|
|
|
2020-07-18 17:40:24 +00:00
|
|
|
fn install_document_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
|
2020-07-18 16:35:17 +00:00
|
|
|
use wasm_bindgen::JsCast;
|
2020-07-20 13:08:27 +00:00
|
|
|
let window = web_sys::window().unwrap();
|
|
|
|
let document = window.document().unwrap();
|
2020-07-18 16:35:17 +00:00
|
|
|
|
|
|
|
{
|
|
|
|
// keydown
|
2020-07-18 17:40:24 +00:00
|
|
|
let runner_ref = runner_ref.clone();
|
2020-07-18 16:35:17 +00:00
|
|
|
let closure = Closure::wrap(Box::new(move |event: web_sys::KeyboardEvent| {
|
2020-07-30 09:54:42 +00:00
|
|
|
if event.is_composing() || event.key_code() == 229 {
|
|
|
|
// https://www.fxsitecompat.dev/en-CA/docs/2018/keydown-and-keyup-events-are-now-fired-during-ime-composition/
|
|
|
|
return;
|
|
|
|
}
|
2020-11-15 13:21:21 +00:00
|
|
|
|
2020-07-18 17:40:24 +00:00
|
|
|
let mut runner_lock = runner_ref.0.lock();
|
2020-11-15 13:21:21 +00:00
|
|
|
let modifiers = modifiers_from_event(&event);
|
|
|
|
runner_lock.input.raw.modifiers = modifiers;
|
|
|
|
|
2020-07-18 16:35:17 +00:00
|
|
|
let key = event.key();
|
2020-11-14 20:01:21 +00:00
|
|
|
|
|
|
|
if let Some(key) = translate_key(&key) {
|
2020-11-15 13:21:21 +00:00
|
|
|
runner_lock.input.raw.events.push(egui::Event::Key {
|
2020-11-14 20:01:21 +00:00
|
|
|
key,
|
|
|
|
pressed: true,
|
2020-11-15 13:21:21 +00:00
|
|
|
modifiers,
|
2020-11-14 20:01:21 +00:00
|
|
|
});
|
2020-07-18 16:35:17 +00:00
|
|
|
}
|
2020-11-15 13:21:21 +00:00
|
|
|
if !modifiers.ctrl && !modifiers.command && !should_ignore_key(&key) {
|
|
|
|
runner_lock.input.raw.events.push(egui::Event::Text(key));
|
|
|
|
}
|
2020-11-20 19:35:16 +00:00
|
|
|
runner_lock.needs_repaint.set_true();
|
2020-11-18 18:16:13 +00:00
|
|
|
|
2021-01-17 11:22:19 +00:00
|
|
|
let egui_wants_keyboard = runner_lock.egui_ctx().wants_keyboard_input();
|
|
|
|
|
|
|
|
let prevent_default = if matches!(event.key().as_str(), "Tab") {
|
|
|
|
// Always prevent moving cursor to url bar.
|
2021-01-17 13:48:59 +00:00
|
|
|
// egui wants to use tab to move to the next text field.
|
2021-01-17 11:22:19 +00:00
|
|
|
true
|
|
|
|
} else if egui_wants_keyboard {
|
|
|
|
matches!(
|
|
|
|
event.key().as_str(),
|
|
|
|
"Backspace" // so we don't go back to previous page when deleting text
|
|
|
|
| "ArrowDown" | "ArrowLeft" | "ArrowRight" | "ArrowUp" // cmd-left is "back" on Mac (https://github.com/emilk/egui/issues/58)
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
// We never want to prevent:
|
|
|
|
// * F5 / cmd-R (refresh)
|
|
|
|
// * cmd-shift-C (debug tools)
|
|
|
|
// * cmd/ctrl-c/v/x (or we stop copy/past/cut events)
|
|
|
|
false
|
|
|
|
};
|
|
|
|
|
|
|
|
// console_log(format!(
|
|
|
|
// "On key-down {:?}, egui_wants_keyboard: {}, prevent_default: {}",
|
|
|
|
// event.key().as_str(),
|
|
|
|
// egui_wants_keyboard,
|
|
|
|
// prevent_default
|
|
|
|
// ));
|
|
|
|
|
|
|
|
if prevent_default {
|
2020-11-18 18:16:13 +00:00
|
|
|
event.prevent_default();
|
|
|
|
}
|
2020-07-18 16:35:17 +00:00
|
|
|
}) as Box<dyn FnMut(_)>);
|
|
|
|
document.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref())?;
|
|
|
|
closure.forget();
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
// keyup
|
2020-07-18 17:40:24 +00:00
|
|
|
let runner_ref = runner_ref.clone();
|
2020-07-18 16:35:17 +00:00
|
|
|
let closure = Closure::wrap(Box::new(move |event: web_sys::KeyboardEvent| {
|
2020-07-18 17:40:24 +00:00
|
|
|
let mut runner_lock = runner_ref.0.lock();
|
2020-11-15 13:21:21 +00:00
|
|
|
let modifiers = modifiers_from_event(&event);
|
|
|
|
runner_lock.input.raw.modifiers = modifiers;
|
|
|
|
if let Some(key) = translate_key(&event.key()) {
|
|
|
|
runner_lock.input.raw.events.push(egui::Event::Key {
|
2020-07-18 16:35:17 +00:00
|
|
|
key,
|
|
|
|
pressed: false,
|
2020-11-15 13:21:21 +00:00
|
|
|
modifiers,
|
2020-07-18 16:35:17 +00:00
|
|
|
});
|
|
|
|
}
|
2020-11-20 19:35:16 +00:00
|
|
|
runner_lock.needs_repaint.set_true();
|
2020-07-18 16:35:17 +00:00
|
|
|
}) as Box<dyn FnMut(_)>);
|
|
|
|
document.add_event_listener_with_callback("keyup", closure.as_ref().unchecked_ref())?;
|
|
|
|
closure.forget();
|
|
|
|
}
|
|
|
|
|
2020-12-29 11:42:15 +00:00
|
|
|
#[cfg(web_sys_unstable_apis)]
|
2020-11-15 19:55:41 +00:00
|
|
|
{
|
|
|
|
// paste
|
|
|
|
let runner_ref = runner_ref.clone();
|
|
|
|
let closure = Closure::wrap(Box::new(move |event: web_sys::ClipboardEvent| {
|
|
|
|
if let Some(data) = event.clipboard_data() {
|
|
|
|
if let Ok(text) = data.get_data("text") {
|
|
|
|
let mut runner_lock = runner_ref.0.lock();
|
|
|
|
runner_lock.input.raw.events.push(egui::Event::Text(text));
|
2020-11-20 19:35:16 +00:00
|
|
|
runner_lock.needs_repaint.set_true();
|
2020-11-15 19:55:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}) as Box<dyn FnMut(_)>);
|
|
|
|
document.add_event_listener_with_callback("paste", closure.as_ref().unchecked_ref())?;
|
|
|
|
closure.forget();
|
|
|
|
}
|
|
|
|
|
2020-12-29 11:42:15 +00:00
|
|
|
#[cfg(web_sys_unstable_apis)]
|
2020-11-15 19:55:41 +00:00
|
|
|
{
|
|
|
|
// cut
|
|
|
|
let runner_ref = runner_ref.clone();
|
|
|
|
let closure = Closure::wrap(Box::new(move |_: web_sys::ClipboardEvent| {
|
|
|
|
let mut runner_lock = runner_ref.0.lock();
|
|
|
|
runner_lock.input.raw.events.push(egui::Event::Cut);
|
2020-11-20 19:35:16 +00:00
|
|
|
runner_lock.needs_repaint.set_true();
|
2020-11-15 19:55:41 +00:00
|
|
|
}) as Box<dyn FnMut(_)>);
|
|
|
|
document.add_event_listener_with_callback("cut", closure.as_ref().unchecked_ref())?;
|
|
|
|
closure.forget();
|
|
|
|
}
|
|
|
|
|
2020-12-29 11:42:15 +00:00
|
|
|
#[cfg(web_sys_unstable_apis)]
|
2020-11-15 19:55:41 +00:00
|
|
|
{
|
|
|
|
// copy
|
|
|
|
let runner_ref = runner_ref.clone();
|
|
|
|
let closure = Closure::wrap(Box::new(move |_: web_sys::ClipboardEvent| {
|
|
|
|
let mut runner_lock = runner_ref.0.lock();
|
|
|
|
runner_lock.input.raw.events.push(egui::Event::Copy);
|
2020-11-20 19:35:16 +00:00
|
|
|
runner_lock.needs_repaint.set_true();
|
2020-11-15 19:55:41 +00:00
|
|
|
}) as Box<dyn FnMut(_)>);
|
|
|
|
document.add_event_listener_with_callback("copy", closure.as_ref().unchecked_ref())?;
|
|
|
|
closure.forget();
|
|
|
|
}
|
|
|
|
|
2020-07-18 16:35:17 +00:00
|
|
|
for event_name in &["load", "pagehide", "pageshow", "resize"] {
|
2020-07-18 17:40:24 +00:00
|
|
|
let runner_ref = runner_ref.clone();
|
2020-07-18 16:35:17 +00:00
|
|
|
let closure = Closure::wrap(Box::new(move || {
|
2020-11-20 19:35:16 +00:00
|
|
|
runner_ref.0.lock().needs_repaint.set_true();
|
2020-07-18 16:35:17 +00:00
|
|
|
}) as Box<dyn FnMut()>);
|
2020-07-20 13:08:27 +00:00
|
|
|
window.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
|
2020-07-18 16:35:17 +00:00
|
|
|
closure.forget();
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-11-18 00:07:32 +00:00
|
|
|
/// Repaint at least every `ms` milliseconds.
|
|
|
|
fn repaint_every_ms(runner_ref: &AppRunnerRef, milliseconds: i32) -> Result<(), JsValue> {
|
|
|
|
assert!(milliseconds >= 0);
|
|
|
|
use wasm_bindgen::JsCast;
|
|
|
|
let window = web_sys::window().unwrap();
|
|
|
|
let runner_ref = runner_ref.clone();
|
|
|
|
let closure = Closure::wrap(Box::new(move || {
|
2020-11-20 19:35:16 +00:00
|
|
|
runner_ref.0.lock().needs_repaint.set_true();
|
2020-11-18 00:07:32 +00:00
|
|
|
}) as Box<dyn FnMut()>);
|
|
|
|
window.set_interval_with_callback_and_timeout_and_arguments_0(
|
|
|
|
closure.as_ref().unchecked_ref(),
|
|
|
|
milliseconds,
|
|
|
|
)?;
|
|
|
|
closure.forget();
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-11-14 20:01:21 +00:00
|
|
|
fn modifiers_from_event(event: &web_sys::KeyboardEvent) -> egui::Modifiers {
|
|
|
|
egui::Modifiers {
|
|
|
|
alt: event.alt_key(),
|
|
|
|
ctrl: event.ctrl_key(),
|
|
|
|
shift: event.shift_key(),
|
|
|
|
|
|
|
|
// Ideally we should know if we are running or mac or not,
|
|
|
|
// but this works good enough for now.
|
|
|
|
mac_cmd: event.meta_key(),
|
|
|
|
|
|
|
|
// Ideally we should know if we are running or mac or not,
|
|
|
|
// but this works good enough for now.
|
|
|
|
command: event.ctrl_key() || event.meta_key(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-18 17:40:24 +00:00
|
|
|
fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
|
2020-07-18 16:35:17 +00:00
|
|
|
use wasm_bindgen::JsCast;
|
2020-07-18 17:40:24 +00:00
|
|
|
let canvas = canvas_element(runner_ref.0.lock().canvas_id()).unwrap();
|
2020-07-18 16:35:17 +00:00
|
|
|
|
2021-01-25 17:50:19 +00:00
|
|
|
{
|
|
|
|
// By default, right-clicks open a context menu.
|
|
|
|
// We don't want to do that (right clicks is handled by egui):
|
|
|
|
let event_name = "contextmenu";
|
|
|
|
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
|
|
|
|
event.prevent_default();
|
|
|
|
}) as Box<dyn FnMut(_)>);
|
|
|
|
canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
|
|
|
|
closure.forget();
|
|
|
|
}
|
|
|
|
|
2020-07-18 16:35:17 +00:00
|
|
|
{
|
|
|
|
let event_name = "mousedown";
|
2020-07-18 17:40:24 +00:00
|
|
|
let runner_ref = runner_ref.clone();
|
2020-07-18 16:35:17 +00:00
|
|
|
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
|
2020-07-18 17:40:24 +00:00
|
|
|
let mut runner_lock = runner_ref.0.lock();
|
2020-11-15 13:21:21 +00:00
|
|
|
if !runner_lock.input.is_touch {
|
2021-01-25 17:50:19 +00:00
|
|
|
if let Some(button) = button_from_mouse_event(&event) {
|
|
|
|
let pos = pos_from_mouse_event(runner_lock.canvas_id(), &event);
|
|
|
|
let modifiers = runner_lock.input.raw.modifiers;
|
|
|
|
runner_lock
|
|
|
|
.input
|
|
|
|
.raw
|
|
|
|
.events
|
|
|
|
.push(egui::Event::PointerButton {
|
|
|
|
pos,
|
|
|
|
button,
|
|
|
|
pressed: true,
|
|
|
|
modifiers,
|
|
|
|
});
|
|
|
|
runner_lock.needs_repaint.set_true();
|
|
|
|
event.stop_propagation();
|
|
|
|
event.prevent_default();
|
|
|
|
}
|
2020-07-18 16:35:17 +00:00
|
|
|
}
|
|
|
|
}) as Box<dyn FnMut(_)>);
|
|
|
|
canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
|
|
|
|
closure.forget();
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
let event_name = "mousemove";
|
2020-07-18 17:40:24 +00:00
|
|
|
let runner_ref = runner_ref.clone();
|
2020-07-18 16:35:17 +00:00
|
|
|
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
|
2020-07-18 17:40:24 +00:00
|
|
|
let mut runner_lock = runner_ref.0.lock();
|
2020-11-15 13:21:21 +00:00
|
|
|
if !runner_lock.input.is_touch {
|
2021-01-25 17:50:19 +00:00
|
|
|
let pos = pos_from_mouse_event(runner_lock.canvas_id(), &event);
|
|
|
|
runner_lock
|
|
|
|
.input
|
|
|
|
.raw
|
|
|
|
.events
|
|
|
|
.push(egui::Event::PointerMoved(pos));
|
2020-11-20 19:35:16 +00:00
|
|
|
runner_lock.needs_repaint.set_true();
|
2020-07-18 16:35:17 +00:00
|
|
|
event.stop_propagation();
|
|
|
|
event.prevent_default();
|
|
|
|
}
|
|
|
|
}) as Box<dyn FnMut(_)>);
|
|
|
|
canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
|
|
|
|
closure.forget();
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
let event_name = "mouseup";
|
2020-07-18 17:40:24 +00:00
|
|
|
let runner_ref = runner_ref.clone();
|
2020-07-18 16:35:17 +00:00
|
|
|
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
|
2020-07-18 17:40:24 +00:00
|
|
|
let mut runner_lock = runner_ref.0.lock();
|
2020-11-15 13:21:21 +00:00
|
|
|
if !runner_lock.input.is_touch {
|
2021-01-25 17:50:19 +00:00
|
|
|
if let Some(button) = button_from_mouse_event(&event) {
|
|
|
|
let pos = pos_from_mouse_event(runner_lock.canvas_id(), &event);
|
|
|
|
let modifiers = runner_lock.input.raw.modifiers;
|
|
|
|
runner_lock
|
|
|
|
.input
|
|
|
|
.raw
|
|
|
|
.events
|
|
|
|
.push(egui::Event::PointerButton {
|
|
|
|
pos,
|
|
|
|
button,
|
|
|
|
pressed: false,
|
|
|
|
modifiers,
|
|
|
|
});
|
|
|
|
runner_lock.needs_repaint.set_true();
|
|
|
|
event.stop_propagation();
|
|
|
|
event.prevent_default();
|
|
|
|
}
|
2020-07-18 16:35:17 +00:00
|
|
|
}
|
|
|
|
}) as Box<dyn FnMut(_)>);
|
|
|
|
canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
|
|
|
|
closure.forget();
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
let event_name = "mouseleave";
|
2020-07-18 17:40:24 +00:00
|
|
|
let runner_ref = runner_ref.clone();
|
2020-07-18 16:35:17 +00:00
|
|
|
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
|
2020-07-18 17:40:24 +00:00
|
|
|
let mut runner_lock = runner_ref.0.lock();
|
2020-11-15 13:21:21 +00:00
|
|
|
if !runner_lock.input.is_touch {
|
2021-01-25 17:50:19 +00:00
|
|
|
runner_lock.input.raw.events.push(egui::Event::PointerGone);
|
2020-11-20 19:35:16 +00:00
|
|
|
runner_lock.needs_repaint.set_true();
|
2020-07-18 16:35:17 +00:00
|
|
|
event.stop_propagation();
|
|
|
|
event.prevent_default();
|
|
|
|
}
|
|
|
|
}) as Box<dyn FnMut(_)>);
|
|
|
|
canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
|
|
|
|
closure.forget();
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
let event_name = "touchstart";
|
2020-07-18 17:40:24 +00:00
|
|
|
let runner_ref = runner_ref.clone();
|
2020-07-18 16:35:17 +00:00
|
|
|
let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| {
|
2021-01-25 17:50:19 +00:00
|
|
|
let pos = pos_from_touch_event(&event);
|
2020-07-18 17:40:24 +00:00
|
|
|
let mut runner_lock = runner_ref.0.lock();
|
2021-01-25 17:50:19 +00:00
|
|
|
runner_lock.input.latest_touch_pos = Some(pos);
|
2020-11-15 13:21:21 +00:00
|
|
|
runner_lock.input.is_touch = true;
|
2021-01-25 17:50:19 +00:00
|
|
|
let modifiers = runner_lock.input.raw.modifiers;
|
|
|
|
runner_lock
|
|
|
|
.input
|
|
|
|
.raw
|
|
|
|
.events
|
|
|
|
.push(egui::Event::PointerButton {
|
|
|
|
pos,
|
|
|
|
button: egui::PointerButton::Primary,
|
|
|
|
pressed: true,
|
|
|
|
modifiers,
|
|
|
|
});
|
2020-11-20 19:35:16 +00:00
|
|
|
runner_lock.needs_repaint.set_true();
|
2020-07-18 16:35:17 +00:00
|
|
|
event.stop_propagation();
|
|
|
|
event.prevent_default();
|
|
|
|
}) as Box<dyn FnMut(_)>);
|
|
|
|
canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
|
|
|
|
closure.forget();
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
let event_name = "touchmove";
|
2020-07-18 17:40:24 +00:00
|
|
|
let runner_ref = runner_ref.clone();
|
2020-07-18 16:35:17 +00:00
|
|
|
let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| {
|
2021-01-25 17:50:19 +00:00
|
|
|
let pos = pos_from_touch_event(&event);
|
2020-07-18 17:40:24 +00:00
|
|
|
let mut runner_lock = runner_ref.0.lock();
|
2021-01-25 17:50:19 +00:00
|
|
|
runner_lock.input.latest_touch_pos = Some(pos);
|
2020-11-15 13:21:21 +00:00
|
|
|
runner_lock.input.is_touch = true;
|
2021-01-25 17:50:19 +00:00
|
|
|
runner_lock
|
|
|
|
.input
|
|
|
|
.raw
|
|
|
|
.events
|
|
|
|
.push(egui::Event::PointerMoved(pos));
|
2020-11-20 19:35:16 +00:00
|
|
|
runner_lock.needs_repaint.set_true();
|
2020-07-18 16:35:17 +00:00
|
|
|
event.stop_propagation();
|
|
|
|
event.prevent_default();
|
|
|
|
}) as Box<dyn FnMut(_)>);
|
|
|
|
canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
|
|
|
|
closure.forget();
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
let event_name = "touchend";
|
2020-07-18 17:40:24 +00:00
|
|
|
let runner_ref = runner_ref.clone();
|
2020-07-18 16:35:17 +00:00
|
|
|
let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| {
|
2020-07-18 17:40:24 +00:00
|
|
|
let mut runner_lock = runner_ref.0.lock();
|
2020-11-15 13:21:21 +00:00
|
|
|
runner_lock.input.is_touch = true;
|
2021-01-25 17:50:19 +00:00
|
|
|
if let Some(pos) = runner_lock.input.latest_touch_pos {
|
|
|
|
let modifiers = runner_lock.input.raw.modifiers;
|
|
|
|
// First release mouse to click:
|
|
|
|
runner_lock
|
|
|
|
.input
|
|
|
|
.raw
|
|
|
|
.events
|
|
|
|
.push(egui::Event::PointerButton {
|
|
|
|
pos,
|
|
|
|
button: egui::PointerButton::Primary,
|
|
|
|
pressed: false,
|
|
|
|
modifiers,
|
|
|
|
});
|
|
|
|
// Then remove hover effect:
|
|
|
|
runner_lock.input.raw.events.push(egui::Event::PointerGone);
|
|
|
|
runner_lock.needs_repaint.set_true();
|
|
|
|
event.stop_propagation();
|
|
|
|
event.prevent_default();
|
|
|
|
}
|
2020-07-18 16:35:17 +00:00
|
|
|
}) as Box<dyn FnMut(_)>);
|
|
|
|
canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
|
|
|
|
closure.forget();
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
let event_name = "wheel";
|
2020-07-18 17:40:24 +00:00
|
|
|
let runner_ref = runner_ref.clone();
|
2020-07-18 16:35:17 +00:00
|
|
|
let closure = Closure::wrap(Box::new(move |event: web_sys::WheelEvent| {
|
2020-07-18 17:40:24 +00:00
|
|
|
let mut runner_lock = runner_ref.0.lock();
|
2020-11-28 09:46:31 +00:00
|
|
|
runner_lock.input.raw.scroll_delta.x -= event.delta_x() as f32;
|
|
|
|
runner_lock.input.raw.scroll_delta.y -= event.delta_y() as f32;
|
2020-11-20 19:35:16 +00:00
|
|
|
runner_lock.needs_repaint.set_true();
|
2020-07-18 16:35:17 +00:00
|
|
|
event.stop_propagation();
|
|
|
|
event.prevent_default();
|
|
|
|
}) as Box<dyn FnMut(_)>);
|
|
|
|
canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
|
|
|
|
closure.forget();
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|