2022-04-30 08:44:35 +00:00
|
|
|
use super::{glow_wrapping::WrappedGlowPainter, *};
|
|
|
|
|
|
|
|
use crate::epi;
|
2020-07-23 16:54:16 +00:00
|
|
|
|
2022-01-15 12:59:52 +00:00
|
|
|
use egui::TexturesDelta;
|
2021-01-02 16:02:18 +00:00
|
|
|
pub use egui::{pos2, Color32};
|
2020-07-23 16:54:16 +00:00
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
/// Data gathered between frames.
|
|
|
|
#[derive(Default)]
|
|
|
|
pub struct WebInput {
|
2021-01-25 17:50:19 +00:00
|
|
|
/// Required because we don't get a position on touched
|
|
|
|
pub latest_touch_pos: Option<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
|
|
|
/// Required to maintain a stable touch position for multi-touch gestures.
|
|
|
|
pub latest_touch_pos_id: Option<egui::TouchId>,
|
|
|
|
|
2020-11-15 13:21:21 +00:00
|
|
|
pub raw: egui::RawInput,
|
2020-07-23 16:54:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl WebInput {
|
2020-12-18 21:51:23 +00:00
|
|
|
pub fn new_frame(&mut self, canvas_size: egui::Vec2) -> egui::RawInput {
|
2020-07-23 16:54:16 +00:00
|
|
|
egui::RawInput {
|
2020-12-18 21:51:23 +00:00
|
|
|
screen_rect: Some(egui::Rect::from_min_size(Default::default(), canvas_size)),
|
2021-02-21 10:23:33 +00:00
|
|
|
pixels_per_point: Some(native_pixels_per_point()), // We ALWAYS use the native pixels-per-point
|
2020-12-16 20:22:45 +00:00
|
|
|
time: Some(now_sec()),
|
2020-11-15 13:21:21 +00:00
|
|
|
..self.raw.take()
|
2020-07-23 16:54:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2020-11-20 19:35:16 +00:00
|
|
|
use std::sync::atomic::Ordering::SeqCst;
|
|
|
|
|
|
|
|
pub struct NeedRepaint(std::sync::atomic::AtomicBool);
|
|
|
|
|
|
|
|
impl Default for NeedRepaint {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self(true.into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl NeedRepaint {
|
|
|
|
pub fn fetch_and_clear(&self) -> bool {
|
|
|
|
self.0.swap(false, SeqCst)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_true(&self) {
|
|
|
|
self.0.store(true, SeqCst);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2022-02-17 15:46:43 +00:00
|
|
|
fn web_location() -> epi::Location {
|
|
|
|
let location = web_sys::window().unwrap().location();
|
|
|
|
|
|
|
|
let hash = percent_decode(&location.hash().unwrap_or_default());
|
|
|
|
|
|
|
|
let query = location
|
|
|
|
.search()
|
|
|
|
.unwrap_or_default()
|
|
|
|
.strip_prefix('?')
|
|
|
|
.map(percent_decode)
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
|
|
|
let query_map = parse_query_map(&query)
|
|
|
|
.iter()
|
2022-03-21 15:54:29 +00:00
|
|
|
.map(|(k, v)| ((*k).to_string(), (*v).to_string()))
|
2022-02-17 15:46:43 +00:00
|
|
|
.collect();
|
|
|
|
|
|
|
|
epi::Location {
|
|
|
|
url: percent_decode(&location.href().unwrap_or_default()),
|
|
|
|
protocol: percent_decode(&location.protocol().unwrap_or_default()),
|
|
|
|
host: percent_decode(&location.host().unwrap_or_default()),
|
|
|
|
hostname: percent_decode(&location.hostname().unwrap_or_default()),
|
|
|
|
port: percent_decode(&location.port().unwrap_or_default()),
|
|
|
|
hash,
|
|
|
|
query,
|
|
|
|
query_map,
|
|
|
|
origin: percent_decode(&location.origin().unwrap_or_default()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_query_map(query: &str) -> BTreeMap<&str, &str> {
|
|
|
|
query
|
|
|
|
.split('&')
|
|
|
|
.filter_map(|pair| {
|
|
|
|
if pair.is_empty() {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(if let Some((key, value)) = pair.split_once('=') {
|
|
|
|
(key, value)
|
|
|
|
} else {
|
|
|
|
(pair, "")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_query() {
|
|
|
|
assert_eq!(parse_query_map(""), BTreeMap::default());
|
|
|
|
assert_eq!(parse_query_map("foo"), BTreeMap::from_iter([("foo", "")]));
|
|
|
|
assert_eq!(
|
|
|
|
parse_query_map("foo=bar"),
|
|
|
|
BTreeMap::from_iter([("foo", "bar")])
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
parse_query_map("foo=bar&baz=42"),
|
|
|
|
BTreeMap::from_iter([("foo", "bar"), ("baz", "42")])
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
parse_query_map("foo&baz=42"),
|
|
|
|
BTreeMap::from_iter([("foo", ""), ("baz", "42")])
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
parse_query_map("foo&baz&&"),
|
|
|
|
BTreeMap::from_iter([("foo", ""), ("baz", "")])
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2020-07-23 16:54:16 +00:00
|
|
|
pub struct AppRunner {
|
2022-01-21 18:41:18 +00:00
|
|
|
pub(crate) frame: epi::Frame,
|
2022-01-10 22:13:10 +00:00
|
|
|
egui_ctx: egui::Context,
|
2022-03-14 12:25:11 +00:00
|
|
|
painter: WrappedGlowPainter,
|
2020-12-31 13:31:11 +00:00
|
|
|
pub(crate) input: WebInput,
|
|
|
|
app: Box<dyn epi::App>,
|
|
|
|
pub(crate) needs_repaint: std::sync::Arc<NeedRepaint>,
|
|
|
|
last_save_time: f64,
|
2022-04-30 08:44:35 +00:00
|
|
|
screen_reader: super::screen_reader::ScreenReader,
|
2021-10-23 14:23:29 +00:00
|
|
|
pub(crate) text_cursor_pos: Option<egui::Pos2>,
|
|
|
|
pub(crate) mutable_text_under_cursor: bool,
|
2022-01-15 12:59:52 +00:00
|
|
|
textures_delta: TexturesDelta,
|
2020-07-23 16:54:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl AppRunner {
|
2022-06-09 15:41:59 +00:00
|
|
|
pub fn new(
|
|
|
|
canvas_id: &str,
|
|
|
|
web_options: crate::WebOptions,
|
|
|
|
app_creator: epi::AppCreator,
|
|
|
|
) -> Result<Self, JsValue> {
|
2022-04-30 15:16:50 +00:00
|
|
|
let painter = WrappedGlowPainter::new(canvas_id).map_err(JsValue::from)?; // fail early
|
2021-06-07 18:56:18 +00:00
|
|
|
|
2022-06-09 15:41:59 +00:00
|
|
|
let system_theme = if web_options.follow_system_theme {
|
|
|
|
super::system_theme()
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2021-06-07 18:56:18 +00:00
|
|
|
|
2022-04-30 15:16:50 +00:00
|
|
|
let info = epi::IntegrationInfo {
|
|
|
|
web_info: Some(epi::WebInfo {
|
|
|
|
location: web_location(),
|
|
|
|
}),
|
2022-06-09 15:41:59 +00:00
|
|
|
system_theme,
|
2022-04-30 15:16:50 +00:00
|
|
|
cpu_usage: None,
|
|
|
|
native_pixels_per_point: Some(native_pixels_per_point()),
|
2022-05-13 08:15:43 +00:00
|
|
|
window_info: None,
|
2022-03-25 20:19:31 +00:00
|
|
|
};
|
2022-04-30 15:16:50 +00:00
|
|
|
let storage = LocalStorage::default();
|
2022-03-15 16:21:52 +00:00
|
|
|
|
2022-01-10 22:13:10 +00:00
|
|
|
let egui_ctx = egui::Context::default();
|
2021-12-26 20:21:28 +00:00
|
|
|
load_memory(&egui_ctx);
|
2022-06-09 15:41:59 +00:00
|
|
|
|
|
|
|
let theme = system_theme.unwrap_or(web_options.default_theme);
|
|
|
|
egui_ctx.set_visuals(theme.egui_visuals());
|
2021-06-07 18:56:18 +00:00
|
|
|
|
2022-03-16 14:39:48 +00:00
|
|
|
let app = app_creator(&epi::CreationContext {
|
|
|
|
egui_ctx: egui_ctx.clone(),
|
2022-04-30 15:16:50 +00:00
|
|
|
integration_info: info.clone(),
|
|
|
|
storage: Some(&storage),
|
2022-05-12 07:02:28 +00:00
|
|
|
#[cfg(feature = "glow")]
|
|
|
|
gl: Some(painter.painter.gl().clone()),
|
2022-05-28 15:52:36 +00:00
|
|
|
#[cfg(feature = "wgpu")]
|
|
|
|
render_state: None,
|
2022-03-16 14:39:48 +00:00
|
|
|
});
|
|
|
|
|
2022-04-30 15:16:50 +00:00
|
|
|
let frame = epi::Frame {
|
|
|
|
info,
|
|
|
|
output: Default::default(),
|
|
|
|
storage: Some(Box::new(storage)),
|
2022-05-12 07:02:28 +00:00
|
|
|
#[cfg(feature = "glow")]
|
|
|
|
gl: Some(painter.gl().clone()),
|
2022-05-28 15:52:36 +00:00
|
|
|
#[cfg(feature = "wgpu")]
|
|
|
|
render_state: None,
|
2022-04-30 15:16:50 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let needs_repaint: std::sync::Arc<NeedRepaint> = Default::default();
|
|
|
|
{
|
|
|
|
let needs_repaint = needs_repaint.clone();
|
|
|
|
egui_ctx.set_request_repaint_callback(move || {
|
|
|
|
needs_repaint.0.store(true, SeqCst);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-06-07 18:53:33 +00:00
|
|
|
let mut runner = Self {
|
2021-12-26 20:21:28 +00:00
|
|
|
frame,
|
2021-11-03 12:45:51 +00:00
|
|
|
egui_ctx,
|
2021-12-26 20:21:28 +00:00
|
|
|
painter,
|
2020-11-15 13:21:21 +00:00
|
|
|
input: Default::default(),
|
2020-07-23 16:54:16 +00:00
|
|
|
app,
|
2021-12-26 20:21:28 +00:00
|
|
|
needs_repaint,
|
2020-12-19 19:50:00 +00:00
|
|
|
last_save_time: now_sec(),
|
2021-03-08 19:58:01 +00:00
|
|
|
screen_reader: Default::default(),
|
2021-10-23 14:23:29 +00:00
|
|
|
text_cursor_pos: None,
|
|
|
|
mutable_text_under_cursor: false,
|
2022-01-15 12:59:52 +00:00
|
|
|
textures_delta: Default::default(),
|
2021-06-07 18:53:33 +00:00
|
|
|
};
|
|
|
|
|
2022-02-22 12:21:28 +00:00
|
|
|
runner.input.raw.max_texture_side = Some(runner.painter.max_texture_side());
|
2022-01-24 13:32:36 +00:00
|
|
|
|
2021-06-07 18:53:33 +00:00
|
|
|
Ok(runner)
|
2020-07-23 16:54:16 +00:00
|
|
|
}
|
|
|
|
|
2022-01-10 22:13:10 +00:00
|
|
|
pub fn egui_ctx(&self) -> &egui::Context {
|
2021-11-03 12:45:51 +00:00
|
|
|
&self.egui_ctx
|
2021-01-17 11:22:19 +00:00
|
|
|
}
|
|
|
|
|
2020-12-19 19:50:00 +00:00
|
|
|
pub fn auto_save(&mut self) {
|
|
|
|
let now = now_sec();
|
|
|
|
let time_since_last_save = now - self.last_save_time;
|
|
|
|
|
|
|
|
if time_since_last_save > self.app.auto_save_interval().as_secs_f64() {
|
2021-09-29 06:45:13 +00:00
|
|
|
if self.app.persist_egui_memory() {
|
2021-11-03 12:45:51 +00:00
|
|
|
save_memory(&self.egui_ctx);
|
2021-09-29 06:45:13 +00:00
|
|
|
}
|
2022-03-25 20:19:31 +00:00
|
|
|
if let Some(storage) = self.frame.storage_mut() {
|
|
|
|
self.app.save(storage);
|
|
|
|
}
|
2020-12-19 19:50:00 +00:00
|
|
|
self.last_save_time = now;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-23 16:54:16 +00:00
|
|
|
pub fn canvas_id(&self) -> &str {
|
2021-11-03 12:45:51 +00:00
|
|
|
self.painter.canvas_id()
|
2020-07-23 16:54:16 +00:00
|
|
|
}
|
|
|
|
|
2021-01-02 13:42:43 +00:00
|
|
|
pub fn warm_up(&mut self) -> Result<(), JsValue> {
|
|
|
|
if self.app.warm_up_enabled() {
|
2022-01-10 22:13:10 +00:00
|
|
|
let saved_memory: egui::Memory = self.egui_ctx.memory().clone();
|
2021-11-03 12:45:51 +00:00
|
|
|
self.egui_ctx.memory().set_everything_is_visible(true);
|
2021-01-02 13:42:43 +00:00
|
|
|
self.logic()?;
|
2021-11-03 12:45:51 +00:00
|
|
|
*self.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge.
|
|
|
|
self.egui_ctx.clear_animations();
|
2021-01-02 13:42:43 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-01-15 12:59:52 +00:00
|
|
|
/// Returns `true` if egui requests a repaint.
|
|
|
|
///
|
|
|
|
/// Call [`Self::paint`] later to paint
|
2022-03-14 12:25:11 +00:00
|
|
|
pub fn logic(&mut self) -> Result<(bool, Vec<egui::ClippedPrimitive>), JsValue> {
|
2021-11-03 12:45:51 +00:00
|
|
|
let frame_start = now_sec();
|
|
|
|
|
|
|
|
resize_canvas_to_screen_size(self.canvas_id(), self.app.max_size_points());
|
|
|
|
let canvas_size = canvas_size_in_points(self.canvas_id());
|
2021-06-07 18:53:33 +00:00
|
|
|
let raw_input = self.input.new_frame(canvas_size);
|
2021-04-01 20:11:29 +00:00
|
|
|
|
2022-02-22 16:13:53 +00:00
|
|
|
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
|
2022-03-25 20:19:31 +00:00
|
|
|
self.app.update(egui_ctx, &mut self.frame);
|
2021-11-03 19:11:25 +00:00
|
|
|
});
|
2022-02-22 16:13:53 +00:00
|
|
|
let egui::FullOutput {
|
|
|
|
platform_output,
|
|
|
|
needs_repaint,
|
|
|
|
textures_delta,
|
|
|
|
shapes,
|
|
|
|
} = full_output;
|
2021-11-03 12:45:51 +00:00
|
|
|
|
2022-02-22 16:13:53 +00:00
|
|
|
self.handle_platform_output(platform_output);
|
2022-01-15 12:59:52 +00:00
|
|
|
self.textures_delta.append(textures_delta);
|
2022-03-14 12:25:11 +00:00
|
|
|
let clipped_primitives = self.egui_ctx.tessellate(shapes);
|
2020-10-17 10:33:30 +00:00
|
|
|
|
|
|
|
{
|
2021-12-26 20:21:28 +00:00
|
|
|
let app_output = self.frame.take_app_output();
|
2020-12-31 13:31:11 +00:00
|
|
|
let epi::backend::AppOutput {
|
2021-11-03 12:45:51 +00:00
|
|
|
quit: _, // Can't quit a web page
|
|
|
|
window_size: _, // Can't resize a web page
|
2022-05-21 14:53:25 +00:00
|
|
|
window_title: _, // TODO(emilk): change title of window
|
2021-11-03 12:45:51 +00:00
|
|
|
decorated: _, // Can't toggle decorations
|
|
|
|
drag_window: _, // Can't be dragged
|
2022-04-16 20:27:22 +00:00
|
|
|
window_pos: _, // Can't set position of a web page
|
2020-10-17 21:54:46 +00:00
|
|
|
} = app_output;
|
2020-10-17 10:33:30 +00:00
|
|
|
}
|
|
|
|
|
2022-03-25 20:19:31 +00:00
|
|
|
self.frame.info.cpu_usage = Some((now_sec() - frame_start) as f32);
|
2022-03-14 12:25:11 +00:00
|
|
|
Ok((needs_repaint, clipped_primitives))
|
2020-07-23 16:54:16 +00:00
|
|
|
}
|
|
|
|
|
2022-03-27 13:20:45 +00:00
|
|
|
pub fn clear_color_buffer(&self) {
|
2022-04-30 13:41:43 +00:00
|
|
|
self.painter
|
|
|
|
.clear(self.app.clear_color(&self.egui_ctx.style().visuals));
|
2022-03-27 13:20:45 +00:00
|
|
|
}
|
|
|
|
|
2022-01-15 12:59:52 +00:00
|
|
|
/// Paint the results of the last call to [`Self::logic`].
|
2022-03-14 12:25:11 +00:00
|
|
|
pub fn paint(&mut self, clipped_primitives: &[egui::ClippedPrimitive]) -> Result<(), JsValue> {
|
2022-01-15 12:59:52 +00:00
|
|
|
let textures_delta = std::mem::take(&mut self.textures_delta);
|
|
|
|
|
2022-02-21 20:49:52 +00:00
|
|
|
self.painter.paint_and_update_textures(
|
2022-03-14 12:25:11 +00:00
|
|
|
clipped_primitives,
|
2022-02-21 20:49:52 +00:00
|
|
|
self.egui_ctx.pixels_per_point(),
|
|
|
|
&textures_delta,
|
|
|
|
)?;
|
2022-01-15 12:59:52 +00:00
|
|
|
|
2021-12-26 20:21:28 +00:00
|
|
|
Ok(())
|
2021-11-03 12:45:51 +00:00
|
|
|
}
|
|
|
|
|
2022-02-22 16:13:53 +00:00
|
|
|
fn handle_platform_output(&mut self, platform_output: egui::PlatformOutput) {
|
2022-01-29 16:53:41 +00:00
|
|
|
if self.egui_ctx.options().screen_reader {
|
2022-02-22 16:13:53 +00:00
|
|
|
self.screen_reader
|
|
|
|
.speak(&platform_output.events_description());
|
2021-11-03 12:45:51 +00:00
|
|
|
}
|
|
|
|
|
2022-02-22 16:13:53 +00:00
|
|
|
let egui::PlatformOutput {
|
2021-11-03 12:45:51 +00:00
|
|
|
cursor_icon,
|
|
|
|
open_url,
|
|
|
|
copied_text,
|
2022-02-22 16:13:53 +00:00
|
|
|
events: _, // already handled
|
2021-11-03 12:45:51 +00:00
|
|
|
mutable_text_under_cursor,
|
|
|
|
text_cursor_pos,
|
2022-02-22 16:13:53 +00:00
|
|
|
} = platform_output;
|
2021-11-03 12:45:51 +00:00
|
|
|
|
2022-01-15 12:59:52 +00:00
|
|
|
set_cursor_icon(cursor_icon);
|
2021-11-03 12:45:51 +00:00
|
|
|
if let Some(open) = open_url {
|
2022-04-30 08:44:35 +00:00
|
|
|
super::open_url(&open.url, open.new_tab);
|
2021-11-03 12:45:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(web_sys_unstable_apis)]
|
|
|
|
if !copied_text.is_empty() {
|
2022-01-15 12:59:52 +00:00
|
|
|
set_clipboard_text(&copied_text);
|
2021-11-03 12:45:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(not(web_sys_unstable_apis))]
|
|
|
|
let _ = copied_text;
|
|
|
|
|
2022-01-15 12:59:52 +00:00
|
|
|
self.mutable_text_under_cursor = mutable_text_under_cursor;
|
2021-11-03 12:45:51 +00:00
|
|
|
|
2022-01-15 12:59:52 +00:00
|
|
|
if self.text_cursor_pos != text_cursor_pos {
|
2022-02-21 08:23:02 +00:00
|
|
|
text_agent::move_text_cursor(text_cursor_pos, self.canvas_id());
|
2022-01-15 12:59:52 +00:00
|
|
|
self.text_cursor_pos = text_cursor_pos;
|
2021-11-03 12:45:51 +00:00
|
|
|
}
|
2020-07-23 16:54:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-29 06:17:49 +00:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
pub type AppRunnerRef = Arc<Mutex<AppRunner>>;
|
|
|
|
|
|
|
|
pub struct AppRunnerContainer {
|
|
|
|
pub runner: AppRunnerRef,
|
|
|
|
/// Set to `true` if there is a panic.
|
|
|
|
/// Used to ignore callbacks after a panic.
|
|
|
|
pub panicked: Arc<AtomicBool>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl AppRunnerContainer {
|
|
|
|
/// Convenience function to reduce boilerplate and ensure that all event handlers
|
|
|
|
/// are dealt with in the same way
|
|
|
|
pub fn add_event_listener<E: wasm_bindgen::JsCast>(
|
|
|
|
&self,
|
|
|
|
target: &EventTarget,
|
|
|
|
event_name: &'static str,
|
|
|
|
mut closure: impl FnMut(E, MutexGuard<'_, AppRunner>) + 'static,
|
|
|
|
) -> Result<(), JsValue> {
|
|
|
|
use wasm_bindgen::JsCast;
|
|
|
|
|
|
|
|
// Create a JS closure based on the FnMut provided
|
|
|
|
let closure = Closure::wrap({
|
|
|
|
// Clone atomics
|
|
|
|
let runner_ref = self.runner.clone();
|
|
|
|
let panicked = self.panicked.clone();
|
|
|
|
|
|
|
|
Box::new(move |event: web_sys::Event| {
|
|
|
|
// Only call the wrapped closure if the egui code has not panicked
|
|
|
|
if !panicked.load(Ordering::SeqCst) {
|
|
|
|
// Cast the event to the expected event type
|
|
|
|
let event = event.unchecked_into::<E>();
|
|
|
|
|
|
|
|
closure(event, runner_ref.lock());
|
|
|
|
}
|
|
|
|
}) as Box<dyn FnMut(_)>
|
|
|
|
});
|
|
|
|
|
|
|
|
// Add the event listener to the target
|
|
|
|
target.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
|
|
|
|
|
|
|
|
// Bypass closure drop so that event handler can call the closure
|
|
|
|
closure.forget();
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2020-12-19 20:15:07 +00:00
|
|
|
/// Install event listeners to register different input events
|
2020-12-29 14:57:13 +00:00
|
|
|
/// and start running the given app.
|
2022-06-09 15:41:59 +00:00
|
|
|
pub fn start(
|
|
|
|
canvas_id: &str,
|
|
|
|
web_options: crate::WebOptions,
|
|
|
|
app_creator: epi::AppCreator,
|
|
|
|
) -> Result<AppRunnerRef, JsValue> {
|
|
|
|
let mut runner = AppRunner::new(canvas_id, web_options, app_creator)?;
|
2021-01-02 13:42:43 +00:00
|
|
|
runner.warm_up()?;
|
2020-12-19 20:15:07 +00:00
|
|
|
start_runner(runner)
|
|
|
|
}
|
|
|
|
|
2020-07-23 16:54:16 +00:00
|
|
|
/// Install event listeners to register different input events
|
2022-04-03 16:18:35 +00:00
|
|
|
/// and starts running the given [`AppRunner`].
|
2020-12-19 20:15:07 +00:00
|
|
|
fn start_runner(app_runner: AppRunner) -> Result<AppRunnerRef, JsValue> {
|
2022-03-10 07:13:32 +00:00
|
|
|
let runner_container = AppRunnerContainer {
|
|
|
|
runner: Arc::new(Mutex::new(app_runner)),
|
|
|
|
panicked: Arc::new(AtomicBool::new(false)),
|
|
|
|
};
|
|
|
|
|
2022-04-30 08:44:35 +00:00
|
|
|
super::events::install_canvas_events(&runner_container)?;
|
|
|
|
super::events::install_document_events(&runner_container)?;
|
2022-03-10 07:13:32 +00:00
|
|
|
text_agent::install_text_agent(&runner_container)?;
|
2022-05-21 14:53:25 +00:00
|
|
|
super::events::repaint_every_ms(&runner_container, 1000)?; // just in case. TODO(emilk): make it a parameter
|
2022-03-10 07:13:32 +00:00
|
|
|
|
2022-04-30 08:44:35 +00:00
|
|
|
super::events::paint_and_schedule(&runner_container.runner, runner_container.panicked.clone())?;
|
2022-03-10 07:13:32 +00:00
|
|
|
|
|
|
|
// Disable all event handlers on panic
|
2022-04-30 13:47:34 +00:00
|
|
|
let previous_hook = std::panic::take_hook();
|
|
|
|
let panicked = runner_container.panicked;
|
|
|
|
std::panic::set_hook(Box::new(move |panic_info| {
|
|
|
|
tracing::info!("egui disabled all event handlers due to panic");
|
|
|
|
panicked.store(true, SeqCst);
|
|
|
|
|
|
|
|
// Propagate panic info to the previously registered panic hook
|
|
|
|
previous_hook(panic_info);
|
2022-03-10 07:13:32 +00:00
|
|
|
}));
|
|
|
|
|
|
|
|
Ok(runner_container.runner)
|
2020-07-23 16:54:16 +00:00
|
|
|
}
|
2022-04-29 06:17:49 +00:00
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
struct LocalStorage {}
|
|
|
|
|
|
|
|
impl epi::Storage for LocalStorage {
|
|
|
|
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) {}
|
|
|
|
}
|