1
0
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:
Emil Ernerfeldt
2023-02-04 14:19:44 +01:00
committed by GitHub
249 changed files with 9597 additions and 5842 deletions

View File

@@ -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.

View File

@@ -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"] }

View File

@@ -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).

View File

@@ -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

View File

@@ -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"

View File

@@ -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");

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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(())
}