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
|
2020-05-10 17:04:10 +00:00
|
|
|
#![warn(clippy::all)]
|
2019-01-12 22:07:30 +00:00
|
|
|
|
2020-07-23 16:54:16 +00:00
|
|
|
pub mod backend;
|
2020-12-30 19:56:50 +00:00
|
|
|
pub mod http;
|
2019-02-09 22:00:07 +00:00
|
|
|
pub mod webgl;
|
2019-02-11 19:27:32 +00:00
|
|
|
|
2020-07-23 16:54:16 +00:00
|
|
|
pub use backend::*;
|
|
|
|
|
2020-12-29 14:57:13 +00:00
|
|
|
pub use wasm_bindgen;
|
|
|
|
pub use web_sys;
|
|
|
|
|
2020-10-13 22:29:11 +00:00
|
|
|
use egui::mutex::Mutex;
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-10-17 21:54:46 +00:00
|
|
|
pub fn resize_canvas_to_screen_size(canvas_id: &str) -> 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
|
|
|
|
|
|
|
let canvas_size_pixels = pixels_per_point * screen_size_points;
|
|
|
|
// Some browsers get slow with huge WebGL canvases, so we limit the size:
|
|
|
|
let max_size_pixels = egui::vec2(2048.0, 4096.0);
|
|
|
|
let canvas_size_pixels = canvas_size_pixels.min(max_size_pixels);
|
|
|
|
let canvas_size_points = canvas_size_pixels / pixels_per_point;
|
|
|
|
|
2020-07-18 08:54:31 +00:00
|
|
|
canvas
|
|
|
|
.style()
|
2020-12-18 21:51:23 +00:00
|
|
|
.set_property("width", &format!("{}px", canvas_size_points.x))
|
2020-07-18 08:54:31 +00:00
|
|
|
.ok()?;
|
|
|
|
canvas
|
|
|
|
.style()
|
2020-12-18 21:51:23 +00:00
|
|
|
.set_property("height", &format!("{}px", canvas_size_points.y))
|
2020-07-18 08:54:31 +00:00
|
|
|
.ok()?;
|
2020-12-18 21:51:23 +00:00
|
|
|
canvas.set_width(canvas_size_pixels.x.round() as u32);
|
|
|
|
canvas.set_height(canvas_size_pixels.y.round() 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
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
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),
|
2020-07-18 16:00:05 +00:00
|
|
|
"Backspace" => Some(egui::Key::Backspace),
|
|
|
|
"Delete" => Some(egui::Key::Delete),
|
|
|
|
"End" => Some(egui::Key::End),
|
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),
|
2020-07-30 09:54:42 +00:00
|
|
|
"Esc" | "Escape" => Some(egui::Key::Escape),
|
|
|
|
"Help" | "Insert" => Some(egui::Key::Insert),
|
2020-11-14 20:01:21 +00:00
|
|
|
"Home" => Some(egui::Key::Home),
|
2020-07-18 16:00:05 +00:00
|
|
|
"PageDown" => Some(egui::Key::PageDown),
|
|
|
|
"PageUp" => Some(egui::Key::PageUp),
|
|
|
|
"Tab" => Some(egui::Key::Tab),
|
2020-11-15 13:21:21 +00:00
|
|
|
"a" | "A" => Some(egui::Key::A),
|
|
|
|
"k" | "K" => Some(egui::Key::K),
|
|
|
|
"u" | "U" => Some(egui::Key::U),
|
|
|
|
"w" | "W" => Some(egui::Key::W),
|
|
|
|
"z" | "Z" => Some(egui::Key::Z),
|
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() {
|
2020-07-18 22:01:13 +00:00
|
|
|
let (output, paint_jobs) = runner_lock.logic()?;
|
|
|
|
runner_lock.paint(paint_jobs)?;
|
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
|
|
|
|
|
|
|
// So, shall we call prevent_default?
|
|
|
|
// YES:
|
|
|
|
// * Tab (move to next text field)
|
|
|
|
//
|
|
|
|
// SOMETIMES:
|
|
|
|
// * Backspace - when entering text we don't want to go back one page.
|
|
|
|
//
|
|
|
|
// NO:
|
|
|
|
// * F5 / cmd-R (refresh)
|
|
|
|
// * cmd-shift-C (debug tools)
|
|
|
|
// * ...
|
|
|
|
//
|
|
|
|
// NOTE: if we call prevent_default for cmd-c/v/x, we will prevent copy/paste/cut events.
|
|
|
|
// Let's do things manually for now:
|
|
|
|
if matches!(
|
|
|
|
event.key().as_str(),
|
|
|
|
"Backspace" // so we don't go back to previous page when deleting text
|
|
|
|
| "Tab" // so that e.g. tab doesn't move focus to url bar
|
|
|
|
) {
|
|
|
|
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
|
|
|
|
|
|
|
{
|
|
|
|
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 {
|
2020-11-28 09:46:31 +00:00
|
|
|
runner_lock.input.raw.mouse_pos =
|
2020-07-18 17:40:24 +00:00
|
|
|
Some(pos_from_mouse_event(runner_lock.canvas_id(), &event));
|
2020-11-15 13:21:21 +00:00
|
|
|
runner_lock.input.raw.mouse_down = true;
|
2020-07-18 22:44:06 +00:00
|
|
|
runner_lock.logic().unwrap(); // in case we get "mouseup" the same frame. TODO: handle via events instead
|
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 = "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 {
|
2020-11-28 09:46:31 +00:00
|
|
|
runner_lock.input.raw.mouse_pos =
|
2020-07-18 17:40:24 +00:00
|
|
|
Some(pos_from_mouse_event(runner_lock.canvas_id(), &event));
|
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 {
|
2020-11-28 09:46:31 +00:00
|
|
|
runner_lock.input.raw.mouse_pos =
|
2020-07-18 17:40:24 +00:00
|
|
|
Some(pos_from_mouse_event(runner_lock.canvas_id(), &event));
|
2020-11-15 13:21:21 +00:00
|
|
|
runner_lock.input.raw.mouse_down = false;
|
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 = "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 {
|
2020-11-28 09:46:31 +00:00
|
|
|
runner_lock.input.raw.mouse_pos = None;
|
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| {
|
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;
|
2020-11-28 09:46:31 +00:00
|
|
|
runner_lock.input.raw.mouse_pos = Some(pos_from_touch_event(&event));
|
2020-11-15 13:21:21 +00:00
|
|
|
runner_lock.input.raw.mouse_down = true;
|
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| {
|
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;
|
2020-11-28 09:46:31 +00:00
|
|
|
runner_lock.input.raw.mouse_pos = Some(pos_from_touch_event(&event));
|
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;
|
|
|
|
runner_lock.input.raw.mouse_down = false; // First release mouse to click...
|
2020-07-18 22:44:06 +00:00
|
|
|
runner_lock.logic().unwrap(); // ...do the clicking... (TODO: handle via events instead)
|
2020-11-28 09:46:31 +00:00
|
|
|
runner_lock.input.raw.mouse_pos = None; // ...remove hover effect
|
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 = "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(())
|
|
|
|
}
|