Refactor: move things into eframe (#1542)
* Move all epi-related code from egui_glow into eframe * Move epi stuff from egui-winit into eframe * Remove mention of epi in egui * Remove mention of epi in egui_glium * Remove trait epi::NativeTexture * Remove confusing mentions of epi * Refactor egui_web: break up into smaller files * Clean up feature flags further, and update changelogs * Clean up check.sh * Small cleanup of egui_web/Cargo.toml * Fix dependencies for pure_glow example * Fix clippy false positive
This commit is contained in:
parent
5ea51c3f0b
commit
ed002acc68
27 changed files with 797 additions and 803 deletions
9
Cargo.lock
generated
9
Cargo.lock
generated
|
@ -1075,11 +1075,16 @@ checksum = "6907e25393cdcc1f4f3f513d9aac1e840eb1cc341a0fccb01171f7d14d10b946"
|
||||||
name = "eframe"
|
name = "eframe"
|
||||||
version = "0.17.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"dark-light",
|
||||||
"egui",
|
"egui",
|
||||||
"egui-winit",
|
"egui-winit",
|
||||||
"egui_glow",
|
"egui_glow",
|
||||||
"egui_web",
|
"egui_web",
|
||||||
"epi",
|
"epi",
|
||||||
|
"glow",
|
||||||
|
"glutin",
|
||||||
|
"puffin",
|
||||||
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1099,10 +1104,7 @@ name = "egui-winit"
|
||||||
version = "0.17.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arboard",
|
"arboard",
|
||||||
"dark-light",
|
|
||||||
"egui",
|
"egui",
|
||||||
"epi",
|
|
||||||
"glow",
|
|
||||||
"instant",
|
"instant",
|
||||||
"puffin",
|
"puffin",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -1178,7 +1180,6 @@ dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"egui",
|
"egui",
|
||||||
"egui-winit",
|
"egui-winit",
|
||||||
"epi",
|
|
||||||
"glow",
|
"glow",
|
||||||
"glutin",
|
"glutin",
|
||||||
"memoffset",
|
"memoffset",
|
||||||
|
|
|
@ -19,9 +19,9 @@ NOTE: [`egui_web`](../egui_web/CHANGELOG.md), [`egui-winit`](../egui-winit/CHANG
|
||||||
* Changed `App::update` to take `&mut Frame` instead of `&Frame`.
|
* Changed `App::update` to take `&mut Frame` instead of `&Frame`.
|
||||||
* `Frame` is no longer `Clone` or `Sync`.
|
* `Frame` is no longer `Clone` or `Sync`.
|
||||||
* Add `glow` (OpenGL) context to `Frame` ([#1425](https://github.com/emilk/egui/pull/1425)).
|
* Add `glow` (OpenGL) context to `Frame` ([#1425](https://github.com/emilk/egui/pull/1425)).
|
||||||
* `dark-light` (dark mode detection) is now an opt-in feature ([#1437](https://github.com/emilk/egui/pull/1437)).
|
|
||||||
* Fixed potential scale bug when DPI scaling changes (e.g. when dragging a window between different displays) ([#1441](https://github.com/emilk/egui/pull/1441)).
|
* Fixed potential scale bug when DPI scaling changes (e.g. when dragging a window between different displays) ([#1441](https://github.com/emilk/egui/pull/1441)).
|
||||||
* MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)).
|
* MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)).
|
||||||
|
* `dark-light` (dark mode detection) is now an opt-in feature ([#1437](https://github.com/emilk/egui/pull/1437)).
|
||||||
* Added new feature `puffin` to add [`puffin profiler`](https://github.com/EmbarkStudios/puffin) scopes ([#1483](https://github.com/emilk/egui/pull/1483)).
|
* Added new feature `puffin` to add [`puffin profiler`](https://github.com/EmbarkStudios/puffin) scopes ([#1483](https://github.com/emilk/egui/pull/1483)).
|
||||||
* Moved app persistence to a background thread, allowing for smoother frame rates (on native).
|
* Moved app persistence to a background thread, allowing for smoother frame rates (on native).
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ all-features = true
|
||||||
default = ["default_fonts"]
|
default = ["default_fonts"]
|
||||||
|
|
||||||
# detect dark mode system preference
|
# detect dark mode system preference
|
||||||
dark-light = ["egui-winit/dark-light"]
|
dark-light = ["dep:dark-light"]
|
||||||
|
|
||||||
# If set, egui will use `include_bytes!` to bundle some fonts.
|
# If set, egui will use `include_bytes!` to bundle some fonts.
|
||||||
# If you plan on specifying your own fonts you may disable this feature.
|
# If you plan on specifying your own fonts you may disable this feature.
|
||||||
|
@ -31,8 +31,7 @@ default_fonts = ["egui/default_fonts"]
|
||||||
|
|
||||||
# Enable saving app state to disk.
|
# Enable saving app state to disk.
|
||||||
persistence = [
|
persistence = [
|
||||||
"egui-winit/persistence",
|
"egui-winit/serde",
|
||||||
"egui_glow/persistence",
|
|
||||||
"egui/persistence",
|
"egui/persistence",
|
||||||
"epi/persistence",
|
"epi/persistence",
|
||||||
]
|
]
|
||||||
|
@ -40,12 +39,11 @@ persistence = [
|
||||||
# Enable profiling with the puffin crate: https://github.com/EmbarkStudios/puffin
|
# Enable profiling with the puffin crate: https://github.com/EmbarkStudios/puffin
|
||||||
# Only enabled on native, because of the low resolution (1ms) of time keeping in browsers.
|
# Only enabled on native, because of the low resolution (1ms) of time keeping in browsers.
|
||||||
# eframe will call `puffin::GlobalProfiler::lock().new_frame()` for you
|
# eframe will call `puffin::GlobalProfiler::lock().new_frame()` for you
|
||||||
puffin = ["egui_glow/puffin"]
|
puffin = ["dep:puffin", "egui_glow/puffin"]
|
||||||
|
|
||||||
# enable screen reader support (requires `ctx.options().screen_reader = true;`)
|
# enable screen reader support (requires `ctx.options().screen_reader = true;`)
|
||||||
screen_reader = [
|
screen_reader = [
|
||||||
"egui-winit/screen_reader",
|
"egui-winit/screen_reader",
|
||||||
"egui_glow/screen_reader",
|
|
||||||
"egui_web/screen_reader",
|
"egui_web/screen_reader",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -58,11 +56,14 @@ epi = { version = "0.17.0", path = "../epi" }
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
egui_glow = { version = "0.17.0", path = "../egui_glow", default-features = false, features = [
|
egui_glow = { version = "0.17.0", path = "../egui_glow", default-features = false, features = [
|
||||||
"clipboard",
|
"clipboard",
|
||||||
"epi",
|
|
||||||
"links",
|
"links",
|
||||||
"winit",
|
|
||||||
] }
|
] }
|
||||||
egui-winit = { version = "0.17.0", path = "../egui-winit", default-features = false }
|
dark-light = { version = "0.2.1", optional = true }
|
||||||
|
egui-winit = { version = "0.17.0", path = "../egui-winit", default-features = false, features = ["clipboard", "links"] }
|
||||||
|
glow = "0.11"
|
||||||
|
glutin = { version = "0.28.0" }
|
||||||
|
puffin = { version = "0.13", optional = true }
|
||||||
|
winit = "0.26.1"
|
||||||
|
|
||||||
# web:
|
# web:
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
|
|
@ -97,6 +97,9 @@ pub fn start_web(canvas_id: &str, app_creator: AppCreator) -> Result<(), wasm_bi
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// When compiling natively
|
// When compiling natively
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
mod native;
|
||||||
|
|
||||||
/// This is how you start a native (desktop) app.
|
/// This is how you start a native (desktop) app.
|
||||||
///
|
///
|
||||||
/// The first argument is name of your app, used for the title bar of the native window
|
/// The first argument is name of your app, used for the title bar of the native window
|
||||||
|
@ -135,5 +138,29 @@ pub fn start_web(canvas_id: &str, app_creator: AppCreator) -> Result<(), wasm_bi
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
pub fn run_native(app_name: &str, native_options: NativeOptions, app_creator: AppCreator) -> ! {
|
pub fn run_native(app_name: &str, native_options: NativeOptions, app_creator: AppCreator) -> ! {
|
||||||
egui_glow::run(app_name, &native_options, app_creator)
|
native::run(app_name, &native_options, app_creator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Profiling macro for feature "puffin"
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
macro_rules! profile_function {
|
||||||
|
($($arg: tt)*) => {
|
||||||
|
#[cfg(feature = "puffin")]
|
||||||
|
puffin::profile_function!($($arg)*);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub(crate) use profile_function;
|
||||||
|
|
||||||
|
/// Profiling macro for feature "puffin"
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
macro_rules! profile_scope {
|
||||||
|
($($arg: tt)*) => {
|
||||||
|
#[cfg(feature = "puffin")]
|
||||||
|
puffin::profile_scope!($($arg)*);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub(crate) use profile_scope;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use egui_winit::{native_pixels_per_point, WindowSettings};
|
||||||
|
|
||||||
pub fn points_to_size(points: egui::Vec2) -> winit::dpi::LogicalSize<f64> {
|
pub fn points_to_size(points: egui::Vec2) -> winit::dpi::LogicalSize<f64> {
|
||||||
winit::dpi::LogicalSize {
|
winit::dpi::LogicalSize {
|
||||||
width: points.x as f64,
|
width: points.x as f64,
|
||||||
|
@ -7,7 +9,7 @@ pub fn points_to_size(points: egui::Vec2) -> winit::dpi::LogicalSize<f64> {
|
||||||
|
|
||||||
pub fn window_builder(
|
pub fn window_builder(
|
||||||
native_options: &epi::NativeOptions,
|
native_options: &epi::NativeOptions,
|
||||||
window_settings: &Option<crate::WindowSettings>,
|
window_settings: &Option<WindowSettings>,
|
||||||
) -> winit::window::WindowBuilder {
|
) -> winit::window::WindowBuilder {
|
||||||
let epi::NativeOptions {
|
let epi::NativeOptions {
|
||||||
always_on_top,
|
always_on_top,
|
||||||
|
@ -109,7 +111,7 @@ pub fn handle_app_output(
|
||||||
width: (current_pixels_per_point * window_size.x).round(),
|
width: (current_pixels_per_point * window_size.x).round(),
|
||||||
height: (current_pixels_per_point * window_size.y).round(),
|
height: (current_pixels_per_point * window_size.y).round(),
|
||||||
}
|
}
|
||||||
.to_logical::<f32>(crate::native_pixels_per_point(window) as f64),
|
.to_logical::<f32>(native_pixels_per_point(window) as f64),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,10 +147,10 @@ pub fn create_storage(_app_name: &str) -> Option<Box<dyn epi::Storage>> {
|
||||||
/// Everything needed to make a winit-based integration for [`epi`].
|
/// Everything needed to make a winit-based integration for [`epi`].
|
||||||
pub struct EpiIntegration {
|
pub struct EpiIntegration {
|
||||||
pub frame: epi::Frame,
|
pub frame: epi::Frame,
|
||||||
last_auto_save: instant::Instant,
|
last_auto_save: std::time::Instant,
|
||||||
pub egui_ctx: egui::Context,
|
pub egui_ctx: egui::Context,
|
||||||
pending_full_output: egui::FullOutput,
|
pending_full_output: egui::FullOutput,
|
||||||
egui_winit: crate::State,
|
egui_winit: egui_winit::State,
|
||||||
/// When set, it is time to quit
|
/// When set, it is time to quit
|
||||||
quit: bool,
|
quit: bool,
|
||||||
can_drag_window: bool,
|
can_drag_window: bool,
|
||||||
|
@ -174,7 +176,7 @@ impl EpiIntegration {
|
||||||
web_info: None,
|
web_info: None,
|
||||||
prefer_dark_mode,
|
prefer_dark_mode,
|
||||||
cpu_usage: None,
|
cpu_usage: None,
|
||||||
native_pixels_per_point: Some(crate::native_pixels_per_point(window)),
|
native_pixels_per_point: Some(native_pixels_per_point(window)),
|
||||||
},
|
},
|
||||||
output: Default::default(),
|
output: Default::default(),
|
||||||
storage,
|
storage,
|
||||||
|
@ -189,9 +191,9 @@ impl EpiIntegration {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
frame,
|
frame,
|
||||||
last_auto_save: instant::Instant::now(),
|
last_auto_save: std::time::Instant::now(),
|
||||||
egui_ctx,
|
egui_ctx,
|
||||||
egui_winit: crate::State::new(max_texture_side, window),
|
egui_winit: egui_winit::State::new(max_texture_side, window),
|
||||||
pending_full_output: Default::default(),
|
pending_full_output: Default::default(),
|
||||||
quit: false,
|
quit: false,
|
||||||
can_drag_window: false,
|
can_drag_window: false,
|
||||||
|
@ -235,7 +237,7 @@ impl EpiIntegration {
|
||||||
app: &mut dyn epi::App,
|
app: &mut dyn epi::App,
|
||||||
window: &winit::window::Window,
|
window: &winit::window::Window,
|
||||||
) -> egui::FullOutput {
|
) -> egui::FullOutput {
|
||||||
let frame_start = instant::Instant::now();
|
let frame_start = std::time::Instant::now();
|
||||||
|
|
||||||
let raw_input = self.egui_winit.take_egui_input(window);
|
let raw_input = self.egui_winit.take_egui_input(window);
|
||||||
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
|
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
|
||||||
|
@ -252,10 +254,10 @@ impl EpiIntegration {
|
||||||
if app_output.quit {
|
if app_output.quit {
|
||||||
self.quit = app.on_exit_event();
|
self.quit = app.on_exit_event();
|
||||||
}
|
}
|
||||||
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
||||||
}
|
}
|
||||||
|
|
||||||
let frame_time = (instant::Instant::now() - frame_start).as_secs_f64() as f32;
|
let frame_time = (std::time::Instant::now() - frame_start).as_secs_f64() as f32;
|
||||||
self.frame.info.cpu_usage = Some(frame_time);
|
self.frame.info.cpu_usage = Some(frame_time);
|
||||||
|
|
||||||
full_output
|
full_output
|
||||||
|
@ -274,7 +276,7 @@ impl EpiIntegration {
|
||||||
// Persistance stuff:
|
// Persistance stuff:
|
||||||
|
|
||||||
pub fn maybe_autosave(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
|
pub fn maybe_autosave(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
|
||||||
let now = instant::Instant::now();
|
let now = std::time::Instant::now();
|
||||||
if now - self.last_auto_save > app.auto_save_interval() {
|
if now - self.last_auto_save > app.auto_save_interval() {
|
||||||
self.save(app, window);
|
self.save(app, window);
|
||||||
self.last_auto_save = now;
|
self.last_auto_save = now;
|
||||||
|
@ -291,7 +293,7 @@ impl EpiIntegration {
|
||||||
epi::set_value(
|
epi::set_value(
|
||||||
storage,
|
storage,
|
||||||
STORAGE_WINDOW_KEY,
|
STORAGE_WINDOW_KEY,
|
||||||
&crate::WindowSettings::from_display(_window),
|
&WindowSettings::from_display(_window),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if _app.persist_egui_memory() {
|
if _app.persist_egui_memory() {
|
||||||
|
@ -314,7 +316,7 @@ const STORAGE_EGUI_MEMORY_KEY: &str = "egui";
|
||||||
#[cfg(feature = "persistence")]
|
#[cfg(feature = "persistence")]
|
||||||
const STORAGE_WINDOW_KEY: &str = "window";
|
const STORAGE_WINDOW_KEY: &str = "window";
|
||||||
|
|
||||||
pub fn load_window_settings(_storage: Option<&dyn epi::Storage>) -> Option<crate::WindowSettings> {
|
pub fn load_window_settings(_storage: Option<&dyn epi::Storage>) -> Option<WindowSettings> {
|
||||||
#[cfg(feature = "persistence")]
|
#[cfg(feature = "persistence")]
|
||||||
{
|
{
|
||||||
epi::get_value(_storage?, STORAGE_WINDOW_KEY)
|
epi::get_value(_storage?, STORAGE_WINDOW_KEY)
|
4
eframe/src/native/mod.rs
Normal file
4
eframe/src/native/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
mod epi_integration;
|
||||||
|
mod run;
|
||||||
|
|
||||||
|
pub use run::run;
|
|
@ -1,3 +1,4 @@
|
||||||
|
use super::epi_integration;
|
||||||
use egui_winit::winit;
|
use egui_winit::winit;
|
||||||
|
|
||||||
struct RequestRepaintEvent;
|
struct RequestRepaintEvent;
|
||||||
|
@ -37,18 +38,18 @@ pub use epi::NativeOptions;
|
||||||
/// Run an egui app
|
/// Run an egui app
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi::AppCreator) -> ! {
|
pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi::AppCreator) -> ! {
|
||||||
let storage = egui_winit::epi::create_storage(app_name);
|
let storage = epi_integration::create_storage(app_name);
|
||||||
let window_settings = egui_winit::epi::load_window_settings(storage.as_deref());
|
let window_settings = epi_integration::load_window_settings(storage.as_deref());
|
||||||
let window_builder =
|
let window_builder =
|
||||||
egui_winit::epi::window_builder(native_options, &window_settings).with_title(app_name);
|
epi_integration::window_builder(native_options, &window_settings).with_title(app_name);
|
||||||
let event_loop = winit::event_loop::EventLoop::with_user_event();
|
let event_loop = winit::event_loop::EventLoop::with_user_event();
|
||||||
let (gl_window, gl) = create_display(native_options, window_builder, &event_loop);
|
let (gl_window, gl) = create_display(native_options, window_builder, &event_loop);
|
||||||
let gl = std::rc::Rc::new(gl);
|
let gl = std::rc::Rc::new(gl);
|
||||||
|
|
||||||
let mut painter = crate::Painter::new(gl.clone(), None, "")
|
let mut painter = egui_glow::Painter::new(gl.clone(), None, "")
|
||||||
.unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error));
|
.unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error));
|
||||||
|
|
||||||
let mut integration = egui_winit::epi::EpiIntegration::new(
|
let mut integration = epi_integration::EpiIntegration::new(
|
||||||
"egui_glow",
|
"egui_glow",
|
||||||
gl.clone(),
|
gl.clone(),
|
||||||
painter.max_texture_side(),
|
painter.max_texture_side(),
|
||||||
|
@ -94,7 +95,7 @@ pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi
|
||||||
crate::profile_scope!("frame");
|
crate::profile_scope!("frame");
|
||||||
let screen_size_in_pixels: [u32; 2] = gl_window.window().inner_size().into();
|
let screen_size_in_pixels: [u32; 2] = gl_window.window().inner_size().into();
|
||||||
|
|
||||||
crate::painter::clear(&gl, screen_size_in_pixels, app.clear_color());
|
egui_glow::painter::clear(&gl, screen_size_in_pixels, app.clear_color());
|
||||||
|
|
||||||
let egui::FullOutput {
|
let egui::FullOutput {
|
||||||
platform_output,
|
platform_output,
|
|
@ -4,10 +4,11 @@ All notable changes to the `egui-winit` integration will be noted in this file.
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
* Reexport `egui` crate
|
* Reexport `egui` crate
|
||||||
* Renamed the feature `convert_bytemuck` to `bytemuck` ([#1467](https://github.com/emilk/egui/pull/1467)).
|
|
||||||
* Renamed the feature `serialize` to `serde` ([#1467](https://github.com/emilk/egui/pull/1467)).
|
|
||||||
* MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)).
|
* MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)).
|
||||||
* Added new feature `puffin` to add [`puffin profiler`](https://github.com/EmbarkStudios/puffin) scopes ([#1483](https://github.com/emilk/egui/pull/1483)).
|
* Added new feature `puffin` to add [`puffin profiler`](https://github.com/EmbarkStudios/puffin) scopes ([#1483](https://github.com/emilk/egui/pull/1483)).
|
||||||
|
* Renamed the feature `convert_bytemuck` to `bytemuck` ([#1467](https://github.com/emilk/egui/pull/1467)).
|
||||||
|
* Renamed the feature `serialize` to `serde` ([#1467](https://github.com/emilk/egui/pull/1467)).
|
||||||
|
* Removed the features `dark-light` and `persistence` ([#1542](https://github.com/emilk/egui/pull/1542)).
|
||||||
|
|
||||||
|
|
||||||
## 0.17.0 - 2022-02-22
|
## 0.17.0 - 2022-02-22
|
||||||
|
|
|
@ -18,7 +18,7 @@ all-features = true
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["clipboard", "dark-light", "links"]
|
default = ["clipboard", "links"]
|
||||||
|
|
||||||
# implement bytemuck on most types.
|
# implement bytemuck on most types.
|
||||||
bytemuck = ["egui/bytemuck"]
|
bytemuck = ["egui/bytemuck"]
|
||||||
|
@ -27,22 +27,16 @@ bytemuck = ["egui/bytemuck"]
|
||||||
# if disabled a clipboard will be simulated so you can still copy/paste within the egui app.
|
# if disabled a clipboard will be simulated so you can still copy/paste within the egui app.
|
||||||
clipboard = ["arboard"]
|
clipboard = ["arboard"]
|
||||||
|
|
||||||
# detect dark mode system preference
|
|
||||||
dark-light = ["dep:dark-light"]
|
|
||||||
|
|
||||||
# Only for `egui_glow` - the official eframe/epi backend.
|
|
||||||
epi_backend = ["epi", "glow"]
|
|
||||||
|
|
||||||
# enable opening links in a browser when an egui hyperlink is clicked.
|
# enable opening links in a browser when an egui hyperlink is clicked.
|
||||||
links = ["webbrowser"]
|
links = ["webbrowser"]
|
||||||
|
|
||||||
# Enable profiling with the puffin crate: https://github.com/EmbarkStudios/puffin
|
# enable profiling with the puffin crate: https://github.com/EmbarkStudios/puffin
|
||||||
puffin = ["dep:puffin"]
|
puffin = ["dep:puffin"]
|
||||||
|
|
||||||
# experimental support for a screen reader
|
# experimental support for a screen reader
|
||||||
screen_reader = ["tts"]
|
screen_reader = ["tts"]
|
||||||
|
|
||||||
persistence = ["egui/serde", "serde", "epi?/persistence"]
|
# to serialize `WindowSettings`
|
||||||
serde = ["egui/serde", "dep:serde"]
|
serde = ["egui/serde", "dep:serde"]
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,16 +44,11 @@ serde = ["egui/serde", "dep:serde"]
|
||||||
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
|
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
|
||||||
"tracing",
|
"tracing",
|
||||||
] }
|
] }
|
||||||
instant = { version = "0.1", features = ["wasm-bindgen"] }
|
instant = { version = "0.1", features = ["wasm-bindgen"] } # We use instant so we can (maybe) compile for web
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
winit = "0.26.1"
|
winit = "0.26.1"
|
||||||
|
|
||||||
# Optional:
|
|
||||||
epi = { version = "0.17.0", path = "../epi", optional = true }
|
|
||||||
|
|
||||||
arboard = { version = "2.1", optional = true, default-features = false }
|
arboard = { version = "2.1", optional = true, default-features = false }
|
||||||
dark-light = { version = "0.2.1", optional = true }
|
|
||||||
glow = { version = "0.11", optional = true }
|
|
||||||
puffin = { version = "0.13", optional = true }
|
puffin = { version = "0.13", optional = true }
|
||||||
serde = { version = "1.0", optional = true, features = ["derive"] }
|
serde = { version = "1.0", optional = true, features = ["derive"] }
|
||||||
webbrowser = { version = "0.6", optional = true }
|
webbrowser = { version = "0.6", optional = true }
|
||||||
|
|
|
@ -12,9 +12,6 @@ pub mod clipboard;
|
||||||
pub mod screen_reader;
|
pub mod screen_reader;
|
||||||
mod window_settings;
|
mod window_settings;
|
||||||
|
|
||||||
#[cfg(feature = "epi")]
|
|
||||||
pub mod epi;
|
|
||||||
|
|
||||||
pub use window_settings::WindowSettings;
|
pub use window_settings::WindowSettings;
|
||||||
|
|
||||||
pub fn native_pixels_per_point(window: &winit::window::Window) -> f32 {
|
pub fn native_pixels_per_point(window: &winit::window::Window) -> f32 {
|
||||||
|
|
|
@ -60,7 +60,7 @@ pub struct RawInput {
|
||||||
/// Dragged files dropped into egui.
|
/// Dragged files dropped into egui.
|
||||||
///
|
///
|
||||||
/// Note: when using `eframe` on Windows you need to enable
|
/// Note: when using `eframe` on Windows you need to enable
|
||||||
/// drag-and-drop support using `epi::NativeOptions`.
|
/// drag-and-drop support using `eframe::NativeOptions`.
|
||||||
pub dropped_files: Vec<DroppedFile>,
|
pub dropped_files: Vec<DroppedFile>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,23 +24,16 @@ all-features = true
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["clipboard", "default_fonts", "links", "persistence"]
|
default = ["clipboard", "links"]
|
||||||
|
|
||||||
# enable cut/copy/paste to OS clipboard.
|
# enable cut/copy/paste to OS clipboard.
|
||||||
# if disabled a clipboard will be simulated so you can still copy/paste within the egui app.
|
# if disabled a clipboard will be simulated so you can still copy/paste within the egui app.
|
||||||
clipboard = ["egui-winit/clipboard"]
|
clipboard = ["egui-winit/clipboard"]
|
||||||
|
|
||||||
# If set, egui will use `include_bytes!` to bundle some fonts.
|
|
||||||
# If you plan on specifying your own fonts you may disable this feature.
|
|
||||||
default_fonts = ["egui/default_fonts"]
|
|
||||||
|
|
||||||
# enable opening links in a browser when an egui hyperlink is clicked.
|
# enable opening links in a browser when an egui hyperlink is clicked.
|
||||||
links = ["egui-winit/links"]
|
links = ["egui-winit/links"]
|
||||||
|
|
||||||
# enable persisting native window options and egui memory
|
# experimental support for a screen reader.
|
||||||
persistence = ["egui-winit/persistence", "egui/persistence"]
|
|
||||||
|
|
||||||
# experimental support for a screen reader
|
|
||||||
screen_reader = ["egui-winit/screen_reader"]
|
screen_reader = ["egui-winit/screen_reader"]
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,5 +47,6 @@ ahash = "0.7"
|
||||||
bytemuck = "1.7"
|
bytemuck = "1.7"
|
||||||
glium = "0.31"
|
glium = "0.31"
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
image = { version = "0.24", default-features = false, features = ["png"] }
|
image = { version = "0.24", default-features = false, features = ["png"] }
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
//! Example how to use [`epi::NativeTexture`] with glium.
|
|
||||||
|
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
|
|
||||||
use glium::glutin;
|
use glium::glutin;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//! Example how to use pure `egui_glium` without [`epi`].
|
//! Example how to use `egui_glium`.
|
||||||
|
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,11 @@ All notable changes to the `egui_glow` integration will be noted in this file.
|
||||||
## Unreleased
|
## Unreleased
|
||||||
* Improved logging on rendering failures.
|
* Improved logging on rendering failures.
|
||||||
* Add new `NativeOptions`: `vsync`, `multisampling`, `depth_buffer`, `stencil_buffer`.
|
* Add new `NativeOptions`: `vsync`, `multisampling`, `depth_buffer`, `stencil_buffer`.
|
||||||
* `dark-light` (dark mode detection) is now an opt-in feature ([#1437](https://github.com/emilk/egui/pull/1437)).
|
|
||||||
* Fixed potential scale bug when DPI scaling changes (e.g. when dragging a window between different displays) ([#1441](https://github.com/emilk/egui/pull/1441)).
|
* Fixed potential scale bug when DPI scaling changes (e.g. when dragging a window between different displays) ([#1441](https://github.com/emilk/egui/pull/1441)).
|
||||||
* MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)).
|
* MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)).
|
||||||
* `clipboard`, `links`, `persistence`, `winit` are now all opt-in features ([#1467](https://github.com/emilk/egui/pull/1467)).
|
* `clipboard`, `links`, `winit` are now all opt-in features ([#1467](https://github.com/emilk/egui/pull/1467)).
|
||||||
* Added new feature `puffin` to add [`puffin profiler`](https://github.com/EmbarkStudios/puffin) scopes ([#1483](https://github.com/emilk/egui/pull/1483)).
|
* Added new feature `puffin` to add [`puffin profiler`](https://github.com/EmbarkStudios/puffin) scopes ([#1483](https://github.com/emilk/egui/pull/1483)).
|
||||||
|
* Removed the features `dark-light`, `default_fonts` and `persistence` ([#1542](https://github.com/emilk/egui/pull/1542)).
|
||||||
|
|
||||||
|
|
||||||
## 0.17.0 - 2022-02-22
|
## 0.17.0 - 2022-02-22
|
||||||
|
|
|
@ -24,45 +24,31 @@ all-features = true
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["default_fonts"]
|
default = []
|
||||||
|
|
||||||
# enable cut/copy/paste to OS clipboard.
|
# for the winit integration:
|
||||||
|
# enable cut/copy/paste to os clipboard.
|
||||||
# if disabled a clipboard will be simulated so you can still copy/paste within the egui app.
|
# if disabled a clipboard will be simulated so you can still copy/paste within the egui app.
|
||||||
clipboard = ["egui-winit?/clipboard"]
|
clipboard = ["egui-winit?/clipboard"]
|
||||||
|
|
||||||
# detect dark mode system preference
|
# for the winit integration:
|
||||||
dark-light = ["egui-winit?/dark-light"]
|
|
||||||
|
|
||||||
# If set, egui will use `include_bytes!` to bundle some fonts.
|
|
||||||
# If you plan on specifying your own fonts you may disable this feature.
|
|
||||||
default_fonts = ["egui/default_fonts"]
|
|
||||||
|
|
||||||
# enable opening links in a browser when an egui hyperlink is clicked.
|
# enable opening links in a browser when an egui hyperlink is clicked.
|
||||||
links = ["egui-winit?/links"]
|
links = ["egui-winit?/links"]
|
||||||
|
|
||||||
# enable persisting native window options and egui memory
|
# experimental support for a screen reader.
|
||||||
persistence = [
|
|
||||||
"egui-winit?/persistence",
|
|
||||||
"egui/persistence",
|
|
||||||
"epi?/persistence",
|
|
||||||
]
|
|
||||||
|
|
||||||
# Enable profiling with the puffin crate: https://github.com/EmbarkStudios/puffin
|
|
||||||
puffin = ["dep:puffin", "egui-winit?/puffin"]
|
|
||||||
|
|
||||||
# experimental support for a screen reader
|
|
||||||
screen_reader = ["egui-winit?/screen_reader"]
|
screen_reader = ["egui-winit?/screen_reader"]
|
||||||
|
|
||||||
# enable glutin/winit integration.
|
# enable profiling with the puffin crate: https://github.com/embarkstudios/puffin
|
||||||
# if you want to use glow painter on web disable this feature.
|
puffin = ["dep:puffin", "egui-winit?/puffin"]
|
||||||
winit = ["egui-winit", "glutin"]
|
|
||||||
|
# enable winit integration.
|
||||||
|
winit = ["egui-winit",]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
|
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
] }
|
] }
|
||||||
epi = { version = "0.17.0", path = "../epi", optional = true }
|
|
||||||
|
|
||||||
bytemuck = "1.7"
|
bytemuck = "1.7"
|
||||||
glow = "0.11"
|
glow = "0.11"
|
||||||
|
@ -71,13 +57,19 @@ tracing = "0.1"
|
||||||
|
|
||||||
# Native:
|
# Native:
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
egui-winit = { version = "0.17.0", path = "../egui-winit", optional = true, default-features = false, features = [
|
egui-winit = { version = "0.17.0", path = "../egui-winit", optional = true, default-features = false }
|
||||||
"epi_backend",
|
|
||||||
] }
|
|
||||||
glutin = { version = "0.28.0", optional = true }
|
|
||||||
puffin = { version = "0.13", optional = true }
|
puffin = { version = "0.13", optional = true }
|
||||||
|
|
||||||
# Web:
|
# Web:
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
web-sys = { version = "0.3", features = ["console"] }
|
web-sys = { version = "0.3", features = ["console"] }
|
||||||
wasm-bindgen = { version = "0.2" }
|
wasm-bindgen = { version = "0.2" }
|
||||||
|
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
glutin = "0.28.0" # examples/pure_glow
|
||||||
|
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "pure_glow"
|
||||||
|
required-features = ["winit"]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//! Example how to use pure `egui_glow` without [`epi`].
|
//! Example how to use pure `egui_glow`.
|
||||||
|
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
#![allow(unsafe_code)]
|
#![allow(unsafe_code)]
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
//! [`egui`] bindings for [`glow`](https://github.com/grovesNL/glow).
|
//! [`egui`] bindings for [`glow`](https://github.com/grovesNL/glow).
|
||||||
//!
|
//!
|
||||||
//! The main type you want to use is [`EguiGlow`].
|
//! The main types you want to look are are [`Painter`] and [`EguiGlow`].
|
||||||
//!
|
//!
|
||||||
//! This library is an [`epi`] backend.
|
|
||||||
//! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead.
|
//! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead.
|
||||||
|
|
||||||
#![allow(clippy::float_cmp)]
|
#![allow(clippy::float_cmp)]
|
||||||
|
@ -21,12 +20,6 @@ pub mod winit;
|
||||||
#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))]
|
#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))]
|
||||||
pub use winit::*;
|
pub use winit::*;
|
||||||
|
|
||||||
#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))]
|
|
||||||
mod epi_backend;
|
|
||||||
|
|
||||||
#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))]
|
|
||||||
pub use epi_backend::{run, NativeOptions};
|
|
||||||
|
|
||||||
/// Check for OpenGL error and report it using `tracing::error`.
|
/// Check for OpenGL error and report it using `tracing::error`.
|
||||||
///
|
///
|
||||||
/// ``` no_run
|
/// ``` no_run
|
||||||
|
|
|
@ -47,7 +47,6 @@ pub struct Painter {
|
||||||
|
|
||||||
textures: HashMap<egui::TextureId, glow::Texture>,
|
textures: HashMap<egui::TextureId, glow::Texture>,
|
||||||
|
|
||||||
#[cfg(feature = "epi")]
|
|
||||||
next_native_tex_id: u64, // TODO: 128-bit texture space?
|
next_native_tex_id: u64, // TODO: 128-bit texture space?
|
||||||
|
|
||||||
/// Stores outdated OpenGL textures that are yet to be deleted
|
/// Stores outdated OpenGL textures that are yet to be deleted
|
||||||
|
@ -222,7 +221,6 @@ impl Painter {
|
||||||
vbo,
|
vbo,
|
||||||
element_array_buffer,
|
element_array_buffer,
|
||||||
textures: Default::default(),
|
textures: Default::default(),
|
||||||
#[cfg(feature = "epi")]
|
|
||||||
next_native_tex_id: 1 << 32,
|
next_native_tex_id: 1 << 32,
|
||||||
textures_to_destroy: Vec::new(),
|
textures_to_destroy: Vec::new(),
|
||||||
destroyed: false,
|
destroyed: false,
|
||||||
|
@ -603,6 +601,22 @@ impl Painter {
|
||||||
self.textures.get(&texture_id).copied()
|
self.textures.get(&texture_id).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_pass_by_value)] // False positive
|
||||||
|
pub fn register_native_texture(&mut self, native: glow::Texture) -> egui::TextureId {
|
||||||
|
self.assert_not_destroyed();
|
||||||
|
let id = egui::TextureId::User(self.next_native_tex_id);
|
||||||
|
self.next_native_tex_id += 1;
|
||||||
|
self.textures.insert(id, native);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_pass_by_value)] // False positive
|
||||||
|
pub fn replace_native_texture(&mut self, id: egui::TextureId, replacing: glow::Texture) {
|
||||||
|
if let Some(old_tex) = self.textures.insert(id, replacing) {
|
||||||
|
self.textures_to_destroy.push(old_tex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unsafe fn destroy_gl(&self) {
|
unsafe fn destroy_gl(&self) {
|
||||||
self.gl.delete_program(self.program);
|
self.gl.delete_program(self.program);
|
||||||
for tex in self.textures.values() {
|
for tex in self.textures.values() {
|
||||||
|
@ -666,25 +680,6 @@ impl Drop for Painter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "epi")]
|
|
||||||
impl epi::NativeTexture for Painter {
|
|
||||||
type Texture = glow::Texture;
|
|
||||||
|
|
||||||
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
|
|
||||||
self.assert_not_destroyed();
|
|
||||||
let id = egui::TextureId::User(self.next_native_tex_id);
|
|
||||||
self.next_native_tex_id += 1;
|
|
||||||
self.textures.insert(id, native);
|
|
||||||
id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) {
|
|
||||||
if let Some(old_tex) = self.textures.insert(id, replacing) {
|
|
||||||
self.textures_to_destroy.push(old_tex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_clip_rect(
|
fn set_clip_rect(
|
||||||
gl: &glow::Context,
|
gl: &glow::Context,
|
||||||
size_in_pixels: (u32, u32),
|
size_in_pixels: (u32, u32),
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
pub use egui_winit;
|
pub use egui_winit;
|
||||||
use egui_winit::winit;
|
use egui_winit::winit;
|
||||||
|
|
||||||
/// Use [`egui`] from a [`glow`] app.
|
/// Use [`egui`] from a [`glow`] app based on [`winit`].
|
||||||
pub struct EguiGlow {
|
pub struct EguiGlow {
|
||||||
pub egui_ctx: egui::Context,
|
pub egui_ctx: egui::Context,
|
||||||
pub egui_winit: egui_winit::State,
|
pub egui_winit: egui_winit::State,
|
||||||
|
|
|
@ -60,9 +60,7 @@ ron = { version = "0.7", optional = true }
|
||||||
serde = { version = "1", optional = true }
|
serde = { version = "1", optional = true }
|
||||||
tts = { version = "0.20", optional = true } # feature screen_reader
|
tts = { version = "0.20", optional = true } # feature screen_reader
|
||||||
|
|
||||||
[dependencies.web-sys]
|
web-sys = { version = "0.3.52", features = [
|
||||||
version = "0.3.52"
|
|
||||||
features = [
|
|
||||||
"BinaryType",
|
"BinaryType",
|
||||||
"Blob",
|
"Blob",
|
||||||
"Clipboard",
|
"Clipboard",
|
||||||
|
@ -103,4 +101,4 @@ features = [
|
||||||
"WebGlRenderingContext",
|
"WebGlRenderingContext",
|
||||||
"WheelEvent",
|
"WheelEvent",
|
||||||
"Window",
|
"Window",
|
||||||
]
|
] }
|
||||||
|
|
|
@ -330,6 +330,57 @@ impl AppRunner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Install event listeners to register different input events
|
/// Install event listeners to register different input events
|
||||||
/// and start running the given app.
|
/// and start running the given app.
|
||||||
pub fn start(canvas_id: &str, app_creator: epi::AppCreator) -> Result<AppRunnerRef, JsValue> {
|
pub fn start(canvas_id: &str, app_creator: epi::AppCreator) -> Result<AppRunnerRef, JsValue> {
|
||||||
|
@ -346,12 +397,12 @@ fn start_runner(app_runner: AppRunner) -> Result<AppRunnerRef, JsValue> {
|
||||||
panicked: Arc::new(AtomicBool::new(false)),
|
panicked: Arc::new(AtomicBool::new(false)),
|
||||||
};
|
};
|
||||||
|
|
||||||
install_canvas_events(&runner_container)?;
|
crate::events::install_canvas_events(&runner_container)?;
|
||||||
install_document_events(&runner_container)?;
|
crate::events::install_document_events(&runner_container)?;
|
||||||
text_agent::install_text_agent(&runner_container)?;
|
text_agent::install_text_agent(&runner_container)?;
|
||||||
repaint_every_ms(&runner_container, 1000)?; // just in case. TODO: make it a parameter
|
crate::events::repaint_every_ms(&runner_container, 1000)?; // just in case. TODO: make it a parameter
|
||||||
|
|
||||||
paint_and_schedule(&runner_container.runner, runner_container.panicked.clone())?;
|
crate::events::paint_and_schedule(&runner_container.runner, runner_container.panicked.clone())?;
|
||||||
|
|
||||||
// Disable all event handlers on panic
|
// Disable all event handlers on panic
|
||||||
std::panic::set_hook(Box::new({
|
std::panic::set_hook(Box::new({
|
||||||
|
@ -375,3 +426,18 @@ fn start_runner(app_runner: AppRunner) -> Result<AppRunnerRef, JsValue> {
|
||||||
|
|
||||||
Ok(runner_container.runner)
|
Ok(runner_container.runner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#[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) {}
|
||||||
|
}
|
||||||
|
|
544
egui_web/src/events.rs
Normal file
544
egui_web/src/events.rs
Normal file
|
@ -0,0 +1,544 @@
|
||||||
|
use crate::*;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
|
pub fn paint_and_schedule(
|
||||||
|
runner_ref: &AppRunnerRef,
|
||||||
|
panicked: Arc<AtomicBool>,
|
||||||
|
) -> Result<(), JsValue> {
|
||||||
|
fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
|
||||||
|
let mut runner_lock = runner_ref.lock();
|
||||||
|
if runner_lock.needs_repaint.fetch_and_clear() {
|
||||||
|
runner_lock.clear_color_buffer();
|
||||||
|
let (needs_repaint, clipped_primitives) = runner_lock.logic()?;
|
||||||
|
runner_lock.paint(&clipped_primitives)?;
|
||||||
|
if needs_repaint {
|
||||||
|
runner_lock.needs_repaint.set_true();
|
||||||
|
}
|
||||||
|
runner_lock.auto_save();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn request_animation_frame(
|
||||||
|
runner_ref: AppRunnerRef,
|
||||||
|
panicked: Arc<AtomicBool>,
|
||||||
|
) -> Result<(), JsValue> {
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
let window = web_sys::window().unwrap();
|
||||||
|
let closure = Closure::once(move || paint_and_schedule(&runner_ref, panicked));
|
||||||
|
window.request_animation_frame(closure.as_ref().unchecked_ref())?;
|
||||||
|
closure.forget(); // We must forget it, or else the callback is canceled on drop
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only paint and schedule if there has been no panic
|
||||||
|
if !panicked.load(Ordering::SeqCst) {
|
||||||
|
paint_if_needed(runner_ref)?;
|
||||||
|
request_animation_frame(runner_ref.clone(), panicked)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn install_document_events(runner_container: &AppRunnerContainer) -> Result<(), JsValue> {
|
||||||
|
let window = web_sys::window().unwrap();
|
||||||
|
let document = window.document().unwrap();
|
||||||
|
|
||||||
|
runner_container.add_event_listener(
|
||||||
|
&document,
|
||||||
|
"keydown",
|
||||||
|
|event: web_sys::KeyboardEvent, mut runner_lock| {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
let modifiers = modifiers_from_event(&event);
|
||||||
|
runner_lock.input.raw.modifiers = modifiers;
|
||||||
|
|
||||||
|
let key = event.key();
|
||||||
|
|
||||||
|
if let Some(key) = translate_key(&key) {
|
||||||
|
runner_lock.input.raw.events.push(egui::Event::Key {
|
||||||
|
key,
|
||||||
|
pressed: true,
|
||||||
|
modifiers,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if !modifiers.ctrl
|
||||||
|
&& !modifiers.command
|
||||||
|
&& !should_ignore_key(&key)
|
||||||
|
// When text agent is shown, it sends text event instead.
|
||||||
|
&& text_agent::text_agent().hidden()
|
||||||
|
{
|
||||||
|
runner_lock.input.raw.events.push(egui::Event::Text(key));
|
||||||
|
}
|
||||||
|
runner_lock.needs_repaint.set_true();
|
||||||
|
|
||||||
|
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.
|
||||||
|
// egui wants to use tab to move to the next text field.
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
// tracing::debug!(
|
||||||
|
// "On key-down {:?}, egui_wants_keyboard: {}, prevent_default: {}",
|
||||||
|
// event.key().as_str(),
|
||||||
|
// egui_wants_keyboard,
|
||||||
|
// prevent_default
|
||||||
|
// );
|
||||||
|
|
||||||
|
if prevent_default {
|
||||||
|
event.prevent_default();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
runner_container.add_event_listener(
|
||||||
|
&document,
|
||||||
|
"keyup",
|
||||||
|
|event: web_sys::KeyboardEvent, mut runner_lock| {
|
||||||
|
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 {
|
||||||
|
key,
|
||||||
|
pressed: false,
|
||||||
|
modifiers,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
runner_lock.needs_repaint.set_true();
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
#[cfg(web_sys_unstable_apis)]
|
||||||
|
runner_container.add_event_listener(
|
||||||
|
&document,
|
||||||
|
"paste",
|
||||||
|
|event: web_sys::ClipboardEvent, mut runner_lock| {
|
||||||
|
if let Some(data) = event.clipboard_data() {
|
||||||
|
if let Ok(text) = data.get_data("text") {
|
||||||
|
let text = text.replace("\r\n", "\n");
|
||||||
|
if !text.is_empty() {
|
||||||
|
runner_lock.input.raw.events.push(egui::Event::Paste(text));
|
||||||
|
runner_lock.needs_repaint.set_true();
|
||||||
|
}
|
||||||
|
event.stop_propagation();
|
||||||
|
event.prevent_default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
#[cfg(web_sys_unstable_apis)]
|
||||||
|
runner_container.add_event_listener(
|
||||||
|
&document,
|
||||||
|
"cut",
|
||||||
|
|_: web_sys::ClipboardEvent, mut runner_lock| {
|
||||||
|
runner_lock.input.raw.events.push(egui::Event::Cut);
|
||||||
|
runner_lock.needs_repaint.set_true();
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
#[cfg(web_sys_unstable_apis)]
|
||||||
|
runner_container.add_event_listener(
|
||||||
|
&document,
|
||||||
|
"copy",
|
||||||
|
|_: web_sys::ClipboardEvent, mut runner_lock| {
|
||||||
|
runner_lock.input.raw.events.push(egui::Event::Copy);
|
||||||
|
runner_lock.needs_repaint.set_true();
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
for event_name in &["load", "pagehide", "pageshow", "resize"] {
|
||||||
|
runner_container.add_event_listener(
|
||||||
|
&window,
|
||||||
|
event_name,
|
||||||
|
|_: web_sys::Event, runner_lock| {
|
||||||
|
runner_lock.needs_repaint.set_true();
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
runner_container.add_event_listener(
|
||||||
|
&window,
|
||||||
|
"hashchange",
|
||||||
|
|_: web_sys::Event, mut runner_lock| {
|
||||||
|
// `epi::Frame::info(&self)` clones `epi::IntegrationInfo`, but we need to modify the original here
|
||||||
|
if let Some(web_info) = &mut runner_lock.frame.info.web_info {
|
||||||
|
web_info.location.hash = location_hash();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Repaint at least every `ms` milliseconds.
|
||||||
|
pub fn repaint_every_ms(
|
||||||
|
runner_container: &AppRunnerContainer,
|
||||||
|
milliseconds: i32,
|
||||||
|
) -> Result<(), JsValue> {
|
||||||
|
assert!(milliseconds >= 0);
|
||||||
|
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
|
||||||
|
let window = web_sys::window().unwrap();
|
||||||
|
|
||||||
|
let closure = Closure::wrap(Box::new({
|
||||||
|
let runner = runner_container.runner.clone();
|
||||||
|
let panicked = runner_container.panicked.clone();
|
||||||
|
|
||||||
|
move || {
|
||||||
|
// Do not lock the runner if the code has panicked
|
||||||
|
if !panicked.load(Ordering::SeqCst) {
|
||||||
|
runner.lock().needs_repaint.set_true();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) as Box<dyn FnMut()>);
|
||||||
|
|
||||||
|
window.set_interval_with_callback_and_timeout_and_arguments_0(
|
||||||
|
closure.as_ref().unchecked_ref(),
|
||||||
|
milliseconds,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
closure.forget();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn install_canvas_events(runner_container: &AppRunnerContainer) -> Result<(), JsValue> {
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
let canvas = canvas_element(runner_container.runner.lock().canvas_id()).unwrap();
|
||||||
|
|
||||||
|
{
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
runner_container.add_event_listener(
|
||||||
|
&canvas,
|
||||||
|
"mousedown",
|
||||||
|
|event: web_sys::MouseEvent, mut runner_lock| {
|
||||||
|
if let Some(button) = button_from_mouse_event(&event) {
|
||||||
|
let pos = pos_from_mouse_event(runner_lock.canvas_id(), &event);
|
||||||
|
let modifiers = runner_lock.input.raw.modifiers;
|
||||||
|
runner_lock
|
||||||
|
.input
|
||||||
|
.raw
|
||||||
|
.events
|
||||||
|
.push(egui::Event::PointerButton {
|
||||||
|
pos,
|
||||||
|
button,
|
||||||
|
pressed: true,
|
||||||
|
modifiers,
|
||||||
|
});
|
||||||
|
runner_lock.needs_repaint.set_true();
|
||||||
|
}
|
||||||
|
event.stop_propagation();
|
||||||
|
// Note: prevent_default breaks VSCode tab focusing, hence why we don't call it here.
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
runner_container.add_event_listener(
|
||||||
|
&canvas,
|
||||||
|
"mousemove",
|
||||||
|
|event: web_sys::MouseEvent, mut runner_lock| {
|
||||||
|
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();
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
runner_container.add_event_listener(
|
||||||
|
&canvas,
|
||||||
|
"mouseup",
|
||||||
|
|event: web_sys::MouseEvent, mut runner_lock| {
|
||||||
|
if let Some(button) = button_from_mouse_event(&event) {
|
||||||
|
let pos = pos_from_mouse_event(runner_lock.canvas_id(), &event);
|
||||||
|
let modifiers = runner_lock.input.raw.modifiers;
|
||||||
|
runner_lock
|
||||||
|
.input
|
||||||
|
.raw
|
||||||
|
.events
|
||||||
|
.push(egui::Event::PointerButton {
|
||||||
|
pos,
|
||||||
|
button,
|
||||||
|
pressed: false,
|
||||||
|
modifiers,
|
||||||
|
});
|
||||||
|
runner_lock.needs_repaint.set_true();
|
||||||
|
|
||||||
|
text_agent::update_text_agent(runner_lock);
|
||||||
|
}
|
||||||
|
event.stop_propagation();
|
||||||
|
event.prevent_default();
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
runner_container.add_event_listener(
|
||||||
|
&canvas,
|
||||||
|
"mouseleave",
|
||||||
|
|event: web_sys::MouseEvent, mut runner_lock| {
|
||||||
|
runner_lock.input.raw.events.push(egui::Event::PointerGone);
|
||||||
|
runner_lock.needs_repaint.set_true();
|
||||||
|
event.stop_propagation();
|
||||||
|
event.prevent_default();
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
runner_container.add_event_listener(
|
||||||
|
&canvas,
|
||||||
|
"touchstart",
|
||||||
|
|event: web_sys::TouchEvent, mut runner_lock| {
|
||||||
|
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;
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
|
||||||
|
push_touches(&mut *runner_lock, egui::TouchPhase::Start, &event);
|
||||||
|
runner_lock.needs_repaint.set_true();
|
||||||
|
event.stop_propagation();
|
||||||
|
event.prevent_default();
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
runner_container.add_event_listener(
|
||||||
|
&canvas,
|
||||||
|
"touchmove",
|
||||||
|
|event: web_sys::TouchEvent, mut runner_lock| {
|
||||||
|
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;
|
||||||
|
runner_lock.input.latest_touch_pos = Some(pos);
|
||||||
|
runner_lock
|
||||||
|
.input
|
||||||
|
.raw
|
||||||
|
.events
|
||||||
|
.push(egui::Event::PointerMoved(pos));
|
||||||
|
|
||||||
|
push_touches(&mut *runner_lock, egui::TouchPhase::Move, &event);
|
||||||
|
runner_lock.needs_repaint.set_true();
|
||||||
|
event.stop_propagation();
|
||||||
|
event.prevent_default();
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
runner_container.add_event_listener(
|
||||||
|
&canvas,
|
||||||
|
"touchend",
|
||||||
|
|event: web_sys::TouchEvent, mut runner_lock| {
|
||||||
|
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);
|
||||||
|
|
||||||
|
push_touches(&mut *runner_lock, egui::TouchPhase::End, &event);
|
||||||
|
runner_lock.needs_repaint.set_true();
|
||||||
|
event.stop_propagation();
|
||||||
|
event.prevent_default();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, focus or blur text agent to toggle mobile keyboard:
|
||||||
|
text_agent::update_text_agent(runner_lock);
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
runner_container.add_event_listener(
|
||||||
|
&canvas,
|
||||||
|
"touchcancel",
|
||||||
|
|event: web_sys::TouchEvent, mut runner_lock| {
|
||||||
|
push_touches(&mut runner_lock, egui::TouchPhase::Cancel, &event);
|
||||||
|
event.stop_propagation();
|
||||||
|
event.prevent_default();
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
runner_container.add_event_listener(
|
||||||
|
&canvas,
|
||||||
|
"wheel",
|
||||||
|
|event: web_sys::WheelEvent, mut runner_lock| {
|
||||||
|
let scroll_multiplier = match event.delta_mode() {
|
||||||
|
web_sys::WheelEvent::DOM_DELTA_PAGE => {
|
||||||
|
canvas_size_in_points(runner_lock.canvas_id()).y
|
||||||
|
}
|
||||||
|
web_sys::WheelEvent::DOM_DELTA_LINE => {
|
||||||
|
#[allow(clippy::let_and_return)]
|
||||||
|
let points_per_scroll_line = 8.0; // Note that this is intentionally different from what we use in egui_glium / winit.
|
||||||
|
points_per_scroll_line
|
||||||
|
}
|
||||||
|
_ => 1.0, // DOM_DELTA_PIXEL
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut delta =
|
||||||
|
-scroll_multiplier * egui::vec2(event.delta_x() as f32, event.delta_y() as f32);
|
||||||
|
|
||||||
|
// 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() {
|
||||||
|
let factor = (delta.y / 200.0).exp();
|
||||||
|
runner_lock.input.raw.events.push(egui::Event::Zoom(factor));
|
||||||
|
} else {
|
||||||
|
if event.shift_key() {
|
||||||
|
// Treat as horizontal scrolling.
|
||||||
|
// Note: one Mac we already get horizontal scroll events when shift is down.
|
||||||
|
delta = egui::vec2(delta.x + delta.y, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
runner_lock
|
||||||
|
.input
|
||||||
|
.raw
|
||||||
|
.events
|
||||||
|
.push(egui::Event::Scroll(delta));
|
||||||
|
}
|
||||||
|
|
||||||
|
runner_lock.needs_repaint.set_true();
|
||||||
|
event.stop_propagation();
|
||||||
|
event.prevent_default();
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
runner_container.add_event_listener(
|
||||||
|
&canvas,
|
||||||
|
"dragover",
|
||||||
|
|event: web_sys::DragEvent, mut runner_lock| {
|
||||||
|
if let Some(data_transfer) = event.data_transfer() {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
runner_container.add_event_listener(
|
||||||
|
&canvas,
|
||||||
|
"dragleave",
|
||||||
|
|event: web_sys::DragEvent, mut runner_lock| {
|
||||||
|
runner_lock.input.raw.hovered_files.clear();
|
||||||
|
runner_lock.needs_repaint.set_true();
|
||||||
|
event.stop_propagation();
|
||||||
|
event.prevent_default();
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
runner_container.add_event_listener(&canvas, "drop", {
|
||||||
|
let runner_ref = runner_container.runner.clone();
|
||||||
|
|
||||||
|
move |event: web_sys::DragEvent, mut runner_lock| {
|
||||||
|
if let Some(data_transfer) = event.data_transfer() {
|
||||||
|
runner_lock.input.raw.hovered_files.clear();
|
||||||
|
runner_lock.needs_repaint.set_true();
|
||||||
|
// Unlock the runner so it can be locked after a future await point
|
||||||
|
drop(runner_lock);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
tracing::debug!("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();
|
||||||
|
tracing::debug!(
|
||||||
|
"Loaded {:?} ({} bytes).",
|
||||||
|
name,
|
||||||
|
bytes.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Re-lock the mutex on the other side of the await point
|
||||||
|
let mut runner_lock = runner_ref.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) => {
|
||||||
|
tracing::error!("Failed to read file: {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
wasm_bindgen_futures::spawn_local(future);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
event.stop_propagation();
|
||||||
|
event.prevent_default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -7,12 +7,16 @@
|
||||||
#![allow(clippy::missing_errors_doc)] // So many `-> Result<_, JsValue>`
|
#![allow(clippy::missing_errors_doc)] // So many `-> Result<_, JsValue>`
|
||||||
|
|
||||||
pub mod backend;
|
pub mod backend;
|
||||||
|
mod events;
|
||||||
mod glow_wrapping;
|
mod glow_wrapping;
|
||||||
mod input;
|
mod input;
|
||||||
pub mod screen_reader;
|
pub mod screen_reader;
|
||||||
|
pub mod storage;
|
||||||
mod text_agent;
|
mod text_agent;
|
||||||
|
|
||||||
pub use backend::*;
|
pub use backend::*;
|
||||||
|
pub use events::*;
|
||||||
|
pub use storage::*;
|
||||||
|
|
||||||
use egui::mutex::{Mutex, MutexGuard};
|
use egui::mutex::{Mutex, MutexGuard};
|
||||||
pub use wasm_bindgen;
|
pub use wasm_bindgen;
|
||||||
|
@ -133,69 +137,6 @@ pub fn resize_canvas_to_screen_size(canvas_id: &str, max_size_points: egui::Vec2
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "persistence")]
|
|
||||||
pub fn load_memory(ctx: &egui::Context) {
|
|
||||||
if let Some(memory_string) = local_storage_get("egui_memory_ron") {
|
|
||||||
match ron::from_str(&memory_string) {
|
|
||||||
Ok(memory) => {
|
|
||||||
*ctx.memory() = memory;
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
tracing::error!("Failed to parse memory RON: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "persistence"))]
|
|
||||||
pub fn load_memory(_: &egui::Context) {}
|
|
||||||
|
|
||||||
#[cfg(feature = "persistence")]
|
|
||||||
pub fn save_memory(ctx: &egui::Context) {
|
|
||||||
match ron::to_string(&*ctx.memory()) {
|
|
||||||
Ok(ron) => {
|
|
||||||
local_storage_set("egui_memory_ron", &ron);
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
tracing::error!("Failed to serialize memory as RON: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "persistence"))]
|
|
||||||
pub fn save_memory(_: &egui::Context) {}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub 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) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
pub fn set_cursor_icon(cursor: egui::CursorIcon) -> Option<()> {
|
pub fn set_cursor_icon(cursor: egui::CursorIcon) -> Option<()> {
|
||||||
let document = web_sys::window()?.document()?;
|
let document = web_sys::window()?.document()?;
|
||||||
document
|
document
|
||||||
|
@ -300,592 +241,6 @@ pub fn percent_decode(s: &str) -> String {
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
pub type AppRunnerRef = Arc<Mutex<AppRunner>>;
|
|
||||||
|
|
||||||
pub struct AppRunnerContainer {
|
|
||||||
runner: AppRunnerRef,
|
|
||||||
/// Set to `true` if there is a panic.
|
|
||||||
/// Used to ignore callbacks after a panic.
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn paint_and_schedule(runner_ref: &AppRunnerRef, panicked: Arc<AtomicBool>) -> Result<(), JsValue> {
|
|
||||||
fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
|
|
||||||
let mut runner_lock = runner_ref.lock();
|
|
||||||
if runner_lock.needs_repaint.fetch_and_clear() {
|
|
||||||
runner_lock.clear_color_buffer();
|
|
||||||
let (needs_repaint, clipped_primitives) = runner_lock.logic()?;
|
|
||||||
runner_lock.paint(&clipped_primitives)?;
|
|
||||||
if needs_repaint {
|
|
||||||
runner_lock.needs_repaint.set_true();
|
|
||||||
}
|
|
||||||
runner_lock.auto_save();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn request_animation_frame(
|
|
||||||
runner_ref: AppRunnerRef,
|
|
||||||
panicked: Arc<AtomicBool>,
|
|
||||||
) -> Result<(), JsValue> {
|
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
let window = web_sys::window().unwrap();
|
|
||||||
let closure = Closure::once(move || paint_and_schedule(&runner_ref, panicked));
|
|
||||||
window.request_animation_frame(closure.as_ref().unchecked_ref())?;
|
|
||||||
closure.forget(); // We must forget it, or else the callback is canceled on drop
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only paint and schedule if there has been no panic
|
|
||||||
if !panicked.load(Ordering::SeqCst) {
|
|
||||||
paint_if_needed(runner_ref)?;
|
|
||||||
request_animation_frame(runner_ref.clone(), panicked)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn install_document_events(runner_container: &AppRunnerContainer) -> Result<(), JsValue> {
|
|
||||||
let window = web_sys::window().unwrap();
|
|
||||||
let document = window.document().unwrap();
|
|
||||||
|
|
||||||
runner_container.add_event_listener(
|
|
||||||
&document,
|
|
||||||
"keydown",
|
|
||||||
|event: web_sys::KeyboardEvent, mut runner_lock| {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
let modifiers = modifiers_from_event(&event);
|
|
||||||
runner_lock.input.raw.modifiers = modifiers;
|
|
||||||
|
|
||||||
let key = event.key();
|
|
||||||
|
|
||||||
if let Some(key) = translate_key(&key) {
|
|
||||||
runner_lock.input.raw.events.push(egui::Event::Key {
|
|
||||||
key,
|
|
||||||
pressed: true,
|
|
||||||
modifiers,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if !modifiers.ctrl
|
|
||||||
&& !modifiers.command
|
|
||||||
&& !should_ignore_key(&key)
|
|
||||||
// When text agent is shown, it sends text event instead.
|
|
||||||
&& text_agent::text_agent().hidden()
|
|
||||||
{
|
|
||||||
runner_lock.input.raw.events.push(egui::Event::Text(key));
|
|
||||||
}
|
|
||||||
runner_lock.needs_repaint.set_true();
|
|
||||||
|
|
||||||
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.
|
|
||||||
// egui wants to use tab to move to the next text field.
|
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
// tracing::debug!(
|
|
||||||
// "On key-down {:?}, egui_wants_keyboard: {}, prevent_default: {}",
|
|
||||||
// event.key().as_str(),
|
|
||||||
// egui_wants_keyboard,
|
|
||||||
// prevent_default
|
|
||||||
// );
|
|
||||||
|
|
||||||
if prevent_default {
|
|
||||||
event.prevent_default();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
runner_container.add_event_listener(
|
|
||||||
&document,
|
|
||||||
"keyup",
|
|
||||||
|event: web_sys::KeyboardEvent, mut runner_lock| {
|
|
||||||
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 {
|
|
||||||
key,
|
|
||||||
pressed: false,
|
|
||||||
modifiers,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
runner_lock.needs_repaint.set_true();
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
#[cfg(web_sys_unstable_apis)]
|
|
||||||
runner_container.add_event_listener(
|
|
||||||
&document,
|
|
||||||
"paste",
|
|
||||||
|event: web_sys::ClipboardEvent, mut runner_lock| {
|
|
||||||
if let Some(data) = event.clipboard_data() {
|
|
||||||
if let Ok(text) = data.get_data("text") {
|
|
||||||
let text = text.replace("\r\n", "\n");
|
|
||||||
if !text.is_empty() {
|
|
||||||
runner_lock.input.raw.events.push(egui::Event::Paste(text));
|
|
||||||
runner_lock.needs_repaint.set_true();
|
|
||||||
}
|
|
||||||
event.stop_propagation();
|
|
||||||
event.prevent_default();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
#[cfg(web_sys_unstable_apis)]
|
|
||||||
runner_container.add_event_listener(
|
|
||||||
&document,
|
|
||||||
"cut",
|
|
||||||
|_: web_sys::ClipboardEvent, mut runner_lock| {
|
|
||||||
runner_lock.input.raw.events.push(egui::Event::Cut);
|
|
||||||
runner_lock.needs_repaint.set_true();
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
#[cfg(web_sys_unstable_apis)]
|
|
||||||
runner_container.add_event_listener(
|
|
||||||
&document,
|
|
||||||
"copy",
|
|
||||||
|_: web_sys::ClipboardEvent, mut runner_lock| {
|
|
||||||
runner_lock.input.raw.events.push(egui::Event::Copy);
|
|
||||||
runner_lock.needs_repaint.set_true();
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
for event_name in &["load", "pagehide", "pageshow", "resize"] {
|
|
||||||
runner_container.add_event_listener(
|
|
||||||
&window,
|
|
||||||
event_name,
|
|
||||||
|_: web_sys::Event, runner_lock| {
|
|
||||||
runner_lock.needs_repaint.set_true();
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
runner_container.add_event_listener(
|
|
||||||
&window,
|
|
||||||
"hashchange",
|
|
||||||
|_: web_sys::Event, mut runner_lock| {
|
|
||||||
// `epi::Frame::info(&self)` clones `epi::IntegrationInfo`, but we need to modify the original here
|
|
||||||
if let Some(web_info) = &mut runner_lock.frame.info.web_info {
|
|
||||||
web_info.location.hash = location_hash();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Repaint at least every `ms` milliseconds.
|
|
||||||
pub fn repaint_every_ms(
|
|
||||||
runner_container: &AppRunnerContainer,
|
|
||||||
milliseconds: i32,
|
|
||||||
) -> Result<(), JsValue> {
|
|
||||||
assert!(milliseconds >= 0);
|
|
||||||
|
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
|
|
||||||
let window = web_sys::window().unwrap();
|
|
||||||
|
|
||||||
let closure = Closure::wrap(Box::new({
|
|
||||||
let runner = runner_container.runner.clone();
|
|
||||||
let panicked = runner_container.panicked.clone();
|
|
||||||
|
|
||||||
move || {
|
|
||||||
// Do not lock the runner if the code has panicked
|
|
||||||
if !panicked.load(Ordering::SeqCst) {
|
|
||||||
runner.lock().needs_repaint.set_true();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) as Box<dyn FnMut()>);
|
|
||||||
|
|
||||||
window.set_interval_with_callback_and_timeout_and_arguments_0(
|
|
||||||
closure.as_ref().unchecked_ref(),
|
|
||||||
milliseconds,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
closure.forget();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn install_canvas_events(runner_container: &AppRunnerContainer) -> Result<(), JsValue> {
|
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
let canvas = canvas_element(runner_container.runner.lock().canvas_id()).unwrap();
|
|
||||||
|
|
||||||
{
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
runner_container.add_event_listener(
|
|
||||||
&canvas,
|
|
||||||
"mousedown",
|
|
||||||
|event: web_sys::MouseEvent, mut runner_lock| {
|
|
||||||
if let Some(button) = button_from_mouse_event(&event) {
|
|
||||||
let pos = pos_from_mouse_event(runner_lock.canvas_id(), &event);
|
|
||||||
let modifiers = runner_lock.input.raw.modifiers;
|
|
||||||
runner_lock
|
|
||||||
.input
|
|
||||||
.raw
|
|
||||||
.events
|
|
||||||
.push(egui::Event::PointerButton {
|
|
||||||
pos,
|
|
||||||
button,
|
|
||||||
pressed: true,
|
|
||||||
modifiers,
|
|
||||||
});
|
|
||||||
runner_lock.needs_repaint.set_true();
|
|
||||||
}
|
|
||||||
event.stop_propagation();
|
|
||||||
// Note: prevent_default breaks VSCode tab focusing, hence why we don't call it here.
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
runner_container.add_event_listener(
|
|
||||||
&canvas,
|
|
||||||
"mousemove",
|
|
||||||
|event: web_sys::MouseEvent, mut runner_lock| {
|
|
||||||
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();
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
runner_container.add_event_listener(
|
|
||||||
&canvas,
|
|
||||||
"mouseup",
|
|
||||||
|event: web_sys::MouseEvent, mut runner_lock| {
|
|
||||||
if let Some(button) = button_from_mouse_event(&event) {
|
|
||||||
let pos = pos_from_mouse_event(runner_lock.canvas_id(), &event);
|
|
||||||
let modifiers = runner_lock.input.raw.modifiers;
|
|
||||||
runner_lock
|
|
||||||
.input
|
|
||||||
.raw
|
|
||||||
.events
|
|
||||||
.push(egui::Event::PointerButton {
|
|
||||||
pos,
|
|
||||||
button,
|
|
||||||
pressed: false,
|
|
||||||
modifiers,
|
|
||||||
});
|
|
||||||
runner_lock.needs_repaint.set_true();
|
|
||||||
|
|
||||||
text_agent::update_text_agent(runner_lock);
|
|
||||||
}
|
|
||||||
event.stop_propagation();
|
|
||||||
event.prevent_default();
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
runner_container.add_event_listener(
|
|
||||||
&canvas,
|
|
||||||
"mouseleave",
|
|
||||||
|event: web_sys::MouseEvent, mut runner_lock| {
|
|
||||||
runner_lock.input.raw.events.push(egui::Event::PointerGone);
|
|
||||||
runner_lock.needs_repaint.set_true();
|
|
||||||
event.stop_propagation();
|
|
||||||
event.prevent_default();
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
runner_container.add_event_listener(
|
|
||||||
&canvas,
|
|
||||||
"touchstart",
|
|
||||||
|event: web_sys::TouchEvent, mut runner_lock| {
|
|
||||||
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;
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
push_touches(&mut *runner_lock, egui::TouchPhase::Start, &event);
|
|
||||||
runner_lock.needs_repaint.set_true();
|
|
||||||
event.stop_propagation();
|
|
||||||
event.prevent_default();
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
runner_container.add_event_listener(
|
|
||||||
&canvas,
|
|
||||||
"touchmove",
|
|
||||||
|event: web_sys::TouchEvent, mut runner_lock| {
|
|
||||||
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;
|
|
||||||
runner_lock.input.latest_touch_pos = Some(pos);
|
|
||||||
runner_lock
|
|
||||||
.input
|
|
||||||
.raw
|
|
||||||
.events
|
|
||||||
.push(egui::Event::PointerMoved(pos));
|
|
||||||
|
|
||||||
push_touches(&mut *runner_lock, egui::TouchPhase::Move, &event);
|
|
||||||
runner_lock.needs_repaint.set_true();
|
|
||||||
event.stop_propagation();
|
|
||||||
event.prevent_default();
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
runner_container.add_event_listener(
|
|
||||||
&canvas,
|
|
||||||
"touchend",
|
|
||||||
|event: web_sys::TouchEvent, mut runner_lock| {
|
|
||||||
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);
|
|
||||||
|
|
||||||
push_touches(&mut *runner_lock, egui::TouchPhase::End, &event);
|
|
||||||
runner_lock.needs_repaint.set_true();
|
|
||||||
event.stop_propagation();
|
|
||||||
event.prevent_default();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, focus or blur text agent to toggle mobile keyboard:
|
|
||||||
text_agent::update_text_agent(runner_lock);
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
runner_container.add_event_listener(
|
|
||||||
&canvas,
|
|
||||||
"touchcancel",
|
|
||||||
|event: web_sys::TouchEvent, mut runner_lock| {
|
|
||||||
push_touches(&mut runner_lock, egui::TouchPhase::Cancel, &event);
|
|
||||||
event.stop_propagation();
|
|
||||||
event.prevent_default();
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
runner_container.add_event_listener(
|
|
||||||
&canvas,
|
|
||||||
"wheel",
|
|
||||||
|event: web_sys::WheelEvent, mut runner_lock| {
|
|
||||||
let scroll_multiplier = match event.delta_mode() {
|
|
||||||
web_sys::WheelEvent::DOM_DELTA_PAGE => {
|
|
||||||
canvas_size_in_points(runner_lock.canvas_id()).y
|
|
||||||
}
|
|
||||||
web_sys::WheelEvent::DOM_DELTA_LINE => {
|
|
||||||
#[allow(clippy::let_and_return)]
|
|
||||||
let points_per_scroll_line = 8.0; // Note that this is intentionally different from what we use in egui_glium / winit.
|
|
||||||
points_per_scroll_line
|
|
||||||
}
|
|
||||||
_ => 1.0, // DOM_DELTA_PIXEL
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut delta =
|
|
||||||
-scroll_multiplier * egui::vec2(event.delta_x() as f32, event.delta_y() as f32);
|
|
||||||
|
|
||||||
// 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() {
|
|
||||||
let factor = (delta.y / 200.0).exp();
|
|
||||||
runner_lock.input.raw.events.push(egui::Event::Zoom(factor));
|
|
||||||
} else {
|
|
||||||
if event.shift_key() {
|
|
||||||
// Treat as horizontal scrolling.
|
|
||||||
// Note: one Mac we already get horizontal scroll events when shift is down.
|
|
||||||
delta = egui::vec2(delta.x + delta.y, 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
runner_lock
|
|
||||||
.input
|
|
||||||
.raw
|
|
||||||
.events
|
|
||||||
.push(egui::Event::Scroll(delta));
|
|
||||||
}
|
|
||||||
|
|
||||||
runner_lock.needs_repaint.set_true();
|
|
||||||
event.stop_propagation();
|
|
||||||
event.prevent_default();
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
runner_container.add_event_listener(
|
|
||||||
&canvas,
|
|
||||||
"dragover",
|
|
||||||
|event: web_sys::DragEvent, mut runner_lock| {
|
|
||||||
if let Some(data_transfer) = event.data_transfer() {
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
runner_container.add_event_listener(
|
|
||||||
&canvas,
|
|
||||||
"dragleave",
|
|
||||||
|event: web_sys::DragEvent, mut runner_lock| {
|
|
||||||
runner_lock.input.raw.hovered_files.clear();
|
|
||||||
runner_lock.needs_repaint.set_true();
|
|
||||||
event.stop_propagation();
|
|
||||||
event.prevent_default();
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
runner_container.add_event_listener(&canvas, "drop", {
|
|
||||||
let runner_ref = runner_container.runner.clone();
|
|
||||||
|
|
||||||
move |event: web_sys::DragEvent, mut runner_lock| {
|
|
||||||
if let Some(data_transfer) = event.data_transfer() {
|
|
||||||
runner_lock.input.raw.hovered_files.clear();
|
|
||||||
runner_lock.needs_repaint.set_true();
|
|
||||||
// Unlock the runner so it can be locked after a future await point
|
|
||||||
drop(runner_lock);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
tracing::debug!("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();
|
|
||||||
tracing::debug!(
|
|
||||||
"Loaded {:?} ({} bytes).",
|
|
||||||
name,
|
|
||||||
bytes.len()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Re-lock the mutex on the other side of the await point
|
|
||||||
let mut runner_lock = runner_ref.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) => {
|
|
||||||
tracing::error!("Failed to read file: {:?}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
wasm_bindgen_futures::spawn_local(future);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
event.stop_propagation();
|
|
||||||
event.prevent_default();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn webgl1_requires_brightening(gl: &web_sys::WebGlRenderingContext) -> bool {
|
pub(crate) fn webgl1_requires_brightening(gl: &web_sys::WebGlRenderingContext) -> bool {
|
||||||
// See https://github.com/emilk/egui/issues/794
|
// See https://github.com/emilk/egui/issues/794
|
||||||
|
|
||||||
|
|
47
egui_web/src/storage.rs
Normal file
47
egui_web/src/storage.rs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "persistence")]
|
||||||
|
pub fn load_memory(ctx: &egui::Context) {
|
||||||
|
if let Some(memory_string) = local_storage_get("egui_memory_ron") {
|
||||||
|
match ron::from_str(&memory_string) {
|
||||||
|
Ok(memory) => {
|
||||||
|
*ctx.memory() = memory;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
tracing::error!("Failed to parse memory RON: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "persistence"))]
|
||||||
|
pub fn load_memory(_: &egui::Context) {}
|
||||||
|
|
||||||
|
#[cfg(feature = "persistence")]
|
||||||
|
pub fn save_memory(ctx: &egui::Context) {
|
||||||
|
match ron::to_string(&*ctx.memory()) {
|
||||||
|
Ok(ron) => {
|
||||||
|
local_storage_set("egui_memory_ron", &ron);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
tracing::error!("Failed to serialize memory as RON: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "persistence"))]
|
||||||
|
pub fn save_memory(_: &egui::Context) {}
|
|
@ -425,18 +425,6 @@ pub struct IntegrationInfo {
|
||||||
pub native_pixels_per_point: Option<f32>,
|
pub native_pixels_per_point: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Abstraction for platform dependent texture reference
|
|
||||||
pub trait NativeTexture {
|
|
||||||
/// The native texture type.
|
|
||||||
type Texture;
|
|
||||||
|
|
||||||
/// Bind native texture to an egui texture id.
|
|
||||||
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId;
|
|
||||||
|
|
||||||
/// Change what texture the given id refers to.
|
|
||||||
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// A place where you can store custom data in a way that persists when you restart the app.
|
/// A place where you can store custom data in a way that persists when you restart the app.
|
||||||
|
|
15
sh/check.sh
15
sh/check.sh
|
@ -21,18 +21,19 @@ cargo doc -p emath -p epaint -p egui -p eframe -p epi -p egui_web -p egui-winit
|
||||||
cargo doc -p egui_web --target wasm32-unknown-unknown --lib --no-deps --all-features
|
cargo doc -p egui_web --target wasm32-unknown-unknown --lib --no-deps --all-features
|
||||||
cargo doc --document-private-items --no-deps --all-features
|
cargo doc --document-private-items --no-deps --all-features
|
||||||
|
|
||||||
(cd emath && cargo check --no-default-features)
|
|
||||||
(cd epaint && cargo check --no-default-features)
|
|
||||||
(cd epaint && cargo check --no-default-features --release)
|
|
||||||
(cd egui && cargo check --no-default-features --features "serde")
|
|
||||||
(cd eframe && cargo check --no-default-features)
|
(cd eframe && cargo check --no-default-features)
|
||||||
(cd epi && cargo check --no-default-features)
|
(cd egui && cargo check --no-default-features --features "serde")
|
||||||
|
(cd egui_demo_app && cargo check --no-default-features)
|
||||||
(cd egui_demo_lib && cargo check --no-default-features)
|
(cd egui_demo_lib && cargo check --no-default-features)
|
||||||
(cd egui_extras && cargo check --no-default-features)
|
(cd egui_extras && cargo check --no-default-features)
|
||||||
(cd egui_web && cargo check --no-default-features)
|
|
||||||
(cd egui-winit && cargo check --no-default-features)
|
|
||||||
(cd egui_glium && cargo check --no-default-features)
|
(cd egui_glium && cargo check --no-default-features)
|
||||||
(cd egui_glow && cargo check --no-default-features)
|
(cd egui_glow && cargo check --no-default-features)
|
||||||
|
(cd egui_web && cargo check --no-default-features)
|
||||||
|
(cd egui-winit && cargo check --no-default-features)
|
||||||
|
(cd emath && cargo check --no-default-features)
|
||||||
|
(cd epaint && cargo check --no-default-features --release)
|
||||||
|
(cd epaint && cargo check --no-default-features)
|
||||||
|
(cd epi && cargo check --no-default-features)
|
||||||
|
|
||||||
|
|
||||||
(cd eframe && cargo check --all-features)
|
(cd eframe && cargo check --all-features)
|
||||||
|
|
Loading…
Reference in a new issue