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"
|
||||
version = "0.17.0"
|
||||
dependencies = [
|
||||
"dark-light",
|
||||
"egui",
|
||||
"egui-winit",
|
||||
"egui_glow",
|
||||
"egui_web",
|
||||
"epi",
|
||||
"glow",
|
||||
"glutin",
|
||||
"puffin",
|
||||
"winit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1099,10 +1104,7 @@ name = "egui-winit"
|
|||
version = "0.17.0"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"dark-light",
|
||||
"egui",
|
||||
"epi",
|
||||
"glow",
|
||||
"instant",
|
||||
"puffin",
|
||||
"serde",
|
||||
|
@ -1178,7 +1180,6 @@ dependencies = [
|
|||
"bytemuck",
|
||||
"egui",
|
||||
"egui-winit",
|
||||
"epi",
|
||||
"glow",
|
||||
"glutin",
|
||||
"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`.
|
||||
* `Frame` is no longer `Clone` or `Sync`.
|
||||
* 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)).
|
||||
* 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)).
|
||||
* Moved app persistence to a background thread, allowing for smoother frame rates (on native).
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ all-features = true
|
|||
default = ["default_fonts"]
|
||||
|
||||
# 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 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.
|
||||
persistence = [
|
||||
"egui-winit/persistence",
|
||||
"egui_glow/persistence",
|
||||
"egui-winit/serde",
|
||||
"egui/persistence",
|
||||
"epi/persistence",
|
||||
]
|
||||
|
@ -40,12 +39,11 @@ persistence = [
|
|||
# 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.
|
||||
# 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;`)
|
||||
screen_reader = [
|
||||
"egui-winit/screen_reader",
|
||||
"egui_glow/screen_reader",
|
||||
"egui_web/screen_reader",
|
||||
]
|
||||
|
||||
|
@ -58,11 +56,14 @@ epi = { version = "0.17.0", path = "../epi" }
|
|||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
egui_glow = { version = "0.17.0", path = "../egui_glow", default-features = false, features = [
|
||||
"clipboard",
|
||||
"epi",
|
||||
"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:
|
||||
[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
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod native;
|
||||
|
||||
/// 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
|
||||
|
@ -135,5 +138,29 @@ pub fn start_web(canvas_id: &str, app_creator: AppCreator) -> Result<(), wasm_bi
|
|||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
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> {
|
||||
winit::dpi::LogicalSize {
|
||||
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(
|
||||
native_options: &epi::NativeOptions,
|
||||
window_settings: &Option<crate::WindowSettings>,
|
||||
window_settings: &Option<WindowSettings>,
|
||||
) -> winit::window::WindowBuilder {
|
||||
let epi::NativeOptions {
|
||||
always_on_top,
|
||||
|
@ -109,7 +111,7 @@ pub fn handle_app_output(
|
|||
width: (current_pixels_per_point * window_size.x).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`].
|
||||
pub struct EpiIntegration {
|
||||
pub frame: epi::Frame,
|
||||
last_auto_save: instant::Instant,
|
||||
last_auto_save: std::time::Instant,
|
||||
pub egui_ctx: egui::Context,
|
||||
pending_full_output: egui::FullOutput,
|
||||
egui_winit: crate::State,
|
||||
egui_winit: egui_winit::State,
|
||||
/// When set, it is time to quit
|
||||
quit: bool,
|
||||
can_drag_window: bool,
|
||||
|
@ -174,7 +176,7 @@ impl EpiIntegration {
|
|||
web_info: None,
|
||||
prefer_dark_mode,
|
||||
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(),
|
||||
storage,
|
||||
|
@ -189,9 +191,9 @@ impl EpiIntegration {
|
|||
|
||||
Self {
|
||||
frame,
|
||||
last_auto_save: instant::Instant::now(),
|
||||
last_auto_save: std::time::Instant::now(),
|
||||
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(),
|
||||
quit: false,
|
||||
can_drag_window: false,
|
||||
|
@ -235,7 +237,7 @@ impl EpiIntegration {
|
|||
app: &mut dyn epi::App,
|
||||
window: &winit::window::Window,
|
||||
) -> 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 full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
|
||||
|
@ -252,10 +254,10 @@ impl EpiIntegration {
|
|||
if app_output.quit {
|
||||
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);
|
||||
|
||||
full_output
|
||||
|
@ -274,7 +276,7 @@ impl EpiIntegration {
|
|||
// Persistance stuff:
|
||||
|
||||
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() {
|
||||
self.save(app, window);
|
||||
self.last_auto_save = now;
|
||||
|
@ -291,7 +293,7 @@ impl EpiIntegration {
|
|||
epi::set_value(
|
||||
storage,
|
||||
STORAGE_WINDOW_KEY,
|
||||
&crate::WindowSettings::from_display(_window),
|
||||
&WindowSettings::from_display(_window),
|
||||
);
|
||||
}
|
||||
if _app.persist_egui_memory() {
|
||||
|
@ -314,7 +316,7 @@ const STORAGE_EGUI_MEMORY_KEY: &str = "egui";
|
|||
#[cfg(feature = "persistence")]
|
||||
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")]
|
||||
{
|
||||
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;
|
||||
|
||||
struct RequestRepaintEvent;
|
||||
|
@ -37,18 +38,18 @@ pub use epi::NativeOptions;
|
|||
/// Run an egui app
|
||||
#[allow(unsafe_code)]
|
||||
pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi::AppCreator) -> ! {
|
||||
let storage = egui_winit::epi::create_storage(app_name);
|
||||
let window_settings = egui_winit::epi::load_window_settings(storage.as_deref());
|
||||
let storage = epi_integration::create_storage(app_name);
|
||||
let window_settings = epi_integration::load_window_settings(storage.as_deref());
|
||||
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 (gl_window, gl) = create_display(native_options, window_builder, &event_loop);
|
||||
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));
|
||||
|
||||
let mut integration = egui_winit::epi::EpiIntegration::new(
|
||||
let mut integration = epi_integration::EpiIntegration::new(
|
||||
"egui_glow",
|
||||
gl.clone(),
|
||||
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");
|
||||
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 {
|
||||
platform_output,
|
|
@ -4,10 +4,11 @@ All notable changes to the `egui-winit` integration will be noted in this file.
|
|||
|
||||
## Unreleased
|
||||
* 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)).
|
||||
* 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
|
||||
|
|
|
@ -18,7 +18,7 @@ all-features = true
|
|||
|
||||
|
||||
[features]
|
||||
default = ["clipboard", "dark-light", "links"]
|
||||
default = ["clipboard", "links"]
|
||||
|
||||
# implement bytemuck on most types.
|
||||
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.
|
||||
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.
|
||||
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"]
|
||||
|
||||
# experimental support for a screen reader
|
||||
screen_reader = ["tts"]
|
||||
|
||||
persistence = ["egui/serde", "serde", "epi?/persistence"]
|
||||
# to serialize `WindowSettings`
|
||||
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 = [
|
||||
"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"
|
||||
winit = "0.26.1"
|
||||
|
||||
# Optional:
|
||||
epi = { version = "0.17.0", path = "../epi", optional = true }
|
||||
|
||||
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 }
|
||||
serde = { version = "1.0", optional = true, features = ["derive"] }
|
||||
webbrowser = { version = "0.6", optional = true }
|
||||
|
|
|
@ -12,9 +12,6 @@ pub mod clipboard;
|
|||
pub mod screen_reader;
|
||||
mod window_settings;
|
||||
|
||||
#[cfg(feature = "epi")]
|
||||
pub mod epi;
|
||||
|
||||
pub use window_settings::WindowSettings;
|
||||
|
||||
pub fn native_pixels_per_point(window: &winit::window::Window) -> f32 {
|
||||
|
|
|
@ -60,7 +60,7 @@ pub struct RawInput {
|
|||
/// Dragged files dropped into egui.
|
||||
///
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
|
|
|
@ -24,23 +24,16 @@ all-features = true
|
|||
|
||||
|
||||
[features]
|
||||
default = ["clipboard", "default_fonts", "links", "persistence"]
|
||||
default = ["clipboard", "links"]
|
||||
|
||||
# enable cut/copy/paste to OS clipboard.
|
||||
# if disabled a clipboard will be simulated so you can still copy/paste within the egui app.
|
||||
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.
|
||||
links = ["egui-winit/links"]
|
||||
|
||||
# enable persisting native window options and egui memory
|
||||
persistence = ["egui-winit/persistence", "egui/persistence"]
|
||||
|
||||
# experimental support for a screen reader
|
||||
# experimental support for a screen reader.
|
||||
screen_reader = ["egui-winit/screen_reader"]
|
||||
|
||||
|
||||
|
@ -54,5 +47,6 @@ ahash = "0.7"
|
|||
bytemuck = "1.7"
|
||||
glium = "0.31"
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -5,11 +5,11 @@ All notable changes to the `egui_glow` integration will be noted in this file.
|
|||
## Unreleased
|
||||
* Improved logging on rendering failures.
|
||||
* 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)).
|
||||
* 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)).
|
||||
* Removed the features `dark-light`, `default_fonts` and `persistence` ([#1542](https://github.com/emilk/egui/pull/1542)).
|
||||
|
||||
|
||||
## 0.17.0 - 2022-02-22
|
||||
|
|
|
@ -24,45 +24,31 @@ all-features = true
|
|||
|
||||
|
||||
[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.
|
||||
clipboard = ["egui-winit?/clipboard"]
|
||||
|
||||
# detect dark mode system preference
|
||||
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"]
|
||||
|
||||
# for the winit integration:
|
||||
# enable opening links in a browser when an egui hyperlink is clicked.
|
||||
links = ["egui-winit?/links"]
|
||||
|
||||
# enable persisting native window options and egui memory
|
||||
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
|
||||
# experimental support for a screen reader.
|
||||
screen_reader = ["egui-winit?/screen_reader"]
|
||||
|
||||
# enable glutin/winit integration.
|
||||
# if you want to use glow painter on web disable this feature.
|
||||
winit = ["egui-winit", "glutin"]
|
||||
# enable profiling with the puffin crate: https://github.com/embarkstudios/puffin
|
||||
puffin = ["dep:puffin", "egui-winit?/puffin"]
|
||||
|
||||
# enable winit integration.
|
||||
winit = ["egui-winit",]
|
||||
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
|
||||
"bytemuck",
|
||||
] }
|
||||
epi = { version = "0.17.0", path = "../epi", optional = true }
|
||||
|
||||
bytemuck = "1.7"
|
||||
glow = "0.11"
|
||||
|
@ -71,13 +57,19 @@ tracing = "0.1"
|
|||
|
||||
# Native:
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
egui-winit = { version = "0.17.0", path = "../egui-winit", optional = true, default-features = false, features = [
|
||||
"epi_backend",
|
||||
] }
|
||||
glutin = { version = "0.28.0", optional = true }
|
||||
egui-winit = { version = "0.17.0", path = "../egui-winit", optional = true, default-features = false }
|
||||
puffin = { version = "0.13", optional = true }
|
||||
|
||||
# Web:
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
web-sys = { version = "0.3", features = ["console"] }
|
||||
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
|
||||
#![allow(unsafe_code)]
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
//! [`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.
|
||||
|
||||
#![allow(clippy::float_cmp)]
|
||||
|
@ -21,12 +20,6 @@ pub mod winit;
|
|||
#[cfg(all(not(target_arch = "wasm32"), feature = "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`.
|
||||
///
|
||||
/// ``` no_run
|
||||
|
|
|
@ -47,7 +47,6 @@ pub struct Painter {
|
|||
|
||||
textures: HashMap<egui::TextureId, glow::Texture>,
|
||||
|
||||
#[cfg(feature = "epi")]
|
||||
next_native_tex_id: u64, // TODO: 128-bit texture space?
|
||||
|
||||
/// Stores outdated OpenGL textures that are yet to be deleted
|
||||
|
@ -222,7 +221,6 @@ impl Painter {
|
|||
vbo,
|
||||
element_array_buffer,
|
||||
textures: Default::default(),
|
||||
#[cfg(feature = "epi")]
|
||||
next_native_tex_id: 1 << 32,
|
||||
textures_to_destroy: Vec::new(),
|
||||
destroyed: false,
|
||||
|
@ -603,6 +601,22 @@ impl Painter {
|
|||
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) {
|
||||
self.gl.delete_program(self.program);
|
||||
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(
|
||||
gl: &glow::Context,
|
||||
size_in_pixels: (u32, u32),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
pub use egui_winit;
|
||||
use egui_winit::winit;
|
||||
|
||||
/// Use [`egui`] from a [`glow`] app.
|
||||
/// Use [`egui`] from a [`glow`] app based on [`winit`].
|
||||
pub struct EguiGlow {
|
||||
pub egui_ctx: egui::Context,
|
||||
pub egui_winit: egui_winit::State,
|
||||
|
|
|
@ -60,9 +60,7 @@ ron = { version = "0.7", optional = true }
|
|||
serde = { version = "1", optional = true }
|
||||
tts = { version = "0.20", optional = true } # feature screen_reader
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.52"
|
||||
features = [
|
||||
web-sys = { version = "0.3.52", features = [
|
||||
"BinaryType",
|
||||
"Blob",
|
||||
"Clipboard",
|
||||
|
@ -103,4 +101,4 @@ features = [
|
|||
"WebGlRenderingContext",
|
||||
"WheelEvent",
|
||||
"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
|
||||
/// and start running the given app.
|
||||
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)),
|
||||
};
|
||||
|
||||
install_canvas_events(&runner_container)?;
|
||||
install_document_events(&runner_container)?;
|
||||
crate::events::install_canvas_events(&runner_container)?;
|
||||
crate::events::install_document_events(&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
|
||||
std::panic::set_hook(Box::new({
|
||||
|
@ -375,3 +426,18 @@ fn start_runner(app_runner: AppRunner) -> Result<AppRunnerRef, JsValue> {
|
|||
|
||||
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>`
|
||||
|
||||
pub mod backend;
|
||||
mod events;
|
||||
mod glow_wrapping;
|
||||
mod input;
|
||||
pub mod screen_reader;
|
||||
pub mod storage;
|
||||
mod text_agent;
|
||||
|
||||
pub use backend::*;
|
||||
pub use events::*;
|
||||
pub use storage::*;
|
||||
|
||||
use egui::mutex::{Mutex, MutexGuard};
|
||||
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<()> {
|
||||
let document = web_sys::window()?.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 {
|
||||
// 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>,
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
|
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 --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 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_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_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)
|
||||
|
|
Loading…
Reference in a new issue