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.
|
2021-06-03 16:56:26 +00:00
|
|
|
//!
|
|
|
|
//! ## Specifying the size of the egui canvas
|
|
|
|
//! For performance reasons (on some browsers) the egui canvas does not, by default,
|
|
|
|
//! fill the whole width of the browser.
|
|
|
|
//! This can be changed by overriding [`epi::App::max_size_points`].
|
2021-01-02 10:59:20 +00:00
|
|
|
|
2021-06-23 07:16:39 +00:00
|
|
|
// Forbid warnings in release builds:
|
|
|
|
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
|
|
|
// Disabled so we can support rust 1.51:
|
|
|
|
// #![deny(
|
|
|
|
// rustdoc::broken_intra_doc_links,
|
|
|
|
// rustdoc::invalid_codeblock_attributes,
|
|
|
|
// rustdoc::missing_crate_level_docs,
|
|
|
|
// rustdoc::private_intra_doc_links
|
|
|
|
// )]
|
2021-04-15 08:35:15 +00:00
|
|
|
#![forbid(unsafe_code)]
|
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-16 00:30:00 +00:00
|
|
|
mod painter;
|
2021-03-08 19:58:01 +00:00
|
|
|
pub mod screen_reader;
|
2021-01-16 00:30:00 +00:00
|
|
|
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;
|
2021-03-26 12:56:26 +00:00
|
|
|
use std::cell::Cell;
|
|
|
|
use std::rc::Rc;
|
2020-07-18 16:35:17 +00:00
|
|
|
use std::sync::Arc;
|
|
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
|
2021-03-30 06:48:55 +00:00
|
|
|
static AGENT_ID: &str = "egui_text_agent";
|
2021-03-26 12:56:26 +00:00
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-07 18:56:18 +00:00
|
|
|
pub fn prefer_dark_mode() -> Option<bool> {
|
|
|
|
Some(
|
|
|
|
web_sys::window()?
|
|
|
|
.match_media("(prefers-color-scheme: dark)")
|
|
|
|
.ok()??
|
|
|
|
.matches(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-08 20:49:40 +00:00
|
|
|
/// A single touch is translated to a pointer movement. When a second touch is added, the pointer
|
|
|
|
/// should not jump to a different position. Therefore, we do not calculate the average position
|
Basic multi touch support (issue #279) (#306)
* translate touch events from glium to egui
Unfortunately, winit does not seem to create _Touch_ events for the touch pad
on my mac. Only _TouchpadPressure_ events are sent.
Found some issues (like
[this](https://github.com/rust-windowing/winit/issues/54)), but I am not sure
what they exactly mean: Sometimes, touch events are mixed with
touch-to-pointer translation in the discussions.
* translate touch events from web_sys to egui
The are a few open topics:
- egui_web currently translates touch events into pointer events.
I guess this should change, such that egui itself performs this kind of
conversion.
- `pub fn egui_web::pos_from_touch_event` is a public function, but I
would like to change the return type to an `Option`. Shouldn't this
function be private, anyway?
* introduce `TouchState` and `Gesture`
InputState.touch was introduced with type `TouchState`, just as
InputState.pointer is of type `Pointer`.
The TouchState internally relies on a collection of `Gesture`s. This commit
provides the first rudimentary implementation of a Gesture, but has no
functionality, yet.
* add method InputState::zoom()
So far, the method always returns `None`, but it should work as soon as the
`Zoom` gesture is implemented.
* manage one `TouchState` per individual device
Although quite unlikely, it is still possible to connect more than one touch
device. (I have three touch pads connected to my MacBook in total, but
unfortunately `winit` sends touch events for none of them.)
We do not want to mix-up the touches from different devices.
* implement control loop for gesture detection
The basic idea is that each gesture can focus on detection logic and does not
have to care (too much) about managing touch state in general.
* streamline `Gesture` trait, simplifying impl's
* implement first version of Zoom gesture
* fix failing doctest
a simple `TODO` should be enough
* get rid of `Gesture`s
* Provide a Zoom/Rotate window in the demo app
For now, it works for two fingers only. The third finger interrupts the
gesture.
Bugs:
- Pinching in the demo window also moves the window -> Pointer events must be
ignored when touch is active
- Pinching also works when doing it outside the demo window -> it would be nice
to return the touch info in the `Response` of the painter allocation
* fix comments and non-idiomatic code
* update touch state *each frame*
* change egui_demo to use *relative* touch data
* support more than two fingers
This commit includes an improved Demo Window for egui_demo, and a complete
re-write of the gesture detection. The PR should be ready for review, soon.
* cleanup code and comments for review
* minor code simplifications
* oops – forgot the changelog
* resolve comment https://github.com/emilk/egui/pull/306/files/fee8ed83dbe715b5b70433faacfe74b59c99e4a4#r623226656
* accept suggestion https://github.com/emilk/egui/pull/306#discussion_r623229228
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
* fix syntax error (dough!)
* remove `dbg!` (why didnt clippy see this?)
* apply suggested diffs from review
* fix conversion of physical location to Pos2
* remove redundanct type `TouchAverages`
* remove trailing space
* avoid initial translation jump in plot demo
* extend the demo so it shows off translation
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-05-06 19:01:10 +00:00
|
|
|
/// of all touches, but we keep using the same touch as long as it is available.
|
|
|
|
///
|
|
|
|
/// `touch_id_for_pos` is the `TouchId` of the `Touch` we previously used to determine the
|
2021-05-08 20:49:40 +00:00
|
|
|
/// pointer position.
|
Basic multi touch support (issue #279) (#306)
* translate touch events from glium to egui
Unfortunately, winit does not seem to create _Touch_ events for the touch pad
on my mac. Only _TouchpadPressure_ events are sent.
Found some issues (like
[this](https://github.com/rust-windowing/winit/issues/54)), but I am not sure
what they exactly mean: Sometimes, touch events are mixed with
touch-to-pointer translation in the discussions.
* translate touch events from web_sys to egui
The are a few open topics:
- egui_web currently translates touch events into pointer events.
I guess this should change, such that egui itself performs this kind of
conversion.
- `pub fn egui_web::pos_from_touch_event` is a public function, but I
would like to change the return type to an `Option`. Shouldn't this
function be private, anyway?
* introduce `TouchState` and `Gesture`
InputState.touch was introduced with type `TouchState`, just as
InputState.pointer is of type `Pointer`.
The TouchState internally relies on a collection of `Gesture`s. This commit
provides the first rudimentary implementation of a Gesture, but has no
functionality, yet.
* add method InputState::zoom()
So far, the method always returns `None`, but it should work as soon as the
`Zoom` gesture is implemented.
* manage one `TouchState` per individual device
Although quite unlikely, it is still possible to connect more than one touch
device. (I have three touch pads connected to my MacBook in total, but
unfortunately `winit` sends touch events for none of them.)
We do not want to mix-up the touches from different devices.
* implement control loop for gesture detection
The basic idea is that each gesture can focus on detection logic and does not
have to care (too much) about managing touch state in general.
* streamline `Gesture` trait, simplifying impl's
* implement first version of Zoom gesture
* fix failing doctest
a simple `TODO` should be enough
* get rid of `Gesture`s
* Provide a Zoom/Rotate window in the demo app
For now, it works for two fingers only. The third finger interrupts the
gesture.
Bugs:
- Pinching in the demo window also moves the window -> Pointer events must be
ignored when touch is active
- Pinching also works when doing it outside the demo window -> it would be nice
to return the touch info in the `Response` of the painter allocation
* fix comments and non-idiomatic code
* update touch state *each frame*
* change egui_demo to use *relative* touch data
* support more than two fingers
This commit includes an improved Demo Window for egui_demo, and a complete
re-write of the gesture detection. The PR should be ready for review, soon.
* cleanup code and comments for review
* minor code simplifications
* oops – forgot the changelog
* resolve comment https://github.com/emilk/egui/pull/306/files/fee8ed83dbe715b5b70433faacfe74b59c99e4a4#r623226656
* accept suggestion https://github.com/emilk/egui/pull/306#discussion_r623229228
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
* fix syntax error (dough!)
* remove `dbg!` (why didnt clippy see this?)
* apply suggested diffs from review
* fix conversion of physical location to Pos2
* remove redundanct type `TouchAverages`
* remove trailing space
* avoid initial translation jump in plot demo
* extend the demo so it shows off translation
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-05-06 19:01:10 +00:00
|
|
|
pub fn pos_from_touch_event(
|
|
|
|
canvas_id: &str,
|
|
|
|
event: &web_sys::TouchEvent,
|
|
|
|
touch_id_for_pos: &mut Option<egui::TouchId>,
|
|
|
|
) -> egui::Pos2 {
|
|
|
|
let touch_for_pos;
|
|
|
|
if let Some(touch_id_for_pos) = touch_id_for_pos {
|
|
|
|
// search for the touch we previously used for the position
|
|
|
|
// (unfortunately, `event.touches()` is not a rust collection):
|
|
|
|
touch_for_pos = (0..event.touches().length())
|
|
|
|
.into_iter()
|
|
|
|
.map(|i| event.touches().get(i).unwrap())
|
|
|
|
.find(|touch| egui::TouchId::from(touch.identifier()) == *touch_id_for_pos);
|
|
|
|
} else {
|
|
|
|
touch_for_pos = None;
|
|
|
|
}
|
|
|
|
// Use the touch found above or pick the first, or return a default position if there is no
|
|
|
|
// touch at all. (The latter is not expected as the current method is only called when there is
|
|
|
|
// at least one touch.)
|
|
|
|
touch_for_pos
|
|
|
|
.or_else(|| event.touches().get(0))
|
|
|
|
.map_or(Default::default(), |touch| {
|
|
|
|
*touch_id_for_pos = Some(egui::TouchId::from(touch.identifier()));
|
|
|
|
pos_from_touch(canvas_origin(canvas_id), &touch)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
fn pos_from_touch(canvas_origin: egui::Pos2, touch: &web_sys::Touch) -> egui::Pos2 {
|
2020-07-18 16:00:05 +00:00
|
|
|
egui::Pos2 {
|
Basic multi touch support (issue #279) (#306)
* translate touch events from glium to egui
Unfortunately, winit does not seem to create _Touch_ events for the touch pad
on my mac. Only _TouchpadPressure_ events are sent.
Found some issues (like
[this](https://github.com/rust-windowing/winit/issues/54)), but I am not sure
what they exactly mean: Sometimes, touch events are mixed with
touch-to-pointer translation in the discussions.
* translate touch events from web_sys to egui
The are a few open topics:
- egui_web currently translates touch events into pointer events.
I guess this should change, such that egui itself performs this kind of
conversion.
- `pub fn egui_web::pos_from_touch_event` is a public function, but I
would like to change the return type to an `Option`. Shouldn't this
function be private, anyway?
* introduce `TouchState` and `Gesture`
InputState.touch was introduced with type `TouchState`, just as
InputState.pointer is of type `Pointer`.
The TouchState internally relies on a collection of `Gesture`s. This commit
provides the first rudimentary implementation of a Gesture, but has no
functionality, yet.
* add method InputState::zoom()
So far, the method always returns `None`, but it should work as soon as the
`Zoom` gesture is implemented.
* manage one `TouchState` per individual device
Although quite unlikely, it is still possible to connect more than one touch
device. (I have three touch pads connected to my MacBook in total, but
unfortunately `winit` sends touch events for none of them.)
We do not want to mix-up the touches from different devices.
* implement control loop for gesture detection
The basic idea is that each gesture can focus on detection logic and does not
have to care (too much) about managing touch state in general.
* streamline `Gesture` trait, simplifying impl's
* implement first version of Zoom gesture
* fix failing doctest
a simple `TODO` should be enough
* get rid of `Gesture`s
* Provide a Zoom/Rotate window in the demo app
For now, it works for two fingers only. The third finger interrupts the
gesture.
Bugs:
- Pinching in the demo window also moves the window -> Pointer events must be
ignored when touch is active
- Pinching also works when doing it outside the demo window -> it would be nice
to return the touch info in the `Response` of the painter allocation
* fix comments and non-idiomatic code
* update touch state *each frame*
* change egui_demo to use *relative* touch data
* support more than two fingers
This commit includes an improved Demo Window for egui_demo, and a complete
re-write of the gesture detection. The PR should be ready for review, soon.
* cleanup code and comments for review
* minor code simplifications
* oops – forgot the changelog
* resolve comment https://github.com/emilk/egui/pull/306/files/fee8ed83dbe715b5b70433faacfe74b59c99e4a4#r623226656
* accept suggestion https://github.com/emilk/egui/pull/306#discussion_r623229228
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
* fix syntax error (dough!)
* remove `dbg!` (why didnt clippy see this?)
* apply suggested diffs from review
* fix conversion of physical location to Pos2
* remove redundanct type `TouchAverages`
* remove trailing space
* avoid initial translation jump in plot demo
* extend the demo so it shows off translation
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-05-06 19:01:10 +00:00
|
|
|
x: touch.page_x() as f32 - canvas_origin.x as f32,
|
|
|
|
y: touch.page_y() as f32 - canvas_origin.y as f32,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn canvas_origin(canvas_id: &str) -> egui::Pos2 {
|
|
|
|
let rect = canvas_element(canvas_id)
|
|
|
|
.unwrap()
|
|
|
|
.get_bounding_client_rect();
|
|
|
|
egui::Pos2::new(rect.left() as f32, rect.top() as f32)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn push_touches(runner: &mut AppRunner, phase: egui::TouchPhase, event: &web_sys::TouchEvent) {
|
|
|
|
let canvas_origin = canvas_origin(runner.canvas_id());
|
|
|
|
for touch_idx in 0..event.changed_touches().length() {
|
|
|
|
if let Some(touch) = event.changed_touches().item(touch_idx) {
|
|
|
|
runner.input.raw.events.push(egui::Event::Touch {
|
|
|
|
device_id: egui::TouchDeviceId(0),
|
|
|
|
id: egui::TouchId::from(touch.identifier()),
|
|
|
|
phase,
|
|
|
|
pos: pos_from_touch(canvas_origin, &touch),
|
|
|
|
force: touch.force(),
|
|
|
|
});
|
|
|
|
}
|
2020-07-18 16:00:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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) {
|
2021-04-05 10:09:01 +00:00
|
|
|
if let Some(memory_string) = local_storage_get("egui_memory_ron") {
|
|
|
|
match ron::from_str(&memory_string) {
|
2020-05-02 09:37:12 +00:00
|
|
|
Ok(memory) => {
|
|
|
|
*ctx.memory() = memory;
|
|
|
|
}
|
|
|
|
Err(err) => {
|
2021-04-05 10:09:01 +00:00
|
|
|
console_error(format!("Failed to parse memory RON: {}", 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) {
|
2021-04-05 10:09:01 +00:00
|
|
|
match ron::to_string(&*ctx.memory()) {
|
|
|
|
Ok(ron) => {
|
|
|
|
local_storage_set("egui_memory_ron", &ron);
|
2020-05-02 09:37:12 +00:00
|
|
|
}
|
|
|
|
Err(err) => {
|
2021-04-05 10:09:01 +00:00
|
|
|
console_error(format!("Failed to serialize memory as RON: {}", 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) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2021-03-30 06:48:55 +00:00
|
|
|
pub fn handle_output(output: &egui::Output, runner: &mut AppRunner) {
|
2020-11-15 19:55:41 +00:00
|
|
|
let egui::Output {
|
|
|
|
cursor_icon,
|
|
|
|
open_url,
|
|
|
|
copied_text,
|
|
|
|
needs_repaint: _, // handled elsewhere
|
2021-03-07 18:32:27 +00:00
|
|
|
events: _, // we ignore these (TODO: accessibility screen reader)
|
2021-05-20 19:58:44 +00:00
|
|
|
text_cursor_pos,
|
2020-11-15 19:55:41 +00:00
|
|
|
} = output;
|
|
|
|
|
|
|
|
set_cursor_icon(*cursor_icon);
|
2021-03-08 16:48:23 +00:00
|
|
|
if let Some(open) = open_url {
|
|
|
|
crate::open_url(&open.url, open.new_tab);
|
2020-11-15 19:55:41 +00:00
|
|
|
}
|
|
|
|
|
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;
|
2021-03-30 06:48:55 +00:00
|
|
|
|
2021-05-20 19:58:44 +00:00
|
|
|
if &runner.last_text_cursor_pos != text_cursor_pos {
|
|
|
|
move_text_cursor(text_cursor_pos, runner.canvas_id());
|
|
|
|
runner.last_text_cursor_pos = *text_cursor_pos;
|
2021-03-30 06:48:55 +00:00
|
|
|
}
|
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() {
|
2021-08-15 14:55:33 +00:00
|
|
|
if let Some(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 {
|
|
|
|
console_error(format!("Copy/cut action denied: {:?}", err));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
wasm_bindgen_futures::spawn_local(future);
|
|
|
|
}
|
2020-11-15 19:55:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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 {
|
|
|
|
match cursor {
|
2021-03-13 11:38:03 +00:00
|
|
|
egui::CursorIcon::Alias => "alias",
|
|
|
|
egui::CursorIcon::AllScroll => "all-scroll",
|
|
|
|
egui::CursorIcon::Cell => "cell",
|
|
|
|
egui::CursorIcon::ContextMenu => "context-menu",
|
|
|
|
egui::CursorIcon::Copy => "copy",
|
|
|
|
egui::CursorIcon::Crosshair => "crosshair",
|
|
|
|
egui::CursorIcon::Default => "default",
|
|
|
|
egui::CursorIcon::Grab => "grab",
|
|
|
|
egui::CursorIcon::Grabbing => "grabbing",
|
|
|
|
egui::CursorIcon::Help => "help",
|
|
|
|
egui::CursorIcon::Move => "move",
|
|
|
|
egui::CursorIcon::NoDrop => "no-drop",
|
|
|
|
egui::CursorIcon::None => "none",
|
|
|
|
egui::CursorIcon::NotAllowed => "not-allowed",
|
|
|
|
egui::CursorIcon::PointingHand => "pointer",
|
|
|
|
egui::CursorIcon::Progress => "progress",
|
|
|
|
egui::CursorIcon::ResizeHorizontal => "ew-resize",
|
|
|
|
egui::CursorIcon::ResizeNeSw => "nesw-resize",
|
|
|
|
egui::CursorIcon::ResizeNwSe => "nwse-resize",
|
|
|
|
egui::CursorIcon::ResizeVertical => "ns-resize",
|
|
|
|
egui::CursorIcon::Text => "text",
|
|
|
|
egui::CursorIcon::VerticalText => "vertical-text",
|
|
|
|
egui::CursorIcon::Wait => "wait",
|
|
|
|
egui::CursorIcon::ZoomIn => "zoom-in",
|
|
|
|
egui::CursorIcon::ZoomOut => "zoom-out",
|
2020-07-18 08:54:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-08 16:48:23 +00:00
|
|
|
pub fn open_url(url: &str, new_tab: bool) -> Option<()> {
|
|
|
|
let name = if new_tab { "_blank" } else { "_self" };
|
|
|
|
|
2020-07-18 08:54:31 +00:00
|
|
|
web_sys::window()?
|
2021-03-08 16:48:23 +00:00
|
|
|
.open_with_url_and_target(url, name)
|
2020-07-18 08:54:31 +00:00
|
|
|
.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),
|
2021-03-07 09:20:11 +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
|
|
|
}
|
|
|
|
|
2021-03-30 06:48:55 +00:00
|
|
|
fn text_agent() -> web_sys::HtmlInputElement {
|
2021-03-26 12:56:26 +00:00
|
|
|
use wasm_bindgen::JsCast;
|
|
|
|
web_sys::window()
|
|
|
|
.unwrap()
|
|
|
|
.document()
|
|
|
|
.unwrap()
|
|
|
|
.get_element_by_id(AGENT_ID)
|
|
|
|
.unwrap()
|
2021-03-30 06:48:55 +00:00
|
|
|
.dyn_into()
|
2021-03-26 12:56:26 +00:00
|
|
|
.unwrap()
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
2021-03-26 12:56:26 +00:00
|
|
|
if !modifiers.ctrl
|
|
|
|
&& !modifiers.command
|
|
|
|
&& !should_ignore_key(&key)
|
|
|
|
// When text agent is shown, it sends text event instead.
|
2021-03-30 06:48:55 +00:00
|
|
|
&& text_agent().hidden()
|
2021-03-26 12:56:26 +00:00
|
|
|
{
|
2020-11-15 13:21:21 +00:00
|
|
|
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)]
|
2021-05-11 17:25:31 +00:00
|
|
|
// paste is handled by IME text agent!
|
|
|
|
if false {
|
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(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-26 12:56:26 +00:00
|
|
|
///
|
|
|
|
/// Text event handler,
|
|
|
|
fn install_text_agent(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
|
|
|
|
use wasm_bindgen::JsCast;
|
|
|
|
let window = web_sys::window().unwrap();
|
|
|
|
let document = window.document().unwrap();
|
|
|
|
let body = document.body().expect("document should have a body");
|
|
|
|
let input = document
|
|
|
|
.create_element("input")?
|
|
|
|
.dyn_into::<web_sys::HtmlInputElement>()?;
|
|
|
|
let input = std::rc::Rc::new(input);
|
|
|
|
input.set_id(AGENT_ID);
|
|
|
|
let is_composing = Rc::new(Cell::new(false));
|
|
|
|
{
|
|
|
|
let style = input.style();
|
|
|
|
// Transparent
|
|
|
|
style.set_property("opacity", "0").unwrap();
|
|
|
|
// Hide under canvas
|
|
|
|
style.set_property("z-index", "-1").unwrap();
|
|
|
|
}
|
|
|
|
// Set size as small as possible, in case user may click on it.
|
|
|
|
input.set_size(1);
|
|
|
|
input.set_autofocus(true);
|
|
|
|
input.set_hidden(true);
|
|
|
|
{
|
|
|
|
// When IME is off
|
|
|
|
let input_clone = input.clone();
|
|
|
|
let runner_ref = runner_ref.clone();
|
|
|
|
let is_composing = is_composing.clone();
|
|
|
|
let on_input = Closure::wrap(Box::new(move |_event: web_sys::InputEvent| {
|
|
|
|
let text = input_clone.value();
|
|
|
|
if !text.is_empty() && !is_composing.get() {
|
|
|
|
input_clone.set_value("");
|
|
|
|
let mut runner_lock = runner_ref.0.lock();
|
|
|
|
runner_lock.input.raw.events.push(egui::Event::Text(text));
|
|
|
|
runner_lock.needs_repaint.set_true();
|
|
|
|
}
|
|
|
|
}) as Box<dyn FnMut(_)>);
|
|
|
|
input.add_event_listener_with_callback("input", on_input.as_ref().unchecked_ref())?;
|
|
|
|
on_input.forget();
|
|
|
|
}
|
|
|
|
{
|
|
|
|
// When IME is on, handle composition event
|
|
|
|
let input_clone = input.clone();
|
|
|
|
let runner_ref = runner_ref.clone();
|
|
|
|
let on_compositionend = Closure::wrap(Box::new(move |event: web_sys::CompositionEvent| {
|
2021-04-12 18:57:14 +00:00
|
|
|
let mut runner_lock = runner_ref.0.lock();
|
|
|
|
let opt_event = match event.type_().as_ref() {
|
2021-03-26 12:56:26 +00:00
|
|
|
"compositionstart" => {
|
|
|
|
is_composing.set(true);
|
|
|
|
input_clone.set_value("");
|
2021-04-12 18:57:14 +00:00
|
|
|
Some(egui::Event::CompositionStart)
|
2021-03-26 12:56:26 +00:00
|
|
|
}
|
|
|
|
"compositionend" => {
|
|
|
|
is_composing.set(false);
|
|
|
|
input_clone.set_value("");
|
2021-04-12 18:57:14 +00:00
|
|
|
event.data().map(egui::Event::CompositionEnd)
|
|
|
|
}
|
|
|
|
"compositionupdate" => event.data().map(egui::Event::CompositionUpdate),
|
|
|
|
s => {
|
|
|
|
console_error(format!("Unknown composition event type: {:?}", s));
|
|
|
|
None
|
2021-03-26 12:56:26 +00:00
|
|
|
}
|
2021-04-12 18:57:14 +00:00
|
|
|
};
|
|
|
|
if let Some(event) = opt_event {
|
|
|
|
runner_lock.input.raw.events.push(event);
|
|
|
|
runner_lock.needs_repaint.set_true();
|
2021-03-26 12:56:26 +00:00
|
|
|
}
|
|
|
|
}) as Box<dyn FnMut(_)>);
|
|
|
|
let f = on_compositionend.as_ref().unchecked_ref();
|
|
|
|
input.add_event_listener_with_callback("compositionstart", f)?;
|
|
|
|
input.add_event_listener_with_callback("compositionupdate", f)?;
|
|
|
|
input.add_event_listener_with_callback("compositionend", f)?;
|
|
|
|
on_compositionend.forget();
|
|
|
|
}
|
|
|
|
{
|
|
|
|
// When input lost focus, focus on it again.
|
|
|
|
// It is useful when user click somewhere outside canvas.
|
|
|
|
let on_focusout = Closure::wrap(Box::new(move |_event: web_sys::MouseEvent| {
|
|
|
|
// Delay 10 ms, and focus again.
|
|
|
|
let func = js_sys::Function::new_no_args(&format!(
|
|
|
|
"document.getElementById('{}').focus()",
|
|
|
|
AGENT_ID
|
|
|
|
));
|
|
|
|
window
|
|
|
|
.set_timeout_with_callback_and_timeout_and_arguments_0(&func, 10)
|
|
|
|
.unwrap();
|
|
|
|
}) as Box<dyn FnMut(_)>);
|
|
|
|
input.add_event_listener_with_callback("focusout", on_focusout.as_ref().unchecked_ref())?;
|
|
|
|
on_focusout.forget();
|
|
|
|
}
|
|
|
|
body.append_child(&input)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
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| {
|
2021-08-28 06:37:07 +00:00
|
|
|
if let Some(button) = button_from_mouse_event(&event) {
|
|
|
|
let mut runner_lock = runner_ref.0.lock();
|
|
|
|
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();
|
2020-07-18 16:35:17 +00:00
|
|
|
}
|
2021-08-28 06:37:07 +00:00
|
|
|
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();
|
2021-08-28 06:37:07 +00:00
|
|
|
let pos = pos_from_mouse_event(runner_lock.canvas_id(), &event);
|
|
|
|
runner_lock
|
|
|
|
.input
|
|
|
|
.raw
|
|
|
|
.events
|
|
|
|
.push(egui::Event::PointerMoved(pos));
|
|
|
|
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 = "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| {
|
2021-08-28 06:37:07 +00:00
|
|
|
if let Some(button) = button_from_mouse_event(&event) {
|
|
|
|
let mut runner_lock = runner_ref.0.lock();
|
|
|
|
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();
|
2021-03-26 12:56:26 +00:00
|
|
|
manipulate_agent(runner_lock.canvas_id(), runner_lock.input.latest_touch_pos);
|
2020-07-18 16:35:17 +00:00
|
|
|
}
|
2021-08-28 06:37:07 +00:00
|
|
|
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();
|
2021-08-28 06:37:07 +00:00
|
|
|
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 = "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();
|
Basic multi touch support (issue #279) (#306)
* translate touch events from glium to egui
Unfortunately, winit does not seem to create _Touch_ events for the touch pad
on my mac. Only _TouchpadPressure_ events are sent.
Found some issues (like
[this](https://github.com/rust-windowing/winit/issues/54)), but I am not sure
what they exactly mean: Sometimes, touch events are mixed with
touch-to-pointer translation in the discussions.
* translate touch events from web_sys to egui
The are a few open topics:
- egui_web currently translates touch events into pointer events.
I guess this should change, such that egui itself performs this kind of
conversion.
- `pub fn egui_web::pos_from_touch_event` is a public function, but I
would like to change the return type to an `Option`. Shouldn't this
function be private, anyway?
* introduce `TouchState` and `Gesture`
InputState.touch was introduced with type `TouchState`, just as
InputState.pointer is of type `Pointer`.
The TouchState internally relies on a collection of `Gesture`s. This commit
provides the first rudimentary implementation of a Gesture, but has no
functionality, yet.
* add method InputState::zoom()
So far, the method always returns `None`, but it should work as soon as the
`Zoom` gesture is implemented.
* manage one `TouchState` per individual device
Although quite unlikely, it is still possible to connect more than one touch
device. (I have three touch pads connected to my MacBook in total, but
unfortunately `winit` sends touch events for none of them.)
We do not want to mix-up the touches from different devices.
* implement control loop for gesture detection
The basic idea is that each gesture can focus on detection logic and does not
have to care (too much) about managing touch state in general.
* streamline `Gesture` trait, simplifying impl's
* implement first version of Zoom gesture
* fix failing doctest
a simple `TODO` should be enough
* get rid of `Gesture`s
* Provide a Zoom/Rotate window in the demo app
For now, it works for two fingers only. The third finger interrupts the
gesture.
Bugs:
- Pinching in the demo window also moves the window -> Pointer events must be
ignored when touch is active
- Pinching also works when doing it outside the demo window -> it would be nice
to return the touch info in the `Response` of the painter allocation
* fix comments and non-idiomatic code
* update touch state *each frame*
* change egui_demo to use *relative* touch data
* support more than two fingers
This commit includes an improved Demo Window for egui_demo, and a complete
re-write of the gesture detection. The PR should be ready for review, soon.
* cleanup code and comments for review
* minor code simplifications
* oops – forgot the changelog
* resolve comment https://github.com/emilk/egui/pull/306/files/fee8ed83dbe715b5b70433faacfe74b59c99e4a4#r623226656
* accept suggestion https://github.com/emilk/egui/pull/306#discussion_r623229228
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
* fix syntax error (dough!)
* remove `dbg!` (why didnt clippy see this?)
* apply suggested diffs from review
* fix conversion of physical location to Pos2
* remove redundanct type `TouchAverages`
* remove trailing space
* avoid initial translation jump in plot demo
* extend the demo so it shows off translation
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-05-06 19:01:10 +00:00
|
|
|
let mut latest_touch_pos_id = runner_lock.input.latest_touch_pos_id;
|
|
|
|
let pos =
|
|
|
|
pos_from_touch_event(runner_lock.canvas_id(), &event, &mut latest_touch_pos_id);
|
|
|
|
runner_lock.input.latest_touch_pos_id = latest_touch_pos_id;
|
2021-01-25 17:50:19 +00:00
|
|
|
runner_lock.input.latest_touch_pos = Some(pos);
|
|
|
|
let modifiers = runner_lock.input.raw.modifiers;
|
|
|
|
runner_lock
|
|
|
|
.input
|
|
|
|
.raw
|
|
|
|
.events
|
|
|
|
.push(egui::Event::PointerButton {
|
|
|
|
pos,
|
|
|
|
button: egui::PointerButton::Primary,
|
|
|
|
pressed: true,
|
|
|
|
modifiers,
|
|
|
|
});
|
Basic multi touch support (issue #279) (#306)
* translate touch events from glium to egui
Unfortunately, winit does not seem to create _Touch_ events for the touch pad
on my mac. Only _TouchpadPressure_ events are sent.
Found some issues (like
[this](https://github.com/rust-windowing/winit/issues/54)), but I am not sure
what they exactly mean: Sometimes, touch events are mixed with
touch-to-pointer translation in the discussions.
* translate touch events from web_sys to egui
The are a few open topics:
- egui_web currently translates touch events into pointer events.
I guess this should change, such that egui itself performs this kind of
conversion.
- `pub fn egui_web::pos_from_touch_event` is a public function, but I
would like to change the return type to an `Option`. Shouldn't this
function be private, anyway?
* introduce `TouchState` and `Gesture`
InputState.touch was introduced with type `TouchState`, just as
InputState.pointer is of type `Pointer`.
The TouchState internally relies on a collection of `Gesture`s. This commit
provides the first rudimentary implementation of a Gesture, but has no
functionality, yet.
* add method InputState::zoom()
So far, the method always returns `None`, but it should work as soon as the
`Zoom` gesture is implemented.
* manage one `TouchState` per individual device
Although quite unlikely, it is still possible to connect more than one touch
device. (I have three touch pads connected to my MacBook in total, but
unfortunately `winit` sends touch events for none of them.)
We do not want to mix-up the touches from different devices.
* implement control loop for gesture detection
The basic idea is that each gesture can focus on detection logic and does not
have to care (too much) about managing touch state in general.
* streamline `Gesture` trait, simplifying impl's
* implement first version of Zoom gesture
* fix failing doctest
a simple `TODO` should be enough
* get rid of `Gesture`s
* Provide a Zoom/Rotate window in the demo app
For now, it works for two fingers only. The third finger interrupts the
gesture.
Bugs:
- Pinching in the demo window also moves the window -> Pointer events must be
ignored when touch is active
- Pinching also works when doing it outside the demo window -> it would be nice
to return the touch info in the `Response` of the painter allocation
* fix comments and non-idiomatic code
* update touch state *each frame*
* change egui_demo to use *relative* touch data
* support more than two fingers
This commit includes an improved Demo Window for egui_demo, and a complete
re-write of the gesture detection. The PR should be ready for review, soon.
* cleanup code and comments for review
* minor code simplifications
* oops – forgot the changelog
* resolve comment https://github.com/emilk/egui/pull/306/files/fee8ed83dbe715b5b70433faacfe74b59c99e4a4#r623226656
* accept suggestion https://github.com/emilk/egui/pull/306#discussion_r623229228
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
* fix syntax error (dough!)
* remove `dbg!` (why didnt clippy see this?)
* apply suggested diffs from review
* fix conversion of physical location to Pos2
* remove redundanct type `TouchAverages`
* remove trailing space
* avoid initial translation jump in plot demo
* extend the demo so it shows off translation
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-05-06 19:01:10 +00:00
|
|
|
|
|
|
|
push_touches(&mut *runner_lock, egui::TouchPhase::Start, &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 = "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();
|
Basic multi touch support (issue #279) (#306)
* translate touch events from glium to egui
Unfortunately, winit does not seem to create _Touch_ events for the touch pad
on my mac. Only _TouchpadPressure_ events are sent.
Found some issues (like
[this](https://github.com/rust-windowing/winit/issues/54)), but I am not sure
what they exactly mean: Sometimes, touch events are mixed with
touch-to-pointer translation in the discussions.
* translate touch events from web_sys to egui
The are a few open topics:
- egui_web currently translates touch events into pointer events.
I guess this should change, such that egui itself performs this kind of
conversion.
- `pub fn egui_web::pos_from_touch_event` is a public function, but I
would like to change the return type to an `Option`. Shouldn't this
function be private, anyway?
* introduce `TouchState` and `Gesture`
InputState.touch was introduced with type `TouchState`, just as
InputState.pointer is of type `Pointer`.
The TouchState internally relies on a collection of `Gesture`s. This commit
provides the first rudimentary implementation of a Gesture, but has no
functionality, yet.
* add method InputState::zoom()
So far, the method always returns `None`, but it should work as soon as the
`Zoom` gesture is implemented.
* manage one `TouchState` per individual device
Although quite unlikely, it is still possible to connect more than one touch
device. (I have three touch pads connected to my MacBook in total, but
unfortunately `winit` sends touch events for none of them.)
We do not want to mix-up the touches from different devices.
* implement control loop for gesture detection
The basic idea is that each gesture can focus on detection logic and does not
have to care (too much) about managing touch state in general.
* streamline `Gesture` trait, simplifying impl's
* implement first version of Zoom gesture
* fix failing doctest
a simple `TODO` should be enough
* get rid of `Gesture`s
* Provide a Zoom/Rotate window in the demo app
For now, it works for two fingers only. The third finger interrupts the
gesture.
Bugs:
- Pinching in the demo window also moves the window -> Pointer events must be
ignored when touch is active
- Pinching also works when doing it outside the demo window -> it would be nice
to return the touch info in the `Response` of the painter allocation
* fix comments and non-idiomatic code
* update touch state *each frame*
* change egui_demo to use *relative* touch data
* support more than two fingers
This commit includes an improved Demo Window for egui_demo, and a complete
re-write of the gesture detection. The PR should be ready for review, soon.
* cleanup code and comments for review
* minor code simplifications
* oops – forgot the changelog
* resolve comment https://github.com/emilk/egui/pull/306/files/fee8ed83dbe715b5b70433faacfe74b59c99e4a4#r623226656
* accept suggestion https://github.com/emilk/egui/pull/306#discussion_r623229228
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
* fix syntax error (dough!)
* remove `dbg!` (why didnt clippy see this?)
* apply suggested diffs from review
* fix conversion of physical location to Pos2
* remove redundanct type `TouchAverages`
* remove trailing space
* avoid initial translation jump in plot demo
* extend the demo so it shows off translation
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-05-06 19:01:10 +00:00
|
|
|
let mut latest_touch_pos_id = runner_lock.input.latest_touch_pos_id;
|
|
|
|
let pos =
|
|
|
|
pos_from_touch_event(runner_lock.canvas_id(), &event, &mut latest_touch_pos_id);
|
|
|
|
runner_lock.input.latest_touch_pos_id = latest_touch_pos_id;
|
2021-01-25 17:50:19 +00:00
|
|
|
runner_lock.input.latest_touch_pos = Some(pos);
|
|
|
|
runner_lock
|
|
|
|
.input
|
|
|
|
.raw
|
|
|
|
.events
|
|
|
|
.push(egui::Event::PointerMoved(pos));
|
Basic multi touch support (issue #279) (#306)
* translate touch events from glium to egui
Unfortunately, winit does not seem to create _Touch_ events for the touch pad
on my mac. Only _TouchpadPressure_ events are sent.
Found some issues (like
[this](https://github.com/rust-windowing/winit/issues/54)), but I am not sure
what they exactly mean: Sometimes, touch events are mixed with
touch-to-pointer translation in the discussions.
* translate touch events from web_sys to egui
The are a few open topics:
- egui_web currently translates touch events into pointer events.
I guess this should change, such that egui itself performs this kind of
conversion.
- `pub fn egui_web::pos_from_touch_event` is a public function, but I
would like to change the return type to an `Option`. Shouldn't this
function be private, anyway?
* introduce `TouchState` and `Gesture`
InputState.touch was introduced with type `TouchState`, just as
InputState.pointer is of type `Pointer`.
The TouchState internally relies on a collection of `Gesture`s. This commit
provides the first rudimentary implementation of a Gesture, but has no
functionality, yet.
* add method InputState::zoom()
So far, the method always returns `None`, but it should work as soon as the
`Zoom` gesture is implemented.
* manage one `TouchState` per individual device
Although quite unlikely, it is still possible to connect more than one touch
device. (I have three touch pads connected to my MacBook in total, but
unfortunately `winit` sends touch events for none of them.)
We do not want to mix-up the touches from different devices.
* implement control loop for gesture detection
The basic idea is that each gesture can focus on detection logic and does not
have to care (too much) about managing touch state in general.
* streamline `Gesture` trait, simplifying impl's
* implement first version of Zoom gesture
* fix failing doctest
a simple `TODO` should be enough
* get rid of `Gesture`s
* Provide a Zoom/Rotate window in the demo app
For now, it works for two fingers only. The third finger interrupts the
gesture.
Bugs:
- Pinching in the demo window also moves the window -> Pointer events must be
ignored when touch is active
- Pinching also works when doing it outside the demo window -> it would be nice
to return the touch info in the `Response` of the painter allocation
* fix comments and non-idiomatic code
* update touch state *each frame*
* change egui_demo to use *relative* touch data
* support more than two fingers
This commit includes an improved Demo Window for egui_demo, and a complete
re-write of the gesture detection. The PR should be ready for review, soon.
* cleanup code and comments for review
* minor code simplifications
* oops – forgot the changelog
* resolve comment https://github.com/emilk/egui/pull/306/files/fee8ed83dbe715b5b70433faacfe74b59c99e4a4#r623226656
* accept suggestion https://github.com/emilk/egui/pull/306#discussion_r623229228
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
* fix syntax error (dough!)
* remove `dbg!` (why didnt clippy see this?)
* apply suggested diffs from review
* fix conversion of physical location to Pos2
* remove redundanct type `TouchAverages`
* remove trailing space
* avoid initial translation jump in plot demo
* extend the demo so it shows off translation
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-05-06 19:01:10 +00:00
|
|
|
|
|
|
|
push_touches(&mut *runner_lock, egui::TouchPhase::Move, &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();
|
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);
|
Basic multi touch support (issue #279) (#306)
* translate touch events from glium to egui
Unfortunately, winit does not seem to create _Touch_ events for the touch pad
on my mac. Only _TouchpadPressure_ events are sent.
Found some issues (like
[this](https://github.com/rust-windowing/winit/issues/54)), but I am not sure
what they exactly mean: Sometimes, touch events are mixed with
touch-to-pointer translation in the discussions.
* translate touch events from web_sys to egui
The are a few open topics:
- egui_web currently translates touch events into pointer events.
I guess this should change, such that egui itself performs this kind of
conversion.
- `pub fn egui_web::pos_from_touch_event` is a public function, but I
would like to change the return type to an `Option`. Shouldn't this
function be private, anyway?
* introduce `TouchState` and `Gesture`
InputState.touch was introduced with type `TouchState`, just as
InputState.pointer is of type `Pointer`.
The TouchState internally relies on a collection of `Gesture`s. This commit
provides the first rudimentary implementation of a Gesture, but has no
functionality, yet.
* add method InputState::zoom()
So far, the method always returns `None`, but it should work as soon as the
`Zoom` gesture is implemented.
* manage one `TouchState` per individual device
Although quite unlikely, it is still possible to connect more than one touch
device. (I have three touch pads connected to my MacBook in total, but
unfortunately `winit` sends touch events for none of them.)
We do not want to mix-up the touches from different devices.
* implement control loop for gesture detection
The basic idea is that each gesture can focus on detection logic and does not
have to care (too much) about managing touch state in general.
* streamline `Gesture` trait, simplifying impl's
* implement first version of Zoom gesture
* fix failing doctest
a simple `TODO` should be enough
* get rid of `Gesture`s
* Provide a Zoom/Rotate window in the demo app
For now, it works for two fingers only. The third finger interrupts the
gesture.
Bugs:
- Pinching in the demo window also moves the window -> Pointer events must be
ignored when touch is active
- Pinching also works when doing it outside the demo window -> it would be nice
to return the touch info in the `Response` of the painter allocation
* fix comments and non-idiomatic code
* update touch state *each frame*
* change egui_demo to use *relative* touch data
* support more than two fingers
This commit includes an improved Demo Window for egui_demo, and a complete
re-write of the gesture detection. The PR should be ready for review, soon.
* cleanup code and comments for review
* minor code simplifications
* oops – forgot the changelog
* resolve comment https://github.com/emilk/egui/pull/306/files/fee8ed83dbe715b5b70433faacfe74b59c99e4a4#r623226656
* accept suggestion https://github.com/emilk/egui/pull/306#discussion_r623229228
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
* fix syntax error (dough!)
* remove `dbg!` (why didnt clippy see this?)
* apply suggested diffs from review
* fix conversion of physical location to Pos2
* remove redundanct type `TouchAverages`
* remove trailing space
* avoid initial translation jump in plot demo
* extend the demo so it shows off translation
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-05-06 19:01:10 +00:00
|
|
|
|
|
|
|
push_touches(&mut *runner_lock, egui::TouchPhase::End, &event);
|
2021-01-25 17:50:19 +00:00
|
|
|
runner_lock.needs_repaint.set_true();
|
|
|
|
event.stop_propagation();
|
|
|
|
event.prevent_default();
|
2021-03-26 12:56:26 +00:00
|
|
|
|
|
|
|
// Finally, focus or blur on agent to toggle keyboard
|
|
|
|
manipulate_agent(runner_lock.canvas_id(), runner_lock.input.latest_touch_pos);
|
2021-01-25 17:50:19 +00:00
|
|
|
}
|
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();
|
|
|
|
}
|
|
|
|
|
Basic multi touch support (issue #279) (#306)
* translate touch events from glium to egui
Unfortunately, winit does not seem to create _Touch_ events for the touch pad
on my mac. Only _TouchpadPressure_ events are sent.
Found some issues (like
[this](https://github.com/rust-windowing/winit/issues/54)), but I am not sure
what they exactly mean: Sometimes, touch events are mixed with
touch-to-pointer translation in the discussions.
* translate touch events from web_sys to egui
The are a few open topics:
- egui_web currently translates touch events into pointer events.
I guess this should change, such that egui itself performs this kind of
conversion.
- `pub fn egui_web::pos_from_touch_event` is a public function, but I
would like to change the return type to an `Option`. Shouldn't this
function be private, anyway?
* introduce `TouchState` and `Gesture`
InputState.touch was introduced with type `TouchState`, just as
InputState.pointer is of type `Pointer`.
The TouchState internally relies on a collection of `Gesture`s. This commit
provides the first rudimentary implementation of a Gesture, but has no
functionality, yet.
* add method InputState::zoom()
So far, the method always returns `None`, but it should work as soon as the
`Zoom` gesture is implemented.
* manage one `TouchState` per individual device
Although quite unlikely, it is still possible to connect more than one touch
device. (I have three touch pads connected to my MacBook in total, but
unfortunately `winit` sends touch events for none of them.)
We do not want to mix-up the touches from different devices.
* implement control loop for gesture detection
The basic idea is that each gesture can focus on detection logic and does not
have to care (too much) about managing touch state in general.
* streamline `Gesture` trait, simplifying impl's
* implement first version of Zoom gesture
* fix failing doctest
a simple `TODO` should be enough
* get rid of `Gesture`s
* Provide a Zoom/Rotate window in the demo app
For now, it works for two fingers only. The third finger interrupts the
gesture.
Bugs:
- Pinching in the demo window also moves the window -> Pointer events must be
ignored when touch is active
- Pinching also works when doing it outside the demo window -> it would be nice
to return the touch info in the `Response` of the painter allocation
* fix comments and non-idiomatic code
* update touch state *each frame*
* change egui_demo to use *relative* touch data
* support more than two fingers
This commit includes an improved Demo Window for egui_demo, and a complete
re-write of the gesture detection. The PR should be ready for review, soon.
* cleanup code and comments for review
* minor code simplifications
* oops – forgot the changelog
* resolve comment https://github.com/emilk/egui/pull/306/files/fee8ed83dbe715b5b70433faacfe74b59c99e4a4#r623226656
* accept suggestion https://github.com/emilk/egui/pull/306#discussion_r623229228
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
* fix syntax error (dough!)
* remove `dbg!` (why didnt clippy see this?)
* apply suggested diffs from review
* fix conversion of physical location to Pos2
* remove redundanct type `TouchAverages`
* remove trailing space
* avoid initial translation jump in plot demo
* extend the demo so it shows off translation
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-05-06 19:01:10 +00:00
|
|
|
{
|
|
|
|
let event_name = "touchcancel";
|
|
|
|
let runner_ref = runner_ref.clone();
|
|
|
|
let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| {
|
|
|
|
let mut runner_lock = runner_ref.0.lock();
|
|
|
|
push_touches(&mut *runner_lock, egui::TouchPhase::Cancel, &event);
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2020-07-18 16:35:17 +00:00
|
|
|
{
|
|
|
|
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();
|
2021-04-24 09:01:44 +00:00
|
|
|
|
|
|
|
let scroll_multiplier = match event.delta_mode() {
|
|
|
|
web_sys::WheelEvent::DOM_DELTA_PAGE => {
|
|
|
|
canvas_size_in_points(runner_ref.0.lock().canvas_id()).y
|
|
|
|
}
|
|
|
|
web_sys::WheelEvent::DOM_DELTA_LINE => {
|
2021-09-07 17:45:13 +00:00
|
|
|
let points_per_scroll_line = 50.0; // Scroll speed decided by consensus: https://github.com/emilk/egui/issues/461
|
|
|
|
points_per_scroll_line
|
2021-04-24 09:01:44 +00:00
|
|
|
}
|
|
|
|
_ => 1.0,
|
|
|
|
};
|
|
|
|
|
2021-04-25 15:04:34 +00:00
|
|
|
let delta = -scroll_multiplier
|
|
|
|
* egui::Vec2::new(event.delta_x() as f32, event.delta_y() as f32);
|
|
|
|
|
2021-05-20 17:56:33 +00:00
|
|
|
// Report a zoom event in case CTRL (on Windows or Linux) or CMD (on Mac) is pressed.
|
|
|
|
// This if-statement is equivalent to how `Modifiers.command` is determined in
|
|
|
|
// `modifiers_from_event()`, but we cannot directly use that fn for a `WheelEvent`.
|
|
|
|
if event.ctrl_key() || event.meta_key() {
|
2021-04-25 15:04:34 +00:00
|
|
|
runner_lock.input.raw.zoom_delta *= (delta.y / 200.0).exp();
|
|
|
|
} else {
|
|
|
|
runner_lock.input.raw.scroll_delta += delta;
|
|
|
|
}
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2021-08-20 20:20:45 +00:00
|
|
|
{
|
|
|
|
let event_name = "dragover";
|
|
|
|
let runner_ref = runner_ref.clone();
|
|
|
|
let closure = Closure::wrap(Box::new(move |event: web_sys::DragEvent| {
|
|
|
|
if let Some(data_transfer) = event.data_transfer() {
|
|
|
|
let mut runner_lock = runner_ref.0.lock();
|
|
|
|
runner_lock.input.raw.hovered_files.clear();
|
|
|
|
for i in 0..data_transfer.items().length() {
|
|
|
|
if let Some(item) = data_transfer.items().get(i) {
|
|
|
|
runner_lock.input.raw.hovered_files.push(egui::HoveredFile {
|
|
|
|
mime: item.type_(),
|
|
|
|
..Default::default()
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
runner_lock.needs_repaint.set_true();
|
|
|
|
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 = "dragleave";
|
|
|
|
let runner_ref = runner_ref.clone();
|
|
|
|
let closure = Closure::wrap(Box::new(move |event: web_sys::DragEvent| {
|
|
|
|
let mut runner_lock = runner_ref.0.lock();
|
|
|
|
runner_lock.input.raw.hovered_files.clear();
|
|
|
|
runner_lock.needs_repaint.set_true();
|
|
|
|
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 = "drop";
|
|
|
|
let runner_ref = runner_ref.clone();
|
|
|
|
let closure = Closure::wrap(Box::new(move |event: web_sys::DragEvent| {
|
|
|
|
if let Some(data_transfer) = event.data_transfer() {
|
|
|
|
{
|
|
|
|
let mut runner_lock = runner_ref.0.lock();
|
|
|
|
runner_lock.input.raw.hovered_files.clear();
|
|
|
|
runner_lock.needs_repaint.set_true();
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(files) = data_transfer.files() {
|
|
|
|
for i in 0..files.length() {
|
|
|
|
if let Some(file) = files.get(i) {
|
|
|
|
let name = file.name();
|
|
|
|
let last_modified = std::time::UNIX_EPOCH
|
|
|
|
+ std::time::Duration::from_millis(file.last_modified() as u64);
|
|
|
|
|
|
|
|
console_log(format!("Loading {:?} ({} bytes)…", name, file.size()));
|
|
|
|
|
|
|
|
let future = wasm_bindgen_futures::JsFuture::from(file.array_buffer());
|
|
|
|
|
|
|
|
let runner_ref = runner_ref.clone();
|
|
|
|
let future = async move {
|
|
|
|
match future.await {
|
|
|
|
Ok(array_buffer) => {
|
|
|
|
let bytes = js_sys::Uint8Array::new(&array_buffer).to_vec();
|
|
|
|
console_log(format!(
|
|
|
|
"Loaded {:?} ({} bytes).",
|
|
|
|
name,
|
|
|
|
bytes.len()
|
|
|
|
));
|
|
|
|
|
|
|
|
let mut runner_lock = runner_ref.0.lock();
|
|
|
|
runner_lock.input.raw.dropped_files.push(
|
|
|
|
egui::DroppedFile {
|
|
|
|
name,
|
|
|
|
last_modified: Some(last_modified),
|
|
|
|
bytes: Some(bytes.into()),
|
|
|
|
..Default::default()
|
|
|
|
},
|
|
|
|
);
|
|
|
|
runner_lock.needs_repaint.set_true();
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
console_error(format!("Failed to read file: {:?}", err));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
wasm_bindgen_futures::spawn_local(future);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2020-07-18 16:35:17 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
2021-03-26 12:56:26 +00:00
|
|
|
|
|
|
|
fn manipulate_agent(canvas_id: &str, latest_cursor: Option<egui::Pos2>) -> Option<()> {
|
|
|
|
use wasm_bindgen::JsCast;
|
|
|
|
use web_sys::HtmlInputElement;
|
|
|
|
let window = web_sys::window()?;
|
|
|
|
let document = window.document()?;
|
|
|
|
let input: HtmlInputElement = document.get_element_by_id(AGENT_ID)?.dyn_into().unwrap();
|
|
|
|
let cutsor_txt = document.body()?.style().get_property_value("cursor").ok()?;
|
|
|
|
let style = canvas_element(canvas_id)?.style();
|
|
|
|
if cutsor_txt == cursor_web_name(egui::CursorIcon::Text) {
|
|
|
|
input.set_hidden(false);
|
|
|
|
input.focus().ok()?;
|
|
|
|
// Panning canvas so that text edit is shown at 30%
|
|
|
|
// Only on touch screens, when keyboard popups
|
|
|
|
if let Some(p) = latest_cursor {
|
|
|
|
let inner_height = window.inner_height().ok()?.as_f64()? as f32;
|
|
|
|
let current_rel = p.y / inner_height;
|
|
|
|
|
|
|
|
if current_rel > 0.5 {
|
|
|
|
// probably below the keyboard
|
|
|
|
|
|
|
|
let target_rel = 0.3;
|
|
|
|
|
|
|
|
let delta = target_rel - current_rel;
|
|
|
|
let new_pos_percent = (delta * 100.0).round().to_string() + "%";
|
|
|
|
|
|
|
|
style.set_property("position", "absolute").ok()?;
|
|
|
|
style.set_property("top", &new_pos_percent).ok()?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
input.blur().ok()?;
|
|
|
|
input.set_hidden(true);
|
|
|
|
style.set_property("position", "absolute").ok()?;
|
|
|
|
style.set_property("top", "0%").ok()?; // move back to normal position
|
|
|
|
}
|
|
|
|
Some(())
|
|
|
|
}
|
2021-03-30 06:48:55 +00:00
|
|
|
|
|
|
|
const MOBILE_DEVICE: [&str; 6] = ["Android", "iPhone", "iPad", "iPod", "webOS", "BlackBerry"];
|
|
|
|
/// If context is running under mobile device?
|
|
|
|
fn is_mobile() -> Option<bool> {
|
|
|
|
let user_agent = web_sys::window()?.navigator().user_agent().ok()?;
|
|
|
|
let is_mobile = MOBILE_DEVICE.iter().any(|&name| user_agent.contains(name));
|
|
|
|
Some(is_mobile)
|
|
|
|
}
|
|
|
|
|
2021-05-20 19:58:44 +00:00
|
|
|
// Move text agent to text cursor's position, on desktop/laptop,
|
|
|
|
// candidate window moves following text element (agent),
|
2021-03-30 06:48:55 +00:00
|
|
|
// so it appears that the IME candidate window moves with text cursor.
|
|
|
|
// On mobile devices, there is no need to do that.
|
|
|
|
fn move_text_cursor(cursor: &Option<egui::Pos2>, canvas_id: &str) -> Option<()> {
|
|
|
|
let style = text_agent().style();
|
2021-05-20 19:58:44 +00:00
|
|
|
// Note: movint agent on mobile devices will lead to unpredictable scroll.
|
2021-03-30 06:48:55 +00:00
|
|
|
if is_mobile() == Some(false) {
|
|
|
|
cursor.as_ref().and_then(|&egui::Pos2 { x, y }| {
|
|
|
|
let canvas = canvas_element(canvas_id)?;
|
|
|
|
let y = y + (canvas.scroll_top() + canvas.offset_top()) as f32;
|
|
|
|
let x = x + (canvas.scroll_left() + canvas.offset_left()) as f32;
|
|
|
|
// Canvas is translated 50% horizontally in html.
|
|
|
|
let x = x - canvas.offset_width() as f32 / 2.0;
|
|
|
|
style.set_property("position", "absolute").ok()?;
|
|
|
|
style.set_property("top", &(y.to_string() + "px")).ok()?;
|
|
|
|
style.set_property("left", &(x.to_string() + "px")).ok()
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
style.set_property("position", "absolute").ok()?;
|
|
|
|
style.set_property("top", "0px").ok()?;
|
|
|
|
style.set_property("left", "0px").ok()
|
|
|
|
}
|
|
|
|
}
|