mirror of
https://github.com/emilk/egui.git
synced 2026-08-30 05:10:03 -04:00
Merge branch 'master' into feat-window-actions
This commit is contained in:
@@ -5,18 +5,45 @@ NOTE: [`egui-winit`](../egui-winit/CHANGELOG.md), [`egui_glium`](../egui_glium/C
|
||||
|
||||
|
||||
## Unreleased
|
||||
* Added `NativeOptions::fullsize_content` option on Mac to build titlebar-less windows with floating window controls ([#2049](https://github.com/emilk/egui/pull/2049)).
|
||||
* ⚠️ BREAKING: `App::clear_color` now expects you to return a raw float array ([#2666](https://github.com/emilk/egui/pull/2666)).
|
||||
* The `screen_reader` feature has now been renamed `web_screen_reader` and only work on web. On other platforms, use the `accesskit` feature flag instead ([#2669](https://github.com/emilk/egui/pull/2669)).
|
||||
|
||||
#### Desktop/Native:
|
||||
* `eframe::run_native` now returns a `Result` ([#2433](https://github.com/emilk/egui/pull/2433)).
|
||||
* Update to `winit` 0.28, adding support for mac trackpad zoom ([#2654](https://github.com/emilk/egui/pull/2654)).
|
||||
|
||||
#### Web:
|
||||
* Prevent ctrl-P/cmd-P from opening the print dialog ([#2598](https://github.com/emilk/egui/pull/2598)).
|
||||
|
||||
|
||||
## 0.20.1 - 2022-12-11
|
||||
* Fix docs.rs build ([#2420](https://github.com/emilk/egui/pull/2420)).
|
||||
|
||||
|
||||
## 0.20.0 - 2022-12-08 - AccessKit integration and `wgpu` web support
|
||||
* MSRV (Minimum Supported Rust Version) is now `1.65.0` ([#2314](https://github.com/emilk/egui/pull/2314)).
|
||||
* Allow empty textures with the glow renderer.
|
||||
|
||||
#### Desktop/Native:
|
||||
* Don't repaint when just moving window ([#1980](https://github.com/emilk/egui/pull/1980)).
|
||||
* Added `NativeOptions::event_loop_builder` hook for apps to change platform specific event loop options ([#1952](https://github.com/emilk/egui/pull/1952)).
|
||||
* Enabled deferred render state initialization to support Android ([#1952](https://github.com/emilk/egui/pull/1952)).
|
||||
* Allow empty textures with the glow renderer.
|
||||
* Added `shader_version` to `NativeOptions` for cross compiling support on different target OpenGL | ES versions (on native `glow` renderer only) ([#1993](https://github.com/emilk/egui/pull/1993)).
|
||||
* Fix: app state is now saved when user presses Cmd-Q on Mac ([#2013](https://github.com/emilk/egui/pull/2013)).
|
||||
* Added `center` to `NativeOptions` and `monitor_size` to `WindowInfo` on desktop ([#2035](https://github.com/emilk/egui/pull/2035)).
|
||||
* Web: you can access your application from JS using `AppRunner::app_mut`. See `crates/egui_demo_app/src/lib.rs`.
|
||||
* Web: You can now use WebGL on top of `wgpu` by enabling the `wgpu` feature (and disabling `glow` via disabling default features) ([#2107](https://github.com/emilk/egui/pull/2107)).
|
||||
* Web: Add `WebInfo::user_agent` ([#2202](https://github.com/emilk/egui/pull/2202)).
|
||||
* Improve IME support ([#2046](https://github.com/emilk/egui/pull/2046)).
|
||||
* Added mouse-passthrough option ([#2080](https://github.com/emilk/egui/pull/2080)).
|
||||
* Added `NativeOptions::fullsize_content` option on Mac to build titlebar-less windows with floating window controls ([#2049](https://github.com/emilk/egui/pull/2049)).
|
||||
* Wgpu device/adapter/surface creation has now various configuration options exposed via `NativeOptions/WebOptions::wgpu_options` ([#2207](https://github.com/emilk/egui/pull/2207)).
|
||||
* Fix: Make sure that `native_pixels_per_point` is updated ([#2256](https://github.com/emilk/egui/pull/2256)).
|
||||
* Added optional, but enabled by default, integration with [AccessKit](https://accesskit.dev/) for implementing platform accessibility APIs ([#2294](https://github.com/emilk/egui/pull/2294)).
|
||||
* Fix: Less flickering on resize on Windows ([#2280](https://github.com/emilk/egui/pull/2280)).
|
||||
|
||||
#### Web:
|
||||
* ⚠️ BREAKING: `start_web` is a now `async` ([#2107](https://github.com/emilk/egui/pull/2107)).
|
||||
* Web: You can now use WebGL on top of `wgpu` by enabling the `wgpu` feature (and disabling `glow` via disabling default features) ([#2107](https://github.com/emilk/egui/pull/2107)).
|
||||
* Web: Add `WebInfo::user_agent` ([#2202](https://github.com/emilk/egui/pull/2202)).
|
||||
* Web: you can access your application from JS using `AppRunner::app_mut`. See `crates/egui_demo_app/src/lib.rs` ([#1886](https://github.com/emilk/egui/pull/1886)).
|
||||
|
||||
|
||||
## 0.19.0 - 2022-08-20
|
||||
@@ -108,7 +135,7 @@ NOTE: [`egui-winit`](../egui-winit/CHANGELOG.md), [`egui_glium`](../egui_glium/C
|
||||
|
||||
|
||||
## 0.15.0 - 2021-10-24
|
||||
* `Frame` now provides `set_window_title` to set window title dynamically
|
||||
* `Frame` now provides `set_window_title` to set window title dynamically ([#828](https://github.com/emilk/egui/pull/828)).
|
||||
* `Frame` now provides `set_decorations` to set whether to show window decorations.
|
||||
* Remove "http" feature (use https://github.com/emilk/ehttp instead!).
|
||||
* Added `App::persist_native_window` and `App::persist_egui_memory` to control what gets persisted.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
[package]
|
||||
name = "eframe"
|
||||
version = "0.19.0"
|
||||
version = "0.20.1"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "egui framework - write GUI apps that compiles to web and/or natively"
|
||||
edition = "2021"
|
||||
rust-version = "1.62"
|
||||
rust-version = "1.65"
|
||||
homepage = "https://github.com/emilk/egui/tree/master/crates/eframe"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "README.md"
|
||||
@@ -15,12 +15,16 @@ include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"]
|
||||
|
||||
[lib]
|
||||
|
||||
|
||||
[features]
|
||||
default = ["default_fonts", "glow"]
|
||||
default = ["accesskit", "default_fonts", "glow"]
|
||||
|
||||
## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/).
|
||||
accesskit = ["egui/accesskit", "egui-winit/accesskit"]
|
||||
|
||||
## Detect dark mode system preference using [`dark-light`](https://docs.rs/dark-light).
|
||||
##
|
||||
@@ -32,7 +36,7 @@ dark-light = ["dep:dark-light"]
|
||||
default_fonts = ["egui/default_fonts"]
|
||||
|
||||
## Use [`glow`](https://github.com/grovesNL/glow) for painting, via [`egui_glow`](https://github.com/emilk/egui/tree/master/crates/egui_glow).
|
||||
glow = ["dep:glow", "egui_glow"]
|
||||
glow = ["dep:glow", "dep:egui_glow", "dep:glutin"]
|
||||
|
||||
## Enable saving app state to disk.
|
||||
persistence = [
|
||||
@@ -49,26 +53,33 @@ persistence = [
|
||||
## `eframe` will call `puffin::GlobalProfiler::lock().new_frame()` for you
|
||||
puffin = ["dep:puffin", "egui_glow?/puffin", "egui-wgpu?/puffin"]
|
||||
|
||||
## Enable screen reader support (requires `ctx.options().screen_reader = true;`)
|
||||
screen_reader = ["egui-winit/screen_reader", "tts"]
|
||||
## Enable screen reader support (requires `ctx.options_mut(|o| o.screen_reader = true);`) on web.
|
||||
##
|
||||
## For other platforms, use the "accesskit" feature instead.
|
||||
web_screen_reader = ["tts"]
|
||||
|
||||
## If set, eframe will look for the env-var `EFRAME_SCREENSHOT_TO` and write a screenshot to that location, and then quit.
|
||||
## This is used to generate images for the examples.
|
||||
__screenshot = ["dep:image"]
|
||||
|
||||
## Use [`wgpu`](https://docs.rs/wgpu) for painting (via [`egui-wgpu`](https://github.com/emilk/egui/tree/master/crates/egui-wgpu)).
|
||||
## This overrides the `glow` feature.
|
||||
wgpu = ["dep:wgpu", "dep:egui-wgpu"]
|
||||
wgpu = ["dep:wgpu", "dep:egui-wgpu", "dep:pollster"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.19.0", path = "../egui", default-features = false, features = [
|
||||
egui = { version = "0.20.0", path = "../egui", default-features = false, features = [
|
||||
"bytemuck",
|
||||
"tracing",
|
||||
] }
|
||||
thiserror = "1.0.37"
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
|
||||
#! ### Optional dependencies
|
||||
## Enable this when generating docs.
|
||||
document-features = { version = "0.2", optional = true }
|
||||
|
||||
egui_glow = { version = "0.19.0", path = "../egui_glow", optional = true, default-features = false }
|
||||
egui_glow = { version = "0.20.0", path = "../egui_glow", optional = true, default-features = false }
|
||||
glow = { version = "0.11", optional = true }
|
||||
ron = { version = "0.8", optional = true, features = ["integer128"] }
|
||||
serde = { version = "1", optional = true, features = ["derive"] }
|
||||
@@ -76,21 +87,36 @@ serde = { version = "1", optional = true, features = ["derive"] }
|
||||
# -------------------------------------------
|
||||
# native:
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
egui-winit = { version = "0.19.0", path = "../egui-winit", default-features = false, features = [
|
||||
egui-winit = { version = "0.20.0", path = "../egui-winit", default-features = false, features = [
|
||||
"clipboard",
|
||||
"links",
|
||||
] }
|
||||
glutin = { version = "0.29.0" }
|
||||
winit = "0.27.2"
|
||||
raw-window-handle = { version = "0.5.0" }
|
||||
winit = "0.28"
|
||||
|
||||
# optional native:
|
||||
dark-light = { version = "0.2.1", optional = true }
|
||||
dark-light = { version = "1.0", optional = true }
|
||||
directories-next = { version = "2", optional = true }
|
||||
egui-wgpu = { version = "0.19.0", path = "../egui-wgpu", optional = true, features = [
|
||||
egui-wgpu = { version = "0.20.0", path = "../egui-wgpu", optional = true, features = [
|
||||
"winit",
|
||||
] } # if wgpu is used, use it with winit
|
||||
pollster = { version = "0.2", optional = true } # needed for wgpu
|
||||
|
||||
# we can expose these to user so that they can select which backends they want to enable to avoid compiling useless deps.
|
||||
# this can be done at the same time we expose x11/wayland features of winit crate.
|
||||
glutin = { version = "0.30.0", optional = true, es = [
|
||||
"egl",
|
||||
"glx",
|
||||
"x11",
|
||||
"wayland",
|
||||
"wgl",
|
||||
] }
|
||||
|
||||
image = { version = "0.24", optional = true, default-features = false, features = [
|
||||
"png",
|
||||
] }
|
||||
puffin = { version = "0.14", optional = true }
|
||||
wgpu = { version = "0.14", optional = true }
|
||||
wgpu = { version = "0.15.0", optional = true }
|
||||
|
||||
# -------------------------------------------
|
||||
# web:
|
||||
@@ -98,7 +124,7 @@ wgpu = { version = "0.14", optional = true }
|
||||
bytemuck = "1.7"
|
||||
js-sys = "0.3"
|
||||
percent-encoding = "2.1"
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen = "=0.2.83"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
web-sys = { version = "0.3.58", features = [
|
||||
"BinaryType",
|
||||
@@ -144,6 +170,6 @@ web-sys = { version = "0.3.58", features = [
|
||||
] }
|
||||
|
||||
# optional web:
|
||||
egui-wgpu = { version = "0.19.0", path = "../egui-wgpu", optional = true } # if wgpu is used, use it without (!) winit
|
||||
tts = { version = "0.24", optional = true }
|
||||
wgpu = { version = "0.14", optional = true, features = ["webgl"] }
|
||||
egui-wgpu = { version = "0.20.0", path = "../egui-wgpu", optional = true } # if wgpu is used, use it without (!) winit
|
||||
tts = { version = "0.25", optional = true, default-features = false }
|
||||
wgpu = { version = "0.15.0", optional = true, features = ["webgl"] }
|
||||
|
||||
@@ -45,7 +45,6 @@ You can also use `egui_glow` and [`winit`](https://github.com/rust-windowing/win
|
||||
* Mobile text editing is not as good as for a normal web app.
|
||||
* Accessibility: There is an experimental screen reader for `eframe`, but it has to be enabled explicitly. There is no JS function to ask "Does the user want a screen reader?" (and there should probably not be such a function, due to user tracking/integrity concerns).
|
||||
* No integration with browser settings for colors and fonts.
|
||||
* On Linux and Mac, Firefox will copy the WebGL render target from GPU, to CPU and then back again (https://bugzilla.mozilla.org/show_bug.cgi?id=1010527#c0), slowing down egui.
|
||||
|
||||
In many ways, `eframe` is trying to make the browser do something it wasn't designed to do (though there are many things browser vendors could do to improve how well libraries like egui work).
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
use std::any::Any;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use crate::native::run::RequestRepaintEvent;
|
||||
pub use crate::native::run::UserEvent;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use winit::event_loop::EventLoopBuilder;
|
||||
@@ -20,7 +20,7 @@ pub use winit::event_loop::EventLoopBuilder;
|
||||
/// You can configure any platform specific details required on top of the default configuration
|
||||
/// done by `EFrame`.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub type EventLoopBuilderHook = Box<dyn FnOnce(&mut EventLoopBuilder<RequestRepaintEvent>)>;
|
||||
pub type EventLoopBuilderHook = Box<dyn FnOnce(&mut EventLoopBuilder<UserEvent>)>;
|
||||
|
||||
/// This is how your app is created.
|
||||
///
|
||||
@@ -74,7 +74,7 @@ pub trait App {
|
||||
///
|
||||
/// Can be used from web to interact or other external context.
|
||||
///
|
||||
/// You need to implement this if you want to be able to access the application from JS using [`AppRunner::app_mut`].
|
||||
/// You need to implement this if you want to be able to access the application from JS using [`crate::web::backend::AppRunner`].
|
||||
///
|
||||
/// This is needed because downcasting `Box<dyn App>` -> `Box<dyn Any>` to get &`ConcreteApp` is not simple in current rust.
|
||||
///
|
||||
@@ -145,20 +145,25 @@ pub trait App {
|
||||
/// The size limit of the web app canvas.
|
||||
///
|
||||
/// By default the max size is [`egui::Vec2::INFINITY`], i.e. unlimited.
|
||||
///
|
||||
/// A large canvas can lead to bad frame rates on some older browsers on some platforms
|
||||
/// (see <https://bugzilla.mozilla.org/show_bug.cgi?id=1010527#c0>).
|
||||
fn max_size_points(&self) -> egui::Vec2 {
|
||||
egui::Vec2::INFINITY
|
||||
}
|
||||
|
||||
/// Background color for the app, e.g. what is sent to `gl.clearColor`.
|
||||
/// Background color values for the app, e.g. what is sent to `gl.clearColor`.
|
||||
///
|
||||
/// This is the background of your windows if you don't set a central panel.
|
||||
fn clear_color(&self, _visuals: &egui::Visuals) -> egui::Rgba {
|
||||
///
|
||||
/// ATTENTION:
|
||||
/// Since these float values go to the render as-is, any color space conversion as done
|
||||
/// e.g. by converting from [`egui::Color32`] to [`egui::Rgba`] may cause incorrect results.
|
||||
/// egui recommends that rendering backends use a normal "gamma-space" (non-sRGB-aware) blending,
|
||||
/// which means the values you return here should also be in `sRGB` gamma-space in the 0-1 range.
|
||||
/// You can use [`egui::Color32::to_normalized_gamma_f32`] for this.
|
||||
fn clear_color(&self, _visuals: &egui::Visuals) -> [f32; 4] {
|
||||
// NOTE: a bright gray makes the shadows of the windows look weird.
|
||||
// We use a bit of transparency so that if the user switches on the
|
||||
// `transparent()` option they get immediate results.
|
||||
egui::Color32::from_rgba_unmultiplied(12, 12, 12, 180).into()
|
||||
egui::Color32::from_rgba_unmultiplied(12, 12, 12, 180).to_normalized_gamma_f32()
|
||||
|
||||
// _visuals.window_fill() would also be a natural choice
|
||||
}
|
||||
@@ -176,7 +181,7 @@ pub trait App {
|
||||
}
|
||||
|
||||
/// If `true` a warm-up call to [`Self::update`] will be issued where
|
||||
/// `ctx.memory().everything_is_visible()` will be set to `true`.
|
||||
/// `ctx.memory(|mem| mem.everything_is_visible())` will be set to `true`.
|
||||
///
|
||||
/// This can help pre-caching resources loaded by different parts of the UI, preventing stutter later on.
|
||||
///
|
||||
@@ -258,10 +263,10 @@ pub struct NativeOptions {
|
||||
/// The initial inner size of the native window in points (logical pixels).
|
||||
pub initial_window_size: Option<egui::Vec2>,
|
||||
|
||||
/// The minimum inner window size
|
||||
/// The minimum inner window size in points (logical pixels).
|
||||
pub min_window_size: Option<egui::Vec2>,
|
||||
|
||||
/// The maximum inner window size
|
||||
/// The maximum inner window size in points (logical pixels).
|
||||
pub max_window_size: Option<egui::Vec2>,
|
||||
|
||||
/// Should the app window be resizable?
|
||||
@@ -432,6 +437,7 @@ impl NativeOptions {
|
||||
match dark_light::detect() {
|
||||
dark_light::Mode::Dark => Some(Theme::Dark),
|
||||
dark_light::Mode::Light => Some(Theme::Light),
|
||||
dark_light::Mode::Default => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
@@ -708,6 +714,7 @@ impl Frame {
|
||||
#[doc(alias = "exit")]
|
||||
#[doc(alias = "quit")]
|
||||
pub fn close(&mut self) {
|
||||
tracing::debug!("eframe::Frame::close called");
|
||||
self.output.close = true;
|
||||
}
|
||||
|
||||
@@ -734,6 +741,7 @@ impl Frame {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn set_window_size(&mut self, size: egui::Vec2) {
|
||||
self.output.window_size = Some(size);
|
||||
self.info.window_info.size = size; // so that subsequent calls see the updated value
|
||||
}
|
||||
|
||||
/// Set the desired title of the window.
|
||||
@@ -754,12 +762,14 @@ impl Frame {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn set_fullscreen(&mut self, fullscreen: bool) {
|
||||
self.output.fullscreen = Some(fullscreen);
|
||||
self.info.window_info.fullscreen = fullscreen; // so that subsequent calls see the updated value
|
||||
}
|
||||
|
||||
/// set the position of the outer window.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn set_window_pos(&mut self, pos: egui::Pos2) {
|
||||
self.output.window_pos = Some(pos);
|
||||
self.info.window_info.position = Some(pos); // so that subsequent calls see the updated value
|
||||
}
|
||||
|
||||
/// When called, the native window will follow the
|
||||
|
||||
@@ -103,9 +103,20 @@ pub use web_sys;
|
||||
/// /// You can add more callbacks like this if you want to call in to your code.
|
||||
/// #[cfg(target_arch = "wasm32")]
|
||||
/// #[wasm_bindgen]
|
||||
/// pub async fn start(canvas_id: &str) -> Result<AppRunnerRef>, eframe::wasm_bindgen::JsValue> {
|
||||
/// pub struct WebHandle {
|
||||
/// handle: AppRunnerRef,
|
||||
/// }
|
||||
/// #[cfg(target_arch = "wasm32")]
|
||||
/// #[wasm_bindgen]
|
||||
/// pub async fn start(canvas_id: &str) -> Result<WebHandle, eframe::wasm_bindgen::JsValue> {
|
||||
/// let web_options = eframe::WebOptions::default();
|
||||
/// eframe::start_web(canvas_id, web_options, Box::new(|cc| Box::new(MyEguiApp::new(cc)))).await
|
||||
/// eframe::start_web(
|
||||
/// canvas_id,
|
||||
/// web_options,
|
||||
/// Box::new(|cc| Box::new(MyEguiApp::new(cc))),
|
||||
/// )
|
||||
/// .await
|
||||
/// .map(|handle| WebHandle { handle })
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
@@ -116,7 +127,7 @@ pub async fn start_web(
|
||||
canvas_id: &str,
|
||||
web_options: WebOptions,
|
||||
app_creator: AppCreator,
|
||||
) -> Result<AppRunnerRef, wasm_bindgen::JsValue> {
|
||||
) -> std::result::Result<AppRunnerRef, wasm_bindgen::JsValue> {
|
||||
let handle = web::start(canvas_id, web_options, app_creator).await?;
|
||||
|
||||
Ok(handle)
|
||||
@@ -163,26 +174,63 @@ mod native;
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Errors
|
||||
/// This function can fail if we fail to set up a graphics context.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[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,
|
||||
) -> Result<()> {
|
||||
let renderer = native_options.renderer;
|
||||
|
||||
#[cfg(not(feature = "__screenshot"))]
|
||||
assert!(
|
||||
std::env::var("EFRAME_SCREENSHOT_TO").is_err(),
|
||||
"EFRAME_SCREENSHOT_TO found without compiling with the '__screenshot' feature"
|
||||
);
|
||||
|
||||
match renderer {
|
||||
#[cfg(feature = "glow")]
|
||||
Renderer::Glow => {
|
||||
tracing::debug!("Using the glow renderer");
|
||||
native::run::run_glow(app_name, native_options, app_creator);
|
||||
native::run::run_glow(app_name, native_options, app_creator)
|
||||
}
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
Renderer::Wgpu => {
|
||||
tracing::debug!("Using the wgpu renderer");
|
||||
native::run::run_wgpu(app_name, native_options, app_creator);
|
||||
native::run::run_wgpu(app_name, native_options, app_creator)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// The different problems that can occur when trying to run `eframe`.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[error("winit error: {0}")]
|
||||
Winit(#[from] winit::error::OsError),
|
||||
|
||||
#[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
|
||||
#[error("glutin error: {0}")]
|
||||
Glutin(#[from] glutin::error::Error),
|
||||
|
||||
#[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
|
||||
#[error("Found no glutin configs matching the template: {0:?}")]
|
||||
NoGlutinConfigs(glutin::config::ConfigTemplate),
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
#[error("WGPU error: {0}")]
|
||||
Wgpu(#[from] egui_wgpu::WgpuError),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Profiling macro for feature "puffin"
|
||||
|
||||
@@ -3,6 +3,11 @@ use winit::event_loop::EventLoopWindowTarget;
|
||||
#[cfg(target_os = "macos")]
|
||||
use winit::platform::macos::WindowBuilderExtMacOS as _;
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
use egui::accesskit;
|
||||
use egui::NumExt as _;
|
||||
#[cfg(feature = "accesskit")]
|
||||
use egui_winit::accesskit_winit;
|
||||
use egui_winit::{native_pixels_per_point, EventResponse, WindowSettings};
|
||||
|
||||
use crate::{epi, Theme, WindowInfo};
|
||||
@@ -44,10 +49,12 @@ pub fn read_window_info(window: &winit::window::Window, pixels_per_point: f32) -
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_builder(
|
||||
pub fn build_window<E>(
|
||||
event_loop: &EventLoopWindowTarget<E>,
|
||||
title: &str,
|
||||
native_options: &epi::NativeOptions,
|
||||
window_settings: &Option<WindowSettings>,
|
||||
) -> winit::window::WindowBuilder {
|
||||
window_settings: Option<WindowSettings>,
|
||||
) -> Result<winit::window::Window, winit::error::OsError> {
|
||||
let epi::NativeOptions {
|
||||
always_on_top,
|
||||
maximized,
|
||||
@@ -63,19 +70,23 @@ pub fn window_builder(
|
||||
max_window_size,
|
||||
resizable,
|
||||
transparent,
|
||||
centered,
|
||||
..
|
||||
} = native_options;
|
||||
|
||||
let window_icon = icon_data.clone().and_then(load_icon);
|
||||
|
||||
let mut window_builder = winit::window::WindowBuilder::new()
|
||||
.with_always_on_top(*always_on_top)
|
||||
.with_title(title)
|
||||
.with_decorations(*decorated)
|
||||
.with_fullscreen(fullscreen.then(|| winit::window::Fullscreen::Borderless(None)))
|
||||
.with_maximized(*maximized)
|
||||
.with_resizable(*resizable)
|
||||
.with_transparent(*transparent)
|
||||
.with_window_icon(window_icon);
|
||||
.with_window_icon(window_icon)
|
||||
// Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
|
||||
// We must also keep the window hidden until AccessKit is initialized.
|
||||
.with_visible(false);
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
if *fullsize_content {
|
||||
@@ -94,21 +105,69 @@ pub fn window_builder(
|
||||
|
||||
window_builder = window_builder_drag_and_drop(window_builder, *drag_and_drop_support);
|
||||
|
||||
if let Some(window_settings) = window_settings {
|
||||
let inner_size_points = if let Some(mut window_settings) = window_settings {
|
||||
// Restore pos/size from previous session
|
||||
window_settings.clamp_to_sane_values(largest_monitor_point_size(event_loop));
|
||||
window_builder = window_settings.initialize_window(window_builder);
|
||||
window_settings.inner_size_points()
|
||||
} else {
|
||||
if let Some(pos) = *initial_window_pos {
|
||||
window_builder = window_builder.with_position(winit::dpi::PhysicalPosition {
|
||||
window_builder = window_builder.with_position(winit::dpi::LogicalPosition {
|
||||
x: pos.x as f64,
|
||||
y: pos.y as f64,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(initial_window_size) = *initial_window_size {
|
||||
let initial_window_size =
|
||||
initial_window_size.at_most(largest_monitor_point_size(event_loop));
|
||||
window_builder = window_builder.with_inner_size(points_to_size(initial_window_size));
|
||||
}
|
||||
|
||||
*initial_window_size
|
||||
};
|
||||
|
||||
if *centered {
|
||||
if let Some(monitor) = event_loop.available_monitors().next() {
|
||||
let monitor_size = monitor.size();
|
||||
let inner_size = inner_size_points.unwrap_or(egui::Vec2 { x: 800.0, y: 600.0 });
|
||||
if monitor_size.width > 0 && monitor_size.height > 0 {
|
||||
let x = (monitor_size.width - inner_size.x as u32) / 2;
|
||||
let y = (monitor_size.height - inner_size.y as u32) / 2;
|
||||
window_builder = window_builder.with_position(winit::dpi::LogicalPosition {
|
||||
x: x as f64,
|
||||
y: y as f64,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window_builder
|
||||
let window = window_builder.build(event_loop)?;
|
||||
|
||||
use winit::window::WindowLevel;
|
||||
window.set_window_level(if *always_on_top {
|
||||
WindowLevel::AlwaysOnTop
|
||||
} else {
|
||||
WindowLevel::Normal
|
||||
});
|
||||
|
||||
Ok(window)
|
||||
}
|
||||
|
||||
fn largest_monitor_point_size<E>(event_loop: &EventLoopWindowTarget<E>) -> egui::Vec2 {
|
||||
let mut max_size = egui::Vec2::ZERO;
|
||||
|
||||
for monitor in event_loop.available_monitors() {
|
||||
let size = monitor.size().to_logical::<f32>(monitor.scale_factor());
|
||||
let size = egui::vec2(size.width, size.height);
|
||||
max_size = max_size.max(size);
|
||||
}
|
||||
|
||||
if max_size == egui::Vec2::ZERO {
|
||||
egui::Vec2::splat(16000.0)
|
||||
} else {
|
||||
max_size
|
||||
}
|
||||
}
|
||||
|
||||
fn load_icon(icon_data: epi::IconData) -> Option<winit::window::Icon> {
|
||||
@@ -167,7 +226,7 @@ pub fn handle_app_output(
|
||||
}
|
||||
|
||||
if let Some(fullscreen) = fullscreen {
|
||||
window.set_fullscreen(fullscreen.then(|| winit::window::Fullscreen::Borderless(None)));
|
||||
window.set_fullscreen(fullscreen.then_some(winit::window::Fullscreen::Borderless(None)));
|
||||
}
|
||||
|
||||
if let Some(window_title) = window_title {
|
||||
@@ -186,7 +245,12 @@ pub fn handle_app_output(
|
||||
}
|
||||
|
||||
if let Some(always_on_top) = always_on_top {
|
||||
window.set_always_on_top(always_on_top);
|
||||
use winit::window::WindowLevel;
|
||||
window.set_window_level(if always_on_top {
|
||||
WindowLevel::AlwaysOnTop
|
||||
} else {
|
||||
WindowLevel::Normal
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(minimized) = minimized {
|
||||
@@ -235,7 +299,8 @@ impl EpiIntegration {
|
||||
) -> Self {
|
||||
let egui_ctx = egui::Context::default();
|
||||
|
||||
*egui_ctx.memory() = load_egui_memory(storage.as_deref()).unwrap_or_default();
|
||||
let memory = load_egui_memory(storage.as_deref()).unwrap_or_default();
|
||||
egui_ctx.memory_mut(|mem| *mem = memory);
|
||||
|
||||
let native_pixels_per_point = window.scale_factor() as f32;
|
||||
|
||||
@@ -272,13 +337,33 @@ impl EpiIntegration {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
pub fn init_accesskit<E: From<accesskit_winit::ActionRequestEvent> + Send>(
|
||||
&mut self,
|
||||
window: &winit::window::Window,
|
||||
event_loop_proxy: winit::event_loop::EventLoopProxy<E>,
|
||||
) {
|
||||
let egui_ctx = self.egui_ctx.clone();
|
||||
self.egui_winit
|
||||
.init_accesskit(window, event_loop_proxy, move || {
|
||||
// This function is called when an accessibility client
|
||||
// (e.g. screen reader) makes its first request. If we got here,
|
||||
// we know that an accessibility tree is actually wanted.
|
||||
egui_ctx.enable_accesskit();
|
||||
// Enqueue a repaint so we'll receive a full tree update soon.
|
||||
egui_ctx.request_repaint();
|
||||
egui::accesskit_placeholder_tree_update()
|
||||
});
|
||||
}
|
||||
|
||||
pub fn warm_up(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
|
||||
crate::profile_function!();
|
||||
let saved_memory: egui::Memory = self.egui_ctx.memory().clone();
|
||||
self.egui_ctx.memory().set_everything_is_visible(true);
|
||||
let saved_memory: egui::Memory = self.egui_ctx.memory(|mem| mem.clone());
|
||||
self.egui_ctx
|
||||
.memory_mut(|mem| mem.set_everything_is_visible(true));
|
||||
let full_output = self.update(app, window);
|
||||
self.pending_full_output.append(full_output); // Handle it next frame
|
||||
*self.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge.
|
||||
self.egui_ctx.memory_mut(|mem| *mem = saved_memory); // We don't want to remember that windows were huge.
|
||||
self.egui_ctx.clear_animations();
|
||||
}
|
||||
|
||||
@@ -295,8 +380,15 @@ impl EpiIntegration {
|
||||
use winit::event::{ElementState, MouseButton, WindowEvent};
|
||||
|
||||
match event {
|
||||
WindowEvent::CloseRequested => self.close = app.on_close_event(),
|
||||
WindowEvent::Destroyed => self.close = true,
|
||||
WindowEvent::CloseRequested => {
|
||||
tracing::debug!("Received WindowEvent::CloseRequested");
|
||||
self.close = app.on_close_event();
|
||||
tracing::debug!("App::on_close_event returned {}", self.close);
|
||||
}
|
||||
WindowEvent::Destroyed => {
|
||||
tracing::debug!("Received WindowEvent::Destroyed");
|
||||
self.close = true;
|
||||
}
|
||||
WindowEvent::MouseInput {
|
||||
button: MouseButton::Left,
|
||||
state: ElementState::Pressed,
|
||||
@@ -311,6 +403,11 @@ impl EpiIntegration {
|
||||
self.egui_winit.on_event(&self.egui_ctx, event)
|
||||
}
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
pub fn on_accesskit_action_request(&mut self, request: accesskit::ActionRequest) {
|
||||
self.egui_winit.on_accesskit_action_request(request);
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
app: &mut dyn epi::App,
|
||||
@@ -333,12 +430,13 @@ impl EpiIntegration {
|
||||
self.can_drag_window = false;
|
||||
if app_output.close {
|
||||
self.close = app.on_close_event();
|
||||
tracing::debug!("App::on_close_event returned {}", self.close);
|
||||
}
|
||||
self.frame.output.visible = app_output.visible; // this is handled by post_present
|
||||
handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
||||
}
|
||||
|
||||
let frame_time = (std::time::Instant::now() - frame_start).as_secs_f64() as f32;
|
||||
let frame_time = frame_start.elapsed().as_secs_f64() as f32;
|
||||
self.frame.info.cpu_usage = Some(frame_time);
|
||||
|
||||
full_output
|
||||
@@ -392,7 +490,8 @@ impl EpiIntegration {
|
||||
}
|
||||
if _app.persist_egui_memory() {
|
||||
crate::profile_scope!("egui_memory");
|
||||
epi::set_value(storage, STORAGE_EGUI_MEMORY_KEY, &*self.egui_ctx.memory());
|
||||
self.egui_ctx
|
||||
.memory(|mem| epi::set_value(storage, STORAGE_EGUI_MEMORY_KEY, mem));
|
||||
}
|
||||
{
|
||||
crate::profile_scope!("App::save");
|
||||
|
||||
@@ -26,6 +26,7 @@ impl FileStorage {
|
||||
/// Store the state in this .ron file.
|
||||
pub fn from_ron_filepath(ron_filepath: impl Into<PathBuf>) -> Self {
|
||||
let ron_filepath: PathBuf = ron_filepath.into();
|
||||
tracing::debug!("Loading app state from {:?}…", ron_filepath);
|
||||
Self {
|
||||
kv: read_ron(&ron_filepath).unwrap_or_default(),
|
||||
ron_filepath,
|
||||
|
||||
@@ -1,19 +1,35 @@
|
||||
//! Note that this file contains two similar paths - one for [`glow`], one for [`wgpu`].
|
||||
//! When making changes to one you often also want to apply it to the other.
|
||||
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use egui_winit::winit;
|
||||
use winit::event_loop::{
|
||||
ControlFlow, EventLoop, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget,
|
||||
};
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
use egui_winit::accesskit_winit;
|
||||
use egui_winit::winit;
|
||||
|
||||
use crate::{epi, Result};
|
||||
|
||||
use super::epi_integration::{self, EpiIntegration};
|
||||
use crate::epi;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RequestRepaintEvent;
|
||||
pub enum UserEvent {
|
||||
RequestRepaint,
|
||||
#[cfg(feature = "accesskit")]
|
||||
AccessKitActionRequest(accesskit_winit::ActionRequestEvent),
|
||||
}
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
impl From<accesskit_winit::ActionRequestEvent> for UserEvent {
|
||||
fn from(inner: accesskit_winit::ActionRequestEvent) -> Self {
|
||||
Self::AccessKitActionRequest(inner)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
@@ -45,14 +61,14 @@ trait WinitApp {
|
||||
fn paint(&mut self) -> EventResult;
|
||||
fn on_event(
|
||||
&mut self,
|
||||
event_loop: &EventLoopWindowTarget<RequestRepaintEvent>,
|
||||
event: &winit::event::Event<'_, RequestRepaintEvent>,
|
||||
) -> EventResult;
|
||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
event: &winit::event::Event<'_, UserEvent>,
|
||||
) -> Result<EventResult>;
|
||||
}
|
||||
|
||||
fn create_event_loop_builder(
|
||||
native_options: &mut epi::NativeOptions,
|
||||
) -> EventLoopBuilder<RequestRepaintEvent> {
|
||||
) -> EventLoopBuilder<UserEvent> {
|
||||
let mut event_loop_builder = winit::event_loop::EventLoopBuilder::with_user_event();
|
||||
|
||||
if let Some(hook) = std::mem::take(&mut native_options.event_loop_builder) {
|
||||
@@ -66,12 +82,12 @@ fn create_event_loop_builder(
|
||||
///
|
||||
/// We reuse the event-loop so we can support closing and opening an eframe window
|
||||
/// multiple times. This is just a limitation of winit.
|
||||
fn with_event_loop(
|
||||
fn with_event_loop<R>(
|
||||
mut native_options: epi::NativeOptions,
|
||||
f: impl FnOnce(&mut EventLoop<RequestRepaintEvent>, NativeOptions),
|
||||
) {
|
||||
f: impl FnOnce(&mut EventLoop<UserEvent>, NativeOptions) -> R,
|
||||
) -> R {
|
||||
use std::cell::RefCell;
|
||||
thread_local!(static EVENT_LOOP: RefCell<Option<EventLoop<RequestRepaintEvent>>> = RefCell::new(None));
|
||||
thread_local!(static EVENT_LOOP: RefCell<Option<EventLoop<UserEvent>>> = RefCell::new(None));
|
||||
|
||||
EVENT_LOOP.with(|event_loop| {
|
||||
// Since we want to reference NativeOptions when creating the EventLoop we can't
|
||||
@@ -80,22 +96,31 @@ fn with_event_loop(
|
||||
let mut event_loop = event_loop.borrow_mut();
|
||||
let event_loop = event_loop
|
||||
.get_or_insert_with(|| create_event_loop_builder(&mut native_options).build());
|
||||
f(event_loop, native_options);
|
||||
});
|
||||
f(event_loop, native_options)
|
||||
})
|
||||
}
|
||||
|
||||
fn run_and_return(event_loop: &mut EventLoop<RequestRepaintEvent>, mut winit_app: impl WinitApp) {
|
||||
fn run_and_return(
|
||||
event_loop: &mut EventLoop<UserEvent>,
|
||||
mut winit_app: impl WinitApp,
|
||||
) -> Result<()> {
|
||||
use winit::platform::run_return::EventLoopExtRunReturn as _;
|
||||
|
||||
tracing::debug!("event_loop.run_return");
|
||||
tracing::debug!("Entering the winit event loop (run_return)…");
|
||||
|
||||
let mut next_repaint_time = Instant::now();
|
||||
|
||||
let mut returned_result = Ok(());
|
||||
|
||||
event_loop.run_return(|event, event_loop, control_flow| {
|
||||
let event_result = match &event {
|
||||
winit::event::Event::LoopDestroyed => {
|
||||
tracing::debug!("winit::event::Event::LoopDestroyed");
|
||||
EventResult::Exit
|
||||
// On Mac, Cmd-Q we get here and then `run_return` doesn't return (despite its name),
|
||||
// so we need to save state now:
|
||||
tracing::debug!("Received Event::LoopDestroyed - saving app state…");
|
||||
winit_app.save_and_destroy();
|
||||
*control_flow = ControlFlow::Exit;
|
||||
return;
|
||||
}
|
||||
|
||||
// Platform-dependent event handlers to workaround a winit bug
|
||||
@@ -110,7 +135,7 @@ fn run_and_return(event_loop: &mut EventLoop<RequestRepaintEvent>, mut winit_app
|
||||
winit_app.paint()
|
||||
}
|
||||
|
||||
winit::event::Event::UserEvent(RequestRepaintEvent)
|
||||
winit::event::Event::UserEvent(UserEvent::RequestRepaint)
|
||||
| winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
|
||||
..
|
||||
}) => EventResult::RepaintNext,
|
||||
@@ -124,15 +149,28 @@ fn run_and_return(event_loop: &mut EventLoop<RequestRepaintEvent>, mut winit_app
|
||||
EventResult::Wait
|
||||
}
|
||||
|
||||
event => winit_app.on_event(event_loop, event),
|
||||
event => match winit_app.on_event(event_loop, event) {
|
||||
Ok(event_result) => event_result,
|
||||
Err(err) => {
|
||||
returned_result = Err(err);
|
||||
tracing::debug!("Exiting because of an error");
|
||||
EventResult::Exit
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
match event_result {
|
||||
EventResult::Wait => {}
|
||||
EventResult::RepaintNow => {
|
||||
tracing::trace!("Repaint caused by winit::Event: {:?}", event);
|
||||
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
||||
winit_app.paint();
|
||||
if cfg!(windows) {
|
||||
// Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280
|
||||
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
||||
winit_app.paint();
|
||||
} else {
|
||||
// Fix for https://github.com/emilk/egui/issues/2425
|
||||
next_repaint_time = Instant::now();
|
||||
}
|
||||
}
|
||||
EventResult::RepaintNext => {
|
||||
tracing::trace!("Repaint caused by winit::Event: {:?}", event);
|
||||
@@ -142,10 +180,7 @@ fn run_and_return(event_loop: &mut EventLoop<RequestRepaintEvent>, mut winit_app
|
||||
next_repaint_time = next_repaint_time.min(repaint_time);
|
||||
}
|
||||
EventResult::Exit => {
|
||||
// On Cmd-Q we get here and then `run_return` doesn't return,
|
||||
// so we need to save state now:
|
||||
tracing::debug!("Exiting event loop - saving app state…");
|
||||
winit_app.save_and_destroy();
|
||||
tracing::debug!("Asking to exit event loop…");
|
||||
*control_flow = ControlFlow::Exit;
|
||||
return;
|
||||
}
|
||||
@@ -174,19 +209,21 @@ fn run_and_return(event_loop: &mut EventLoop<RequestRepaintEvent>, mut winit_app
|
||||
event_loop.run_return(|_, _, control_flow| {
|
||||
control_flow.set_exit();
|
||||
});
|
||||
|
||||
returned_result
|
||||
}
|
||||
|
||||
fn run_and_exit(
|
||||
event_loop: EventLoop<RequestRepaintEvent>,
|
||||
mut winit_app: impl WinitApp + 'static,
|
||||
) -> ! {
|
||||
tracing::debug!("event_loop.run");
|
||||
fn run_and_exit(event_loop: EventLoop<UserEvent>, mut winit_app: impl WinitApp + 'static) -> ! {
|
||||
tracing::debug!("Entering the winit event loop (run)…");
|
||||
|
||||
let mut next_repaint_time = Instant::now();
|
||||
|
||||
event_loop.run(move |event, event_loop, control_flow| {
|
||||
let event_result = match event {
|
||||
winit::event::Event::LoopDestroyed => EventResult::Exit,
|
||||
winit::event::Event::LoopDestroyed => {
|
||||
tracing::debug!("Received Event::LoopDestroyed");
|
||||
EventResult::Exit
|
||||
}
|
||||
|
||||
// Platform-dependent event handlers to workaround a winit bug
|
||||
// See: https://github.com/rust-windowing/winit/issues/987
|
||||
@@ -200,19 +237,30 @@ fn run_and_exit(
|
||||
winit_app.paint()
|
||||
}
|
||||
|
||||
winit::event::Event::UserEvent(RequestRepaintEvent)
|
||||
winit::event::Event::UserEvent(UserEvent::RequestRepaint)
|
||||
| winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
|
||||
..
|
||||
}) => EventResult::RepaintNext,
|
||||
|
||||
event => winit_app.on_event(event_loop, &event),
|
||||
event => match winit_app.on_event(event_loop, &event) {
|
||||
Ok(event_result) => event_result,
|
||||
Err(err) => {
|
||||
panic!("eframe encountered a fatal error: {err}");
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
match event_result {
|
||||
EventResult::Wait => {}
|
||||
EventResult::RepaintNow => {
|
||||
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
||||
winit_app.paint();
|
||||
if cfg!(windows) {
|
||||
// Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280
|
||||
next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000);
|
||||
winit_app.paint();
|
||||
} else {
|
||||
// Fix for https://github.com/emilk/egui/issues/2425
|
||||
next_repaint_time = Instant::now();
|
||||
}
|
||||
}
|
||||
EventResult::RepaintNext => {
|
||||
next_repaint_time = Instant::now();
|
||||
@@ -221,7 +269,7 @@ fn run_and_exit(
|
||||
next_repaint_time = next_repaint_time.min(repaint_time);
|
||||
}
|
||||
EventResult::Exit => {
|
||||
tracing::debug!("Quitting…");
|
||||
tracing::debug!("Quitting - saving app state…");
|
||||
winit_app.save_and_destroy();
|
||||
#[allow(clippy::exit)]
|
||||
std::process::exit(0);
|
||||
@@ -242,33 +290,14 @@ fn run_and_exit(
|
||||
})
|
||||
}
|
||||
|
||||
fn centere_window_pos(
|
||||
monitor: Option<winit::monitor::MonitorHandle>,
|
||||
native_options: &mut epi::NativeOptions,
|
||||
) {
|
||||
// Get the current_monitor.
|
||||
if let Some(monitor) = monitor {
|
||||
let monitor_size = monitor.size();
|
||||
let inner_size = native_options
|
||||
.initial_window_size
|
||||
.unwrap_or(egui::Vec2 { x: 800.0, y: 600.0 });
|
||||
if monitor_size.width > 0 && monitor_size.height > 0 {
|
||||
let x = (monitor_size.width - inner_size.x as u32) / 2;
|
||||
let y = (monitor_size.height - inner_size.y as u32) / 2;
|
||||
native_options.initial_window_pos = Some(egui::Pos2 {
|
||||
x: x as _,
|
||||
y: y as _,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/// Run an egui app
|
||||
#[cfg(feature = "glow")]
|
||||
mod glow_integration {
|
||||
use std::sync::Arc;
|
||||
|
||||
use egui::NumExt as _;
|
||||
|
||||
use super::*;
|
||||
|
||||
// Note: that the current Glutin API design tightly couples the GL context with
|
||||
@@ -295,11 +324,134 @@ mod glow_integration {
|
||||
|
||||
// Conceptually this will be split out eventually so that the rest of the state
|
||||
// can be persistent.
|
||||
gl_window: glutin::WindowedContext<glutin::PossiblyCurrent>,
|
||||
gl_window: GlutinWindowContext,
|
||||
}
|
||||
struct GlutinWindowContext {
|
||||
window: winit::window::Window,
|
||||
gl_context: glutin::context::PossiblyCurrentContext,
|
||||
gl_display: glutin::display::Display,
|
||||
gl_surface: glutin::surface::Surface<glutin::surface::WindowSurface>,
|
||||
}
|
||||
|
||||
impl GlutinWindowContext {
|
||||
// refactor this function to use `glutin-winit` crate eventually.
|
||||
// preferably add android support at the same time.
|
||||
#[allow(unsafe_code)]
|
||||
unsafe fn new(
|
||||
winit_window: winit::window::Window,
|
||||
native_options: &epi::NativeOptions,
|
||||
) -> Result<Self> {
|
||||
use glutin::prelude::*;
|
||||
use raw_window_handle::*;
|
||||
let hardware_acceleration = match native_options.hardware_acceleration {
|
||||
crate::HardwareAcceleration::Required => Some(true),
|
||||
crate::HardwareAcceleration::Preferred => None,
|
||||
crate::HardwareAcceleration::Off => Some(false),
|
||||
};
|
||||
|
||||
let raw_display_handle = winit_window.raw_display_handle();
|
||||
let raw_window_handle = winit_window.raw_window_handle();
|
||||
|
||||
// EGL is crossplatform and the official khronos way
|
||||
// but sometimes platforms/drivers may not have it, so we use back up options where possible.
|
||||
// TODO: check whether we can expose these options as "features", so that users can select the relevant backend they want.
|
||||
|
||||
// try egl and fallback to windows wgl. Windows is the only platform that *requires* window handle to create display.
|
||||
#[cfg(target_os = "windows")]
|
||||
let preference =
|
||||
glutin::display::DisplayApiPreference::EglThenWgl(Some(raw_window_handle));
|
||||
// try egl and fallback to x11 glx
|
||||
#[cfg(target_os = "linux")]
|
||||
let preference = glutin::display::DisplayApiPreference::EglThenGlx(Box::new(
|
||||
winit::platform::x11::register_xlib_error_hook,
|
||||
));
|
||||
#[cfg(target_os = "macos")]
|
||||
let preference = glutin::display::DisplayApiPreference::Cgl;
|
||||
#[cfg(target_os = "android")]
|
||||
let preference = glutin::display::DisplayApiPreference::Egl;
|
||||
|
||||
let gl_display = glutin::display::Display::new(raw_display_handle, preference)?;
|
||||
let swap_interval = if native_options.vsync {
|
||||
glutin::surface::SwapInterval::Wait(std::num::NonZeroU32::new(1).unwrap())
|
||||
} else {
|
||||
glutin::surface::SwapInterval::DontWait
|
||||
};
|
||||
|
||||
let config_template = glutin::config::ConfigTemplateBuilder::new()
|
||||
.prefer_hardware_accelerated(hardware_acceleration)
|
||||
.with_depth_size(native_options.depth_buffer);
|
||||
// we don't know if multi sampling option is set. so, check if its more than 0.
|
||||
let config_template = if native_options.multisampling > 0 {
|
||||
config_template.with_multisampling(
|
||||
native_options
|
||||
.multisampling
|
||||
.try_into()
|
||||
.expect("failed to fit multisamples into u8"),
|
||||
)
|
||||
} else {
|
||||
config_template
|
||||
};
|
||||
let config_template = config_template
|
||||
.with_stencil_size(native_options.stencil_buffer)
|
||||
.with_transparency(native_options.transparent)
|
||||
.compatible_with_native_window(raw_window_handle)
|
||||
.build();
|
||||
// finds all valid configurations supported by this display that match the config_template
|
||||
// this is where we will try to get a "fallback" config if we are okay with ignoring some native
|
||||
// options required by user like multi sampling, srgb, transparency etc..
|
||||
// TODO: need to figure out a good fallback config template
|
||||
let config = gl_display
|
||||
.find_configs(config_template.clone())?
|
||||
.next()
|
||||
.ok_or(crate::Error::NoGlutinConfigs(config_template))?;
|
||||
|
||||
let context_attributes =
|
||||
glutin::context::ContextAttributesBuilder::new().build(Some(raw_window_handle));
|
||||
// for surface creation.
|
||||
let (width, height): (u32, u32) = winit_window.inner_size().into();
|
||||
let width = std::num::NonZeroU32::new(width.at_least(1)).unwrap();
|
||||
let height = std::num::NonZeroU32::new(height.at_least(1)).unwrap();
|
||||
let surface_attributes =
|
||||
glutin::surface::SurfaceAttributesBuilder::<glutin::surface::WindowSurface>::new()
|
||||
.build(raw_window_handle, width, height);
|
||||
// start creating the gl objects
|
||||
let gl_context = gl_display.create_context(&config, &context_attributes)?;
|
||||
|
||||
let gl_surface = gl_display.create_window_surface(&config, &surface_attributes)?;
|
||||
let gl_context = gl_context.make_current(&gl_surface)?;
|
||||
gl_surface.set_swap_interval(&gl_context, swap_interval)?;
|
||||
Ok(GlutinWindowContext {
|
||||
window: winit_window,
|
||||
gl_context,
|
||||
gl_display,
|
||||
gl_surface,
|
||||
})
|
||||
}
|
||||
|
||||
fn window(&self) -> &winit::window::Window {
|
||||
&self.window
|
||||
}
|
||||
|
||||
fn resize(&self, physical_size: winit::dpi::PhysicalSize<u32>) {
|
||||
use glutin::surface::GlSurface;
|
||||
let width = std::num::NonZeroU32::new(physical_size.width.at_least(1)).unwrap();
|
||||
let height = std::num::NonZeroU32::new(physical_size.height.at_least(1)).unwrap();
|
||||
self.gl_surface.resize(&self.gl_context, width, height);
|
||||
}
|
||||
|
||||
fn swap_buffers(&self) -> glutin::error::Result<()> {
|
||||
use glutin::surface::GlSurface;
|
||||
self.gl_surface.swap_buffers(&self.gl_context)
|
||||
}
|
||||
|
||||
fn get_proc_address(&self, addr: &std::ffi::CStr) -> *const std::ffi::c_void {
|
||||
use glutin::display::GlDisplay;
|
||||
self.gl_display.get_proc_address(addr)
|
||||
}
|
||||
}
|
||||
|
||||
struct GlowWinitApp {
|
||||
repaint_proxy: Arc<egui::mutex::Mutex<EventLoopProxy<RequestRepaintEvent>>>,
|
||||
repaint_proxy: Arc<egui::mutex::Mutex<EventLoopProxy<UserEvent>>>,
|
||||
app_name: String,
|
||||
native_options: epi::NativeOptions,
|
||||
running: Option<GlowWinitRunning>,
|
||||
@@ -309,11 +461,13 @@ mod glow_integration {
|
||||
// suspends and resumes.
|
||||
app_creator: Option<epi::AppCreator>,
|
||||
is_focused: bool,
|
||||
|
||||
frame_nr: u64,
|
||||
}
|
||||
|
||||
impl GlowWinitApp {
|
||||
fn new(
|
||||
event_loop: &EventLoop<RequestRepaintEvent>,
|
||||
event_loop: &EventLoop<UserEvent>,
|
||||
app_name: &str,
|
||||
native_options: epi::NativeOptions,
|
||||
app_creator: epi::AppCreator,
|
||||
@@ -325,54 +479,39 @@ mod glow_integration {
|
||||
running: None,
|
||||
app_creator: Some(app_creator),
|
||||
is_focused: true,
|
||||
frame_nr: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
fn create_glutin_windowed_context(
|
||||
event_loop: &EventLoopWindowTarget<RequestRepaintEvent>,
|
||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
storage: Option<&dyn epi::Storage>,
|
||||
title: &String,
|
||||
title: &str,
|
||||
native_options: &NativeOptions,
|
||||
) -> (
|
||||
glutin::WindowedContext<glutin::PossiblyCurrent>,
|
||||
glow::Context,
|
||||
) {
|
||||
) -> Result<(GlutinWindowContext, glow::Context)> {
|
||||
crate::profile_function!();
|
||||
|
||||
use crate::HardwareAcceleration;
|
||||
|
||||
let hardware_acceleration = match native_options.hardware_acceleration {
|
||||
HardwareAcceleration::Required => Some(true),
|
||||
HardwareAcceleration::Preferred => None,
|
||||
HardwareAcceleration::Off => Some(false),
|
||||
};
|
||||
let window_settings = epi_integration::load_window_settings(storage);
|
||||
|
||||
let window_builder = epi_integration::window_builder(native_options, &window_settings)
|
||||
.with_title(title)
|
||||
.with_visible(false); // Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
|
||||
let winit_window =
|
||||
epi_integration::build_window(event_loop, title, native_options, window_settings)?;
|
||||
// a lot of the code below has been lifted from glutin example in their repo.
|
||||
let glutin_window_context =
|
||||
unsafe { GlutinWindowContext::new(winit_window, native_options)? };
|
||||
let gl = unsafe {
|
||||
glow::Context::from_loader_function(|s| {
|
||||
let s = std::ffi::CString::new(s)
|
||||
.expect("failed to construct C string from string for gl proc address");
|
||||
|
||||
let gl_window = unsafe {
|
||||
glutin::ContextBuilder::new()
|
||||
.with_hardware_acceleration(hardware_acceleration)
|
||||
.with_depth_buffer(native_options.depth_buffer)
|
||||
.with_multisampling(native_options.multisampling)
|
||||
.with_stencil_buffer(native_options.stencil_buffer)
|
||||
.with_vsync(native_options.vsync)
|
||||
.build_windowed(window_builder, event_loop)
|
||||
.unwrap()
|
||||
.make_current()
|
||||
.unwrap()
|
||||
glutin_window_context.get_proc_address(&s)
|
||||
})
|
||||
};
|
||||
|
||||
let gl =
|
||||
unsafe { glow::Context::from_loader_function(|s| gl_window.get_proc_address(s)) };
|
||||
|
||||
(gl_window, gl)
|
||||
Ok((glutin_window_context, gl))
|
||||
}
|
||||
|
||||
fn init_run_state(&mut self, event_loop: &EventLoopWindowTarget<RequestRepaintEvent>) {
|
||||
fn init_run_state(&mut self, event_loop: &EventLoopWindowTarget<UserEvent>) -> Result<()> {
|
||||
let storage = epi_integration::create_storage(&self.app_name);
|
||||
|
||||
let (gl_window, gl) = Self::create_glutin_windowed_context(
|
||||
@@ -380,7 +519,7 @@ mod glow_integration {
|
||||
storage.as_deref(),
|
||||
&self.app_name,
|
||||
&self.native_options,
|
||||
);
|
||||
)?;
|
||||
let gl = Arc::new(gl);
|
||||
|
||||
let painter =
|
||||
@@ -398,6 +537,10 @@ mod glow_integration {
|
||||
#[cfg(feature = "wgpu")]
|
||||
None,
|
||||
);
|
||||
#[cfg(feature = "accesskit")]
|
||||
{
|
||||
integration.init_accesskit(gl_window.window(), self.repaint_proxy.lock().clone());
|
||||
}
|
||||
let theme = system_theme.unwrap_or(self.native_options.default_theme);
|
||||
integration.egui_ctx.set_visuals(theme.egui_visuals());
|
||||
|
||||
@@ -409,7 +552,10 @@ mod glow_integration {
|
||||
{
|
||||
let event_loop_proxy = self.repaint_proxy.clone();
|
||||
integration.egui_ctx.set_request_repaint_callback(move || {
|
||||
event_loop_proxy.lock().send_event(RequestRepaintEvent).ok();
|
||||
event_loop_proxy
|
||||
.lock()
|
||||
.send_event(UserEvent::RequestRepaint)
|
||||
.ok();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -435,6 +581,8 @@ mod glow_integration {
|
||||
integration,
|
||||
app,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -515,6 +663,26 @@ mod glow_integration {
|
||||
|
||||
integration.post_present(window);
|
||||
|
||||
#[cfg(feature = "__screenshot")]
|
||||
// give it time to settle:
|
||||
if self.frame_nr == 2 {
|
||||
if let Ok(path) = std::env::var("EFRAME_SCREENSHOT_TO") {
|
||||
assert!(
|
||||
path.ends_with(".png"),
|
||||
"Expected EFRAME_SCREENSHOT_TO to end with '.png', got {path:?}"
|
||||
);
|
||||
let [w, h] = screen_size_in_pixels;
|
||||
let pixels = painter.read_screen_rgba(screen_size_in_pixels);
|
||||
let image = image::RgbaImage::from_vec(w, h, pixels).unwrap();
|
||||
let image = image::imageops::flip_vertical(&image);
|
||||
image.save(&path).unwrap_or_else(|err| {
|
||||
panic!("Failed to save screenshot to {path:?}: {err}");
|
||||
});
|
||||
eprintln!("Screenshot saved to {path:?}.");
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
let control_flow = if integration.should_close() {
|
||||
EventResult::Exit
|
||||
} else if repaint_after.is_zero() {
|
||||
@@ -544,6 +712,8 @@ mod glow_integration {
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
}
|
||||
|
||||
self.frame_nr += 1;
|
||||
|
||||
control_flow
|
||||
} else {
|
||||
EventResult::Wait
|
||||
@@ -552,13 +722,13 @@ mod glow_integration {
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
event_loop: &EventLoopWindowTarget<RequestRepaintEvent>,
|
||||
event: &winit::event::Event<'_, RequestRepaintEvent>,
|
||||
) -> EventResult {
|
||||
match event {
|
||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
event: &winit::event::Event<'_, UserEvent>,
|
||||
) -> Result<EventResult> {
|
||||
Ok(match event {
|
||||
winit::event::Event::Resumed => {
|
||||
if self.running.is_none() {
|
||||
self.init_run_state(event_loop);
|
||||
self.init_run_state(event_loop)?;
|
||||
}
|
||||
EventResult::RepaintNow
|
||||
}
|
||||
@@ -621,7 +791,8 @@ mod glow_integration {
|
||||
winit::event::WindowEvent::CloseRequested
|
||||
if running.integration.should_close() =>
|
||||
{
|
||||
return EventResult::Exit
|
||||
tracing::debug!("Received WindowEvent::CloseRequested");
|
||||
return Ok(EventResult::Exit);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -644,8 +815,23 @@ mod glow_integration {
|
||||
EventResult::Wait
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "accesskit")]
|
||||
winit::event::Event::UserEvent(UserEvent::AccessKitActionRequest(
|
||||
accesskit_winit::ActionRequestEvent { request, .. },
|
||||
)) => {
|
||||
if let Some(running) = &mut self.running {
|
||||
running
|
||||
.integration
|
||||
.on_accesskit_action_request(request.clone());
|
||||
// As a form of user input, accessibility actions should
|
||||
// lead to a repaint.
|
||||
EventResult::RepaintNext
|
||||
} else {
|
||||
EventResult::Wait
|
||||
}
|
||||
}
|
||||
_ => EventResult::Wait,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -653,24 +839,15 @@ mod glow_integration {
|
||||
app_name: &str,
|
||||
mut native_options: epi::NativeOptions,
|
||||
app_creator: epi::AppCreator,
|
||||
) {
|
||||
) -> Result<()> {
|
||||
if native_options.run_and_return {
|
||||
with_event_loop(native_options, |event_loop, mut native_options| {
|
||||
if native_options.centered {
|
||||
centere_window_pos(event_loop.available_monitors().next(), &mut native_options);
|
||||
}
|
||||
|
||||
with_event_loop(native_options, |event_loop, native_options| {
|
||||
let glow_eframe =
|
||||
GlowWinitApp::new(event_loop, app_name, native_options, app_creator);
|
||||
run_and_return(event_loop, glow_eframe);
|
||||
});
|
||||
run_and_return(event_loop, glow_eframe)
|
||||
})
|
||||
} else {
|
||||
let event_loop = create_event_loop_builder(&mut native_options).build();
|
||||
|
||||
if native_options.centered {
|
||||
centere_window_pos(event_loop.available_monitors().next(), &mut native_options);
|
||||
}
|
||||
|
||||
let glow_eframe = GlowWinitApp::new(&event_loop, app_name, native_options, app_creator);
|
||||
run_and_exit(event_loop, glow_eframe);
|
||||
}
|
||||
@@ -697,7 +874,7 @@ mod wgpu_integration {
|
||||
}
|
||||
|
||||
struct WgpuWinitApp {
|
||||
repaint_proxy: Arc<std::sync::Mutex<EventLoopProxy<RequestRepaintEvent>>>,
|
||||
repaint_proxy: Arc<std::sync::Mutex<EventLoopProxy<UserEvent>>>,
|
||||
app_name: String,
|
||||
native_options: epi::NativeOptions,
|
||||
app_creator: Option<epi::AppCreator>,
|
||||
@@ -711,11 +888,17 @@ mod wgpu_integration {
|
||||
|
||||
impl WgpuWinitApp {
|
||||
fn new(
|
||||
event_loop: &EventLoop<RequestRepaintEvent>,
|
||||
event_loop: &EventLoop<UserEvent>,
|
||||
app_name: &str,
|
||||
native_options: epi::NativeOptions,
|
||||
app_creator: epi::AppCreator,
|
||||
) -> Self {
|
||||
#[cfg(feature = "__screenshot")]
|
||||
assert!(
|
||||
std::env::var("EFRAME_SCREENSHOT_TO").is_err(),
|
||||
"EFRAME_SCREENSHOT_TO not yet implemented for wgpu backend"
|
||||
);
|
||||
|
||||
Self {
|
||||
repaint_proxy: Arc::new(std::sync::Mutex::new(event_loop.create_proxy())),
|
||||
app_name: app_name.to_owned(),
|
||||
@@ -728,46 +911,47 @@ mod wgpu_integration {
|
||||
}
|
||||
|
||||
fn create_window(
|
||||
event_loop: &EventLoopWindowTarget<RequestRepaintEvent>,
|
||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
storage: Option<&dyn epi::Storage>,
|
||||
title: &String,
|
||||
title: &str,
|
||||
native_options: &NativeOptions,
|
||||
) -> winit::window::Window {
|
||||
) -> std::result::Result<winit::window::Window, winit::error::OsError> {
|
||||
let window_settings = epi_integration::load_window_settings(storage);
|
||||
epi_integration::window_builder(native_options, &window_settings)
|
||||
.with_title(title)
|
||||
.with_visible(false) // Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
|
||||
.build(event_loop)
|
||||
.unwrap()
|
||||
epi_integration::build_window(event_loop, title, native_options, window_settings)
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
fn set_window(&mut self, window: winit::window::Window) {
|
||||
fn set_window(
|
||||
&mut self,
|
||||
window: winit::window::Window,
|
||||
) -> std::result::Result<(), egui_wgpu::WgpuError> {
|
||||
self.window = Some(window);
|
||||
if let Some(running) = &mut self.running {
|
||||
unsafe {
|
||||
running.painter.set_window(self.window.as_ref());
|
||||
pollster::block_on(running.painter.set_window(self.window.as_ref()))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
#[cfg(target_os = "android")]
|
||||
fn drop_window(&mut self) {
|
||||
fn drop_window(&mut self) -> std::result::Result<(), egui_wgpu::WgpuError> {
|
||||
self.window = None;
|
||||
if let Some(running) = &mut self.running {
|
||||
unsafe {
|
||||
running.painter.set_window(None);
|
||||
pollster::block_on(running.painter.set_window(None))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_run_state(
|
||||
&mut self,
|
||||
event_loop: &EventLoopWindowTarget<RequestRepaintEvent>,
|
||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
storage: Option<Box<dyn epi::Storage>>,
|
||||
window: winit::window::Window,
|
||||
) {
|
||||
) -> std::result::Result<(), egui_wgpu::WgpuError> {
|
||||
#[allow(unsafe_code, unused_mut, unused_unsafe)]
|
||||
let painter = unsafe {
|
||||
let mut painter = egui_wgpu::winit::Painter::new(
|
||||
@@ -775,7 +959,7 @@ mod wgpu_integration {
|
||||
self.native_options.multisampling.max(1) as _,
|
||||
self.native_options.depth_buffer,
|
||||
);
|
||||
painter.set_window(Some(&window));
|
||||
pollster::block_on(painter.set_window(Some(&window)))?;
|
||||
painter
|
||||
};
|
||||
|
||||
@@ -792,6 +976,10 @@ mod wgpu_integration {
|
||||
None,
|
||||
wgpu_render_state.clone(),
|
||||
);
|
||||
#[cfg(feature = "accesskit")]
|
||||
{
|
||||
integration.init_accesskit(&window, self.repaint_proxy.lock().unwrap().clone());
|
||||
}
|
||||
let theme = system_theme.unwrap_or(self.native_options.default_theme);
|
||||
integration.egui_ctx.set_visuals(theme.egui_visuals());
|
||||
|
||||
@@ -803,7 +991,7 @@ mod wgpu_integration {
|
||||
event_loop_proxy
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send_event(RequestRepaintEvent)
|
||||
.send_event(UserEvent::RequestRepaint)
|
||||
.ok();
|
||||
});
|
||||
}
|
||||
@@ -829,6 +1017,8 @@ mod wgpu_integration {
|
||||
app,
|
||||
});
|
||||
self.window = Some(window);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -934,10 +1124,10 @@ mod wgpu_integration {
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
event_loop: &EventLoopWindowTarget<RequestRepaintEvent>,
|
||||
event: &winit::event::Event<'_, RequestRepaintEvent>,
|
||||
) -> EventResult {
|
||||
match event {
|
||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
event: &winit::event::Event<'_, UserEvent>,
|
||||
) -> Result<EventResult> {
|
||||
Ok(match event {
|
||||
winit::event::Event::Resumed => {
|
||||
if let Some(running) = &self.running {
|
||||
if self.window.is_none() {
|
||||
@@ -946,8 +1136,8 @@ mod wgpu_integration {
|
||||
running.integration.frame.storage(),
|
||||
&self.app_name,
|
||||
&self.native_options,
|
||||
);
|
||||
self.set_window(window);
|
||||
)?;
|
||||
self.set_window(window)?;
|
||||
}
|
||||
} else {
|
||||
let storage = epi_integration::create_storage(&self.app_name);
|
||||
@@ -956,14 +1146,14 @@ mod wgpu_integration {
|
||||
storage.as_deref(),
|
||||
&self.app_name,
|
||||
&self.native_options,
|
||||
);
|
||||
self.init_run_state(event_loop, storage, window);
|
||||
)?;
|
||||
self.init_run_state(event_loop, storage, window)?;
|
||||
}
|
||||
EventResult::RepaintNow
|
||||
}
|
||||
winit::event::Event::Suspended => {
|
||||
#[cfg(target_os = "android")]
|
||||
self.drop_window();
|
||||
self.drop_window()?;
|
||||
EventResult::Wait
|
||||
}
|
||||
|
||||
@@ -1013,7 +1203,8 @@ mod wgpu_integration {
|
||||
winit::event::WindowEvent::CloseRequested
|
||||
if running.integration.should_close() =>
|
||||
{
|
||||
return EventResult::Exit
|
||||
tracing::debug!("Received WindowEvent::CloseRequested");
|
||||
return Ok(EventResult::Exit);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
@@ -1035,8 +1226,23 @@ mod wgpu_integration {
|
||||
EventResult::Wait
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "accesskit")]
|
||||
winit::event::Event::UserEvent(UserEvent::AccessKitActionRequest(
|
||||
accesskit_winit::ActionRequestEvent { request, .. },
|
||||
)) => {
|
||||
if let Some(running) = &mut self.running {
|
||||
running
|
||||
.integration
|
||||
.on_accesskit_action_request(request.clone());
|
||||
// As a form of user input, accessibility actions should
|
||||
// lead to a repaint.
|
||||
EventResult::RepaintNext
|
||||
} else {
|
||||
EventResult::Wait
|
||||
}
|
||||
}
|
||||
_ => EventResult::Wait,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1044,24 +1250,15 @@ mod wgpu_integration {
|
||||
app_name: &str,
|
||||
mut native_options: epi::NativeOptions,
|
||||
app_creator: epi::AppCreator,
|
||||
) {
|
||||
) -> Result<()> {
|
||||
if native_options.run_and_return {
|
||||
with_event_loop(native_options, |event_loop, mut native_options| {
|
||||
if native_options.centered {
|
||||
centere_window_pos(event_loop.available_monitors().next(), &mut native_options);
|
||||
}
|
||||
|
||||
with_event_loop(native_options, |event_loop, native_options| {
|
||||
let wgpu_eframe =
|
||||
WgpuWinitApp::new(event_loop, app_name, native_options, app_creator);
|
||||
run_and_return(event_loop, wgpu_eframe);
|
||||
});
|
||||
run_and_return(event_loop, wgpu_eframe)
|
||||
})
|
||||
} else {
|
||||
let event_loop = create_event_loop_builder(&mut native_options).build();
|
||||
|
||||
if native_options.centered {
|
||||
centere_window_pos(event_loop.available_monitors().next(), &mut native_options);
|
||||
}
|
||||
|
||||
let wgpu_eframe = WgpuWinitApp::new(&event_loop, app_name, native_options, app_creator);
|
||||
run_and_exit(event_loop, wgpu_eframe);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use super::{web_painter::WebPainter, *};
|
||||
use crate::epi;
|
||||
|
||||
use egui::{
|
||||
mutex::{Mutex, MutexGuard},
|
||||
TexturesDelta,
|
||||
};
|
||||
pub use egui::{pos2, Color32};
|
||||
|
||||
use crate::{epi, App};
|
||||
|
||||
use super::{web_painter::WebPainter, *};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
@@ -284,7 +284,7 @@ impl AppRunner {
|
||||
/// Get mutable access to the concrete [`App`] we enclose.
|
||||
///
|
||||
/// This will panic if your app does not implement [`App::as_any_mut`].
|
||||
pub fn app_mut<ConreteApp: 'static + crate::App>(&mut self) -> &mut ConreteApp {
|
||||
pub fn app_mut<ConreteApp: 'static + App>(&mut self) -> &mut ConreteApp {
|
||||
self.app
|
||||
.as_any_mut()
|
||||
.expect("Your app must implement `as_any_mut`, but it doesn't")
|
||||
@@ -313,10 +313,11 @@ impl AppRunner {
|
||||
|
||||
pub fn warm_up(&mut self) -> Result<(), JsValue> {
|
||||
if self.app.warm_up_enabled() {
|
||||
let saved_memory: egui::Memory = self.egui_ctx.memory().clone();
|
||||
self.egui_ctx.memory().set_everything_is_visible(true);
|
||||
let saved_memory: egui::Memory = self.egui_ctx.memory(|m| m.clone());
|
||||
self.egui_ctx
|
||||
.memory_mut(|m| m.set_everything_is_visible(true));
|
||||
self.logic()?;
|
||||
*self.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge.
|
||||
self.egui_ctx.memory_mut(|m| *m = saved_memory); // We don't want to remember that windows were huge.
|
||||
self.egui_ctx.clear_animations();
|
||||
}
|
||||
Ok(())
|
||||
@@ -388,7 +389,7 @@ impl AppRunner {
|
||||
}
|
||||
|
||||
fn handle_platform_output(&mut self, platform_output: egui::PlatformOutput) {
|
||||
if self.egui_ctx.options().screen_reader {
|
||||
if self.egui_ctx.options(|o| o.screen_reader) {
|
||||
self.screen_reader
|
||||
.speak(&platform_output.events_description());
|
||||
}
|
||||
@@ -400,6 +401,8 @@ impl AppRunner {
|
||||
events: _, // already handled
|
||||
mutable_text_under_cursor,
|
||||
text_cursor_pos,
|
||||
#[cfg(feature = "accesskit")]
|
||||
accesskit_update: _, // not currently implemented
|
||||
} = platform_output;
|
||||
|
||||
set_cursor_icon(cursor_icon);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use super::*;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use egui::Key;
|
||||
|
||||
use super::*;
|
||||
|
||||
struct IsDestroyed(pub bool);
|
||||
|
||||
pub fn paint_and_schedule(
|
||||
@@ -64,11 +67,13 @@ pub fn install_document_events(runner_container: &mut AppRunnerContainer) -> Res
|
||||
runner_lock.input.raw.modifiers = modifiers;
|
||||
|
||||
let key = event.key();
|
||||
let egui_key = translate_key(&key);
|
||||
|
||||
if let Some(key) = translate_key(&key) {
|
||||
if let Some(key) = egui_key {
|
||||
runner_lock.input.raw.events.push(egui::Event::Key {
|
||||
key,
|
||||
pressed: true,
|
||||
repeat: false, // egui will fill this in for us!
|
||||
modifiers,
|
||||
});
|
||||
}
|
||||
@@ -84,10 +89,13 @@ pub fn install_document_events(runner_container: &mut AppRunnerContainer) -> Res
|
||||
|
||||
let egui_wants_keyboard = runner_lock.egui_ctx().wants_keyboard_input();
|
||||
|
||||
let prevent_default = if matches!(event.key().as_str(), "Tab") {
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
let prevent_default = if egui_key == Some(Key::Tab) {
|
||||
// Always prevent moving cursor to url bar.
|
||||
// egui wants to use tab to move to the next text field.
|
||||
true
|
||||
} else if egui_key == Some(Key::P) {
|
||||
true // Prevent ctrl-P opening the print dialog. Users may want to use it for a command palette.
|
||||
} else if egui_wants_keyboard {
|
||||
matches!(
|
||||
event.key().as_str(),
|
||||
@@ -111,6 +119,7 @@ pub fn install_document_events(runner_container: &mut AppRunnerContainer) -> Res
|
||||
|
||||
if prevent_default {
|
||||
event.prevent_default();
|
||||
// event.stop_propagation();
|
||||
}
|
||||
},
|
||||
)?;
|
||||
@@ -125,6 +134,7 @@ pub fn install_document_events(runner_container: &mut AppRunnerContainer) -> Res
|
||||
runner_lock.input.raw.events.push(egui::Event::Key {
|
||||
key,
|
||||
pressed: false,
|
||||
repeat: false,
|
||||
modifiers,
|
||||
});
|
||||
}
|
||||
@@ -196,15 +206,21 @@ pub fn install_document_events(runner_container: &mut AppRunnerContainer) -> Res
|
||||
pub fn install_canvas_events(runner_container: &mut AppRunnerContainer) -> Result<(), JsValue> {
|
||||
let canvas = canvas_element(runner_container.runner.lock().canvas_id()).unwrap();
|
||||
|
||||
{
|
||||
let prevent_default_events = [
|
||||
// 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";
|
||||
"contextmenu",
|
||||
// Allow users to use ctrl-p for e.g. a command palette
|
||||
"afterprint",
|
||||
];
|
||||
|
||||
for event_name in prevent_default_events {
|
||||
let closure =
|
||||
move |event: web_sys::MouseEvent,
|
||||
mut _runner_lock: egui::mutex::MutexGuard<'_, AppRunner>| {
|
||||
event.prevent_default();
|
||||
// event.stop_propagation();
|
||||
// tracing::debug!("Preventing event {:?}", event_name);
|
||||
};
|
||||
|
||||
runner_container.add_event_listener(&canvas, event_name, closure)?;
|
||||
@@ -308,7 +324,7 @@ pub fn install_canvas_events(runner_container: &mut AppRunnerContainer) -> Resul
|
||||
modifiers,
|
||||
});
|
||||
|
||||
push_touches(&mut *runner_lock, egui::TouchPhase::Start, &event);
|
||||
push_touches(&mut runner_lock, egui::TouchPhase::Start, &event);
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
@@ -330,7 +346,7 @@ pub fn install_canvas_events(runner_container: &mut AppRunnerContainer) -> Resul
|
||||
.events
|
||||
.push(egui::Event::PointerMoved(pos));
|
||||
|
||||
push_touches(&mut *runner_lock, egui::TouchPhase::Move, &event);
|
||||
push_touches(&mut runner_lock, egui::TouchPhase::Move, &event);
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
@@ -357,7 +373,7 @@ pub fn install_canvas_events(runner_container: &mut AppRunnerContainer) -> Resul
|
||||
// Then remove hover effect:
|
||||
runner_lock.input.raw.events.push(egui::Event::PointerGone);
|
||||
|
||||
push_touches(&mut *runner_lock, egui::TouchPhase::End, &event);
|
||||
push_touches(&mut runner_lock, egui::TouchPhase::End, &event);
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
@@ -388,7 +404,7 @@ pub fn install_canvas_events(runner_container: &mut AppRunnerContainer) -> Resul
|
||||
}
|
||||
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.
|
||||
let points_per_scroll_line = 8.0; // Note that this is intentionally different from what we use in winit.
|
||||
points_per_scroll_line
|
||||
}
|
||||
_ => 1.0, // DOM_DELTA_PIXEL
|
||||
|
||||
@@ -91,7 +91,7 @@ pub fn canvas_element(canvas_id: &str) -> Option<web_sys::HtmlCanvasElement> {
|
||||
|
||||
pub fn canvas_element_or_die(canvas_id: &str) -> web_sys::HtmlCanvasElement {
|
||||
canvas_element(canvas_id)
|
||||
.unwrap_or_else(|| panic!("Failed to find canvas with id '{}'", canvas_id))
|
||||
.unwrap_or_else(|| panic!("Failed to find canvas with id {:?}", canvas_id))
|
||||
}
|
||||
|
||||
fn canvas_origin(canvas_id: &str) -> egui::Pos2 {
|
||||
|
||||
@@ -15,7 +15,7 @@ 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;
|
||||
ctx.memory_mut(|m| *m = memory);
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to parse memory RON: {}", err);
|
||||
@@ -29,7 +29,7 @@ pub fn load_memory(_: &egui::Context) {}
|
||||
|
||||
#[cfg(feature = "persistence")]
|
||||
pub fn save_memory(ctx: &egui::Context) {
|
||||
match ron::to_string(&*ctx.memory()) {
|
||||
match ctx.memory(|mem| ron::to_string(mem)) {
|
||||
Ok(ron) => {
|
||||
local_storage_set("egui_memory_ron", &ron);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use egui::Rgba;
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
/// Renderer for a browser canvas.
|
||||
@@ -19,7 +18,7 @@ pub(crate) trait WebPainter {
|
||||
/// Update all internal textures and paint gui.
|
||||
fn paint_and_update_textures(
|
||||
&mut self,
|
||||
clear_color: Rgba,
|
||||
clear_color: [f32; 4],
|
||||
clipped_primitives: &[egui::ClippedPrimitive],
|
||||
pixels_per_point: f32,
|
||||
textures_delta: &egui::TexturesDelta,
|
||||
|
||||
@@ -2,7 +2,6 @@ use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::HtmlCanvasElement;
|
||||
|
||||
use egui::Rgba;
|
||||
use egui_glow::glow;
|
||||
|
||||
use crate::{WebGlContextOption, WebOptions};
|
||||
@@ -49,7 +48,7 @@ impl WebPainter for WebPainterGlow {
|
||||
|
||||
fn paint_and_update_textures(
|
||||
&mut self,
|
||||
clear_color: Rgba,
|
||||
clear_color: [f32; 4],
|
||||
clipped_primitives: &[egui::ClippedPrimitive],
|
||||
pixels_per_point: f32,
|
||||
textures_delta: &egui::TexturesDelta,
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::HtmlCanvasElement;
|
||||
|
||||
use egui::{mutex::RwLock, Rgba};
|
||||
use egui::mutex::RwLock;
|
||||
use egui_wgpu::{renderer::ScreenDescriptor, RenderState, SurfaceErrorAction};
|
||||
|
||||
use crate::WebOptions;
|
||||
@@ -18,6 +18,8 @@ pub(crate) struct WebPainterWgpu {
|
||||
limits: wgpu::Limits,
|
||||
render_state: Option<RenderState>,
|
||||
on_surface_error: Arc<dyn Fn(wgpu::SurfaceError) -> SurfaceErrorAction>,
|
||||
depth_format: Option<wgpu::TextureFormat>,
|
||||
depth_texture_view: Option<wgpu::TextureView>,
|
||||
}
|
||||
|
||||
impl WebPainterWgpu {
|
||||
@@ -26,14 +28,46 @@ impl WebPainterWgpu {
|
||||
self.render_state.clone()
|
||||
}
|
||||
|
||||
pub fn generate_depth_texture_view(
|
||||
&self,
|
||||
render_state: &RenderState,
|
||||
width_in_pixels: u32,
|
||||
height_in_pixels: u32,
|
||||
) -> Option<wgpu::TextureView> {
|
||||
let device = &render_state.device;
|
||||
self.depth_format.map(|depth_format| {
|
||||
device
|
||||
.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("egui_depth_texture"),
|
||||
size: wgpu::Extent3d {
|
||||
width: width_in_pixels,
|
||||
height: height_in_pixels,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: depth_format,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
view_formats: &[depth_format],
|
||||
})
|
||||
.create_view(&wgpu::TextureViewDescriptor::default())
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(unused)] // only used if `wgpu` is the only active feature.
|
||||
pub async fn new(canvas_id: &str, options: &WebOptions) -> Result<Self, String> {
|
||||
tracing::debug!("Creating wgpu painter");
|
||||
|
||||
let canvas = super::canvas_element_or_die(canvas_id);
|
||||
|
||||
let instance = wgpu::Instance::new(options.wgpu_options.backends);
|
||||
let surface = instance.create_surface_from_canvas(&canvas);
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
backends: options.wgpu_options.backends,
|
||||
dx12_shader_compiler: Default::default(),
|
||||
});
|
||||
let surface = instance
|
||||
.create_surface_from_canvas(&canvas)
|
||||
.map_err(|err| format!("failed to create wgpu surface: {err}"))?;
|
||||
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
@@ -53,9 +87,10 @@ impl WebPainterWgpu {
|
||||
.map_err(|err| format!("Failed to find wgpu device: {}", err))?;
|
||||
|
||||
let target_format =
|
||||
egui_wgpu::preferred_framebuffer_format(&surface.get_supported_formats(&adapter));
|
||||
egui_wgpu::preferred_framebuffer_format(&surface.get_capabilities(&adapter).formats);
|
||||
|
||||
let renderer = egui_wgpu::Renderer::new(&device, target_format, None, 1);
|
||||
let depth_format = options.wgpu_options.depth_format;
|
||||
let renderer = egui_wgpu::Renderer::new(&device, target_format, depth_format, 1);
|
||||
let render_state = RenderState {
|
||||
device: Arc::new(device),
|
||||
queue: Arc::new(queue),
|
||||
@@ -70,6 +105,7 @@ impl WebPainterWgpu {
|
||||
height: 0,
|
||||
present_mode: options.wgpu_options.present_mode,
|
||||
alpha_mode: wgpu::CompositeAlphaMode::Auto,
|
||||
view_formats: vec![target_format],
|
||||
};
|
||||
|
||||
tracing::debug!("wgpu painter initialized.");
|
||||
@@ -80,6 +116,8 @@ impl WebPainterWgpu {
|
||||
render_state: Some(render_state),
|
||||
surface,
|
||||
surface_configuration,
|
||||
depth_format,
|
||||
depth_texture_view: None,
|
||||
limits: options.wgpu_options.device_descriptor.limits.clone(),
|
||||
on_surface_error: options.wgpu_options.on_surface_error.clone(),
|
||||
})
|
||||
@@ -97,11 +135,13 @@ impl WebPainter for WebPainterWgpu {
|
||||
|
||||
fn paint_and_update_textures(
|
||||
&mut self,
|
||||
clear_color: Rgba,
|
||||
clear_color: [f32; 4],
|
||||
clipped_primitives: &[egui::ClippedPrimitive],
|
||||
pixels_per_point: f32,
|
||||
textures_delta: &egui::TexturesDelta,
|
||||
) -> Result<(), JsValue> {
|
||||
let size_in_pixels = [self.canvas.width(), self.canvas.height()];
|
||||
|
||||
let render_state = if let Some(render_state) = &self.render_state {
|
||||
render_state
|
||||
} else {
|
||||
@@ -110,32 +150,6 @@ impl WebPainter for WebPainterWgpu {
|
||||
));
|
||||
};
|
||||
|
||||
// Resize surface if needed
|
||||
let size_in_pixels = [self.canvas.width(), self.canvas.height()];
|
||||
if size_in_pixels[0] != self.surface_configuration.width
|
||||
|| size_in_pixels[1] != self.surface_configuration.height
|
||||
{
|
||||
self.surface_configuration.width = size_in_pixels[0];
|
||||
self.surface_configuration.height = size_in_pixels[1];
|
||||
self.surface
|
||||
.configure(&render_state.device, &self.surface_configuration);
|
||||
}
|
||||
|
||||
let frame = match self.surface.get_current_texture() {
|
||||
Ok(frame) => frame,
|
||||
#[allow(clippy::single_match_else)]
|
||||
Err(e) => match (*self.on_surface_error)(e) {
|
||||
SurfaceErrorAction::RecreateSurface => {
|
||||
self.surface
|
||||
.configure(&render_state.device, &self.surface_configuration);
|
||||
return Ok(());
|
||||
}
|
||||
SurfaceErrorAction::SkipFrame => {
|
||||
return Ok(());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let mut encoder =
|
||||
render_state
|
||||
.device
|
||||
@@ -169,31 +183,77 @@ impl WebPainter for WebPainterWgpu {
|
||||
)
|
||||
};
|
||||
|
||||
{
|
||||
let renderer = render_state.renderer.read();
|
||||
let frame_view = frame
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &frame_view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||
r: clear_color.r() as f64,
|
||||
g: clear_color.g() as f64,
|
||||
b: clear_color.b() as f64,
|
||||
a: clear_color.a() as f64,
|
||||
}),
|
||||
store: true,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
label: Some("egui_render"),
|
||||
});
|
||||
// Resize surface if needed
|
||||
let is_zero_sized_surface = size_in_pixels[0] == 0 || size_in_pixels[1] == 0;
|
||||
let frame = if is_zero_sized_surface {
|
||||
None
|
||||
} else {
|
||||
if size_in_pixels[0] != self.surface_configuration.width
|
||||
|| size_in_pixels[1] != self.surface_configuration.height
|
||||
{
|
||||
self.surface_configuration.width = size_in_pixels[0];
|
||||
self.surface_configuration.height = size_in_pixels[1];
|
||||
self.surface
|
||||
.configure(&render_state.device, &self.surface_configuration);
|
||||
self.depth_texture_view = self.generate_depth_texture_view(
|
||||
render_state,
|
||||
size_in_pixels[0],
|
||||
size_in_pixels[1],
|
||||
);
|
||||
}
|
||||
|
||||
renderer.render(&mut render_pass, clipped_primitives, &screen_descriptor);
|
||||
}
|
||||
let frame = match self.surface.get_current_texture() {
|
||||
Ok(frame) => frame,
|
||||
#[allow(clippy::single_match_else)]
|
||||
Err(e) => match (*self.on_surface_error)(e) {
|
||||
SurfaceErrorAction::RecreateSurface => {
|
||||
self.surface
|
||||
.configure(&render_state.device, &self.surface_configuration);
|
||||
return Ok(());
|
||||
}
|
||||
SurfaceErrorAction::SkipFrame => {
|
||||
return Ok(());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
{
|
||||
let renderer = render_state.renderer.read();
|
||||
let frame_view = frame
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &frame_view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||
r: clear_color[0] as f64,
|
||||
g: clear_color[1] as f64,
|
||||
b: clear_color[2] as f64,
|
||||
a: clear_color[3] as f64,
|
||||
}),
|
||||
store: true,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: self.depth_texture_view.as_ref().map(|view| {
|
||||
wgpu::RenderPassDepthStencilAttachment {
|
||||
view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(1.0),
|
||||
store: false,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}
|
||||
}),
|
||||
label: Some("egui_render"),
|
||||
});
|
||||
|
||||
renderer.render(&mut render_pass, clipped_primitives, &screen_descriptor);
|
||||
}
|
||||
|
||||
Some(frame)
|
||||
};
|
||||
|
||||
{
|
||||
let mut renderer = render_state.renderer.write();
|
||||
@@ -208,7 +268,10 @@ impl WebPainter for WebPainterWgpu {
|
||||
.into_iter()
|
||||
.chain(std::iter::once(encoder.finish())),
|
||||
);
|
||||
frame.present();
|
||||
|
||||
if let Some(frame) = frame {
|
||||
frame.present();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user