mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 21:00:03 -04:00
Merge branch 'main' into lucas/malmal/main
Re-port the WASM canvas font fallback onto main's new skrifa/harfrust shaping pipeline: - Add `chr` to `ShapedGlyph` and trigger the canvas fallback from `allocate_glyph` on NOTDEF (WASM only). - Drive the fallback from `text_layout`'s NOTDEF branch via `has_glyph`, so it only kicks in when no loaded font contains the character. - Update `allocate_canvas_glyph` for main's `GlyphAllocation` (uv_rect only) and `color_transfer_function` API; fix `get_image_data` f64 args. Merge the per-axis scroll fade gating into main's refactored `paint_fade_areas_impl`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,15 @@ This file is updated upon each release.
|
||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||
|
||||
|
||||
## 0.34.3 - 2026-05-27
|
||||
* Default `app_id` to `app_name` on native [#8172](https://github.com/emilk/egui/pull/8172) by [@grtlr](https://github.com/grtlr)
|
||||
* Add winit window access to `eframe::Frame` and `CreationContext` [#8205](https://github.com/emilk/egui/pull/8205) by [@emilk](https://github.com/emilk)
|
||||
|
||||
|
||||
## 0.34.2 - 2026-05-04
|
||||
* Document glow-only fields in `NativeOptions` [#8104](https://github.com/emilk/egui/pull/8104) by [@emilk](https://github.com/emilk)
|
||||
|
||||
|
||||
## 0.34.1 - 2026-03-27
|
||||
* `wgpu` backend: Enable WebGL fallback [#8038](https://github.com/emilk/egui/pull/8038) by [@emilk](https://github.com/emilk)
|
||||
* Only apply cursor style to the `<canvas>` [#8036](https://github.com/emilk/egui/pull/8036) by [@mkeeter](https://github.com/mkeeter)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//!
|
||||
//! `epi` provides interfaces for window management and serialization.
|
||||
//!
|
||||
//! Start by looking at the [`App`] trait, and implement [`App::update`].
|
||||
//! Start by looking at the [`App`] trait, and implement [`App::ui`].
|
||||
|
||||
#![warn(missing_docs)] // Let's keep `epi` well-documented.
|
||||
|
||||
@@ -83,6 +83,10 @@ pub struct CreationContext<'s> {
|
||||
#[cfg(feature = "wgpu_no_default_features")]
|
||||
pub wgpu_render_state: Option<egui_wgpu::RenderState>,
|
||||
|
||||
/// The root [`winit::window::Window`].
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) window: Option<std::sync::Arc<winit::window::Window>>,
|
||||
|
||||
/// Raw platform window handle
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) raw_window_handle: Result<RawWindowHandle, HandleError>,
|
||||
@@ -125,11 +129,21 @@ impl CreationContext<'_> {
|
||||
#[cfg(feature = "wgpu_no_default_features")]
|
||||
wgpu_render_state: None,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
window: None,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
raw_window_handle: Err(HandleError::NotSupported),
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
raw_display_handle: Err(HandleError::NotSupported),
|
||||
}
|
||||
}
|
||||
|
||||
/// Access to the root [`winit::window::Window`].
|
||||
///
|
||||
/// `None` for headless (tests etc).
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn winit_window(&self) -> Option<&std::sync::Arc<winit::window::Window>> {
|
||||
self.window.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -161,22 +175,6 @@ pub trait App {
|
||||
/// (A "viewport" in egui means an native OS window).
|
||||
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut Frame);
|
||||
|
||||
/// Called each time the UI needs repainting, which may be many times per second.
|
||||
///
|
||||
/// Put your widgets into a [`egui::Panel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`].
|
||||
///
|
||||
/// The [`egui::Context`] can be cloned and saved if you like.
|
||||
///
|
||||
/// To force a repaint, call [`egui::Context::request_repaint`] at any time (e.g. from another thread).
|
||||
///
|
||||
/// This is called for the root viewport ([`egui::ViewportId::ROOT`]).
|
||||
/// Use [`egui::Context::show_viewport_deferred`] to spawn additional viewports (windows).
|
||||
/// (A "viewport" in egui means an native OS window).
|
||||
#[deprecated = "Use Self::ui instead"]
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut Frame) {
|
||||
_ = (ctx, frame);
|
||||
}
|
||||
|
||||
/// Get a handle to the app.
|
||||
///
|
||||
/// Can be used from web to interact or other external context.
|
||||
@@ -256,7 +254,7 @@ pub trait App {
|
||||
true
|
||||
}
|
||||
|
||||
/// A hook for manipulating or filtering raw input before it is processed by [`Self::update`].
|
||||
/// A hook for manipulating or filtering raw input before it is processed by [`Self::ui`].
|
||||
///
|
||||
/// This function provides a way to modify or filter input events before they are processed by egui.
|
||||
///
|
||||
@@ -275,22 +273,6 @@ pub trait App {
|
||||
fn raw_input_hook(&mut self, _ctx: &egui::Context, _raw_input: &mut egui::RawInput) {}
|
||||
}
|
||||
|
||||
/// Selects the level of hardware graphics acceleration.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum HardwareAcceleration {
|
||||
/// Require graphics acceleration.
|
||||
Required,
|
||||
|
||||
/// Prefer graphics acceleration, but fall back to software.
|
||||
Preferred,
|
||||
|
||||
/// Do NOT use graphics acceleration.
|
||||
///
|
||||
/// On some platforms (macOS) this is ignored and treated the same as [`Self::Preferred`].
|
||||
Off,
|
||||
}
|
||||
|
||||
/// Options controlling the behavior of a native window.
|
||||
///
|
||||
/// Additional windows can be opened using (egui viewports)[`egui::viewport`].
|
||||
@@ -314,11 +296,6 @@ pub struct NativeOptions {
|
||||
/// To avoid this, set the icon to [`egui::IconData::default`].
|
||||
pub viewport: egui::ViewportBuilder,
|
||||
|
||||
/// Turn on vertical syncing, limiting the FPS to the display refresh rate.
|
||||
///
|
||||
/// The default is `true`.
|
||||
pub vsync: bool,
|
||||
|
||||
/// Set the level of the multisampling anti-aliasing (MSAA).
|
||||
///
|
||||
/// Must be a power-of-two. Higher = more smooth 3D.
|
||||
@@ -340,11 +317,6 @@ pub struct NativeOptions {
|
||||
/// `egui` doesn't need the stencil buffer, so the default value is 0.
|
||||
pub stencil_buffer: u8,
|
||||
|
||||
/// Specify whether or not hardware acceleration is preferred, required, or not.
|
||||
///
|
||||
/// Default: [`HardwareAcceleration::Preferred`].
|
||||
pub hardware_acceleration: HardwareAcceleration,
|
||||
|
||||
/// What rendering backend to use.
|
||||
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
|
||||
pub renderer: Renderer,
|
||||
@@ -381,13 +353,6 @@ pub struct NativeOptions {
|
||||
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
|
||||
pub window_builder: Option<WindowBuilderHook>,
|
||||
|
||||
#[cfg(feature = "glow")]
|
||||
/// Needed for cross compiling for VirtualBox VMSVGA driver with OpenGL ES 2.0 and OpenGL 2.1 which doesn't support SRGB texture.
|
||||
/// See <https://github.com/emilk/egui/pull/1993>.
|
||||
///
|
||||
/// For OpenGL ES 2.0: set this to [`egui_glow::ShaderVersion::Es100`] to solve blank texture problem (by using the "fallback shader").
|
||||
pub shader_version: Option<egui_glow::ShaderVersion>,
|
||||
|
||||
/// On desktop: make the window position to be centered at initialization.
|
||||
///
|
||||
/// Platform specific:
|
||||
@@ -395,6 +360,10 @@ pub struct NativeOptions {
|
||||
/// Wayland desktop currently not supported.
|
||||
pub centered: bool,
|
||||
|
||||
/// Configures glow instance.
|
||||
#[cfg(feature = "glow")]
|
||||
pub glow_options: egui_glow::GlowConfiguration,
|
||||
|
||||
/// Configures wgpu instance/device/adapter/surface creation and renderloop.
|
||||
#[cfg(feature = "wgpu_no_default_features")]
|
||||
pub wgpu_options: egui_wgpu::WgpuConfiguration,
|
||||
@@ -439,6 +408,9 @@ impl Clone for NativeOptions {
|
||||
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
|
||||
window_builder: None, // Skip any builder callbacks if cloning
|
||||
|
||||
#[cfg(feature = "glow")]
|
||||
glow_options: self.glow_options.clone(),
|
||||
|
||||
#[cfg(feature = "wgpu_no_default_features")]
|
||||
wgpu_options: self.wgpu_options.clone(),
|
||||
|
||||
@@ -458,11 +430,9 @@ impl Default for NativeOptions {
|
||||
Self {
|
||||
viewport: Default::default(),
|
||||
|
||||
vsync: true,
|
||||
multisampling: 0,
|
||||
depth_buffer: 0,
|
||||
stencil_buffer: 0,
|
||||
hardware_acceleration: HardwareAcceleration::Preferred,
|
||||
|
||||
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
|
||||
renderer: Renderer::default(),
|
||||
@@ -475,13 +445,14 @@ impl Default for NativeOptions {
|
||||
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
|
||||
window_builder: None,
|
||||
|
||||
#[cfg(feature = "glow")]
|
||||
shader_version: None,
|
||||
|
||||
centered: false,
|
||||
|
||||
#[cfg(feature = "glow")]
|
||||
glow_options: egui_glow::GlowConfiguration::default(),
|
||||
|
||||
#[cfg(feature = "wgpu_no_default_features")]
|
||||
wgpu_options: egui_wgpu::WgpuConfiguration::default(),
|
||||
wgpu_options: egui_wgpu::WgpuConfiguration::default()
|
||||
.with_surface_config(egui_wgpu::SurfaceConfig::LOW_LATENCY),
|
||||
|
||||
persist_window: true,
|
||||
|
||||
@@ -516,6 +487,10 @@ pub struct WebOptions {
|
||||
#[cfg(feature = "glow")]
|
||||
pub webgl_context_option: WebGlContextOption,
|
||||
|
||||
/// Configures glow instance.
|
||||
#[cfg(feature = "glow")]
|
||||
pub glow_options: egui_glow::GlowConfiguration,
|
||||
|
||||
/// Configures wgpu instance/device/adapter/surface creation and renderloop.
|
||||
#[cfg(feature = "wgpu_no_default_features")]
|
||||
pub wgpu_options: egui_wgpu::WgpuConfiguration,
|
||||
@@ -560,6 +535,9 @@ impl Default for WebOptions {
|
||||
#[cfg(feature = "glow")]
|
||||
webgl_context_option: WebGlContextOption::BestFirst,
|
||||
|
||||
#[cfg(feature = "glow")]
|
||||
glow_options: egui_glow::GlowConfiguration::default(),
|
||||
|
||||
#[cfg(feature = "wgpu_no_default_features")]
|
||||
wgpu_options: egui_wgpu::WgpuConfiguration::default(),
|
||||
|
||||
@@ -695,6 +673,10 @@ pub struct Frame {
|
||||
#[doc(hidden)]
|
||||
pub wgpu_render_state: Option<egui_wgpu::RenderState>,
|
||||
|
||||
/// The current [`winit::window::Window`] (i.e. the one the active viewport is rendered to).
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) window: Option<std::sync::Arc<winit::window::Window>>,
|
||||
|
||||
/// Raw platform window handle
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) raw_window_handle: Result<RawWindowHandle, HandleError>,
|
||||
@@ -740,6 +722,8 @@ impl Frame {
|
||||
raw_display_handle: Err(HandleError::NotSupported),
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
raw_window_handle: Err(HandleError::NotSupported),
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
window: None,
|
||||
storage: None,
|
||||
#[cfg(feature = "wgpu_no_default_features")]
|
||||
wgpu_render_state: None,
|
||||
@@ -769,6 +753,14 @@ impl Frame {
|
||||
self.storage.as_deref_mut()
|
||||
}
|
||||
|
||||
/// Access to the current [`winit::window::Window`] (i.e. the one the active viewport is rendered to).
|
||||
///
|
||||
/// `None` for headless (tests etc).
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn winit_window(&self) -> Option<&std::sync::Arc<winit::window::Window>> {
|
||||
self.window.as_ref()
|
||||
}
|
||||
|
||||
/// A reference to the underlying [`glow`] (OpenGL) context.
|
||||
///
|
||||
/// This can be used, for instance, to:
|
||||
@@ -776,7 +768,7 @@ impl Frame {
|
||||
/// * Read the pixel buffer from the previous frame (`glow::Context::read_pixels`).
|
||||
/// * Render things behind the egui windows.
|
||||
///
|
||||
/// Note that all egui painting is deferred to after the call to [`App::update`]
|
||||
/// Note that all egui painting is deferred to after the call to [`App::ui`]
|
||||
/// ([`egui`] only collects [`egui::Shape`]s and then eframe paints them all in one go later on).
|
||||
///
|
||||
/// To get a [`glow`] context you need to compile with the `glow` feature flag,
|
||||
@@ -805,6 +797,28 @@ impl Frame {
|
||||
pub fn wgpu_render_state(&self) -> Option<&egui_wgpu::RenderState> {
|
||||
self.wgpu_render_state.as_ref()
|
||||
}
|
||||
|
||||
/// The currently-applied runtime surface config (present mode, frame latency)
|
||||
/// used by the `wgpu` renderer, if any.
|
||||
///
|
||||
/// Returns `None` when not using the `wgpu` backend.
|
||||
#[cfg(feature = "wgpu_no_default_features")]
|
||||
pub fn wgpu_surface_config(&self) -> Option<egui_wgpu::SurfaceConfig> {
|
||||
self.wgpu_render_state
|
||||
.as_ref()
|
||||
.map(|state| state.surface_config)
|
||||
}
|
||||
|
||||
/// Set the runtime surface config (present mode, frame latency) for the `wgpu`
|
||||
/// renderer. The surface is reconfigured on the next paint.
|
||||
///
|
||||
/// No-op when not using the `wgpu` backend.
|
||||
#[cfg(feature = "wgpu_no_default_features")]
|
||||
pub fn set_wgpu_surface_config(&mut self, config: egui_wgpu::SurfaceConfig) {
|
||||
if let Some(state) = &mut self.wgpu_render_state {
|
||||
state.surface_config = config;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about the web environment (if applicable).
|
||||
@@ -882,7 +896,7 @@ pub struct IntegrationInfo {
|
||||
|
||||
/// Seconds of cpu usage (in seconds) on the previous frame.
|
||||
///
|
||||
/// This includes [`App::update`] as well as rendering (except for vsync waiting).
|
||||
/// This includes [`App::ui`] as well as rendering (except for vsync waiting).
|
||||
///
|
||||
/// For a more detailed view of cpu usage, connect your preferred profiler by enabling it's feature in [`profiling`](https://crates.io/crates/profiling).
|
||||
///
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//! To get started, see the [examples](https://github.com/emilk/egui/tree/main/examples).
|
||||
//! To learn how to set up `eframe` for web and native, go to <https://github.com/emilk/eframe_template/> and follow the instructions there!
|
||||
//!
|
||||
//! In short, you implement [`App`] (especially [`App::update`]) and then
|
||||
//! In short, you implement [`App`] (especially [`App::ui`]) and then
|
||||
//! call [`crate::run_native`] from your `main.rs`, and/or use `eframe::WebRunner` from your `lib.rs`.
|
||||
//!
|
||||
//! ## Compiling for web
|
||||
@@ -19,7 +19,7 @@
|
||||
//!
|
||||
//! ## Simplified usage
|
||||
//! If your app is only for native, and you don't need advanced features like state persistence,
|
||||
//! then you can use the simpler function [`run_simple_native`].
|
||||
//! then you can use the simpler function [`run_ui_native`].
|
||||
//!
|
||||
//! ## Usage, native:
|
||||
//! ``` no_run
|
||||
@@ -45,7 +45,7 @@
|
||||
//!
|
||||
//! impl eframe::App for MyEguiApp {
|
||||
//! fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||
//! egui::CentralPanel::default().show_inside(ui, |ui| {
|
||||
//! egui::CentralPanel::default().show(ui, |ui| {
|
||||
//! ui.heading("Hello World!");
|
||||
//! });
|
||||
//! }
|
||||
@@ -159,7 +159,7 @@ pub use {egui, egui::emath, egui::epaint};
|
||||
pub use {egui_glow, glow};
|
||||
|
||||
#[cfg(feature = "wgpu_no_default_features")]
|
||||
pub use {egui_wgpu, egui_wgpu::wgpu};
|
||||
pub use {egui_wgpu, egui_wgpu::SurfaceConfig, egui_wgpu::WgpuConfiguration, egui_wgpu::wgpu};
|
||||
|
||||
mod epi;
|
||||
|
||||
@@ -244,7 +244,7 @@ pub mod icon_data;
|
||||
///
|
||||
/// impl eframe::App for MyEguiApp {
|
||||
/// fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||
/// egui::CentralPanel::default().show_inside(ui, |ui| {
|
||||
/// egui::CentralPanel::default().show(ui, |ui| {
|
||||
/// ui.heading("Hello World!");
|
||||
/// });
|
||||
/// }
|
||||
@@ -257,8 +257,27 @@ pub mod icon_data;
|
||||
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
|
||||
#[allow(clippy::allow_attributes, clippy::needless_pass_by_value)]
|
||||
pub fn run_native(
|
||||
app_name: &str,
|
||||
native_options: NativeOptions,
|
||||
app_creator: AppCreator<'_>,
|
||||
) -> Result {
|
||||
run_native_ext(app_name, native_options, None, app_creator)
|
||||
}
|
||||
|
||||
/// Like [`run_native`], but lets you supply a pre-existing [`egui::Context`].
|
||||
///
|
||||
/// If `egui_ctx` is `Some`, that context will be used by eframe instead of creating a fresh one.
|
||||
/// If it is `None`, eframe creates a new context (same behavior as [`run_native`]).
|
||||
///
|
||||
/// # Errors
|
||||
/// This function can fail if we fail to set up a graphics context.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
|
||||
#[allow(clippy::allow_attributes, clippy::needless_pass_by_value)]
|
||||
pub fn run_native_ext(
|
||||
app_name: &str,
|
||||
mut native_options: NativeOptions,
|
||||
egui_ctx: Option<egui::Context>,
|
||||
app_creator: AppCreator<'_>,
|
||||
) -> Result {
|
||||
let renderer = init_native(app_name, &mut native_options);
|
||||
@@ -267,13 +286,13 @@ pub fn run_native(
|
||||
#[cfg(feature = "glow")]
|
||||
Renderer::Glow => {
|
||||
log::debug!("Using the glow renderer");
|
||||
native::run::run_glow(app_name, native_options, app_creator)
|
||||
native::run::run_glow(app_name, native_options, egui_ctx, app_creator)
|
||||
}
|
||||
|
||||
#[cfg(feature = "wgpu_no_default_features")]
|
||||
Renderer::Wgpu => {
|
||||
log::debug!("Using the wgpu renderer");
|
||||
native::run::run_wgpu(app_name, native_options, app_creator)
|
||||
native::run::run_wgpu(app_name, native_options, egui_ctx, app_creator)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -315,7 +334,7 @@ pub fn run_native(
|
||||
///
|
||||
/// impl eframe::App for MyEguiApp {
|
||||
/// fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||
/// egui::CentralPanel::default().show_inside(ui, |ui| {
|
||||
/// egui::CentralPanel::default().show(ui, |ui| {
|
||||
/// ui.heading("Hello World!");
|
||||
/// });
|
||||
/// }
|
||||
@@ -370,6 +389,9 @@ fn init_native(app_name: &str, native_options: &mut NativeOptions) -> Renderer {
|
||||
if native_options.viewport.title.is_none() {
|
||||
native_options.viewport.title = Some(app_name.to_owned());
|
||||
}
|
||||
if native_options.viewport.app_id.is_none() {
|
||||
native_options.viewport.app_id = Some(app_name.to_owned());
|
||||
}
|
||||
|
||||
let renderer = native_options.renderer;
|
||||
|
||||
@@ -403,7 +425,7 @@ fn init_native(app_name: &str, native_options: &mut NativeOptions) -> Renderer {
|
||||
/// let options = eframe::NativeOptions::default();
|
||||
/// eframe::run_ui_native("My egui App", options, move |ui, _frame| {
|
||||
/// // Wrap everything in a CentralPanel so we get some margins and a background color:
|
||||
/// egui::CentralPanel::default().show_inside(ui, |ui| {
|
||||
/// egui::CentralPanel::default().show(ui, |ui| {
|
||||
/// ui.heading("My egui Application");
|
||||
/// ui.horizontal(|ui| {
|
||||
/// let name_label = ui.label("Your name: ");
|
||||
@@ -446,67 +468,6 @@ pub fn run_ui_native(
|
||||
)
|
||||
}
|
||||
|
||||
/// The simplest way to get started when writing a native app.
|
||||
///
|
||||
/// This does NOT support persistence of custom user data. For that you need to use [`run_native`].
|
||||
/// However, it DOES support persistence of egui data (window positions and sizes, how far the user has scrolled in a
|
||||
/// [`ScrollArea`](egui::ScrollArea), etc.) if the persistence feature is enabled.
|
||||
///
|
||||
/// # Example
|
||||
/// ``` no_run
|
||||
/// fn main() -> eframe::Result {
|
||||
/// // Our application state:
|
||||
/// let mut name = "Arthur".to_owned();
|
||||
/// let mut age = 42;
|
||||
///
|
||||
/// let options = eframe::NativeOptions::default();
|
||||
/// eframe::run_simple_native("My egui App", options, move |ctx, _frame| {
|
||||
/// egui::CentralPanel::default().show(ctx, |ui| {
|
||||
/// ui.heading("My egui Application");
|
||||
/// ui.horizontal(|ui| {
|
||||
/// let name_label = ui.label("Your name: ");
|
||||
/// ui.text_edit_singleline(&mut name)
|
||||
/// .labelled_by(name_label.id);
|
||||
/// });
|
||||
/// ui.add(egui::Slider::new(&mut age, 0..=120).text("age"));
|
||||
/// if ui.button("Increment").clicked() {
|
||||
/// age += 1;
|
||||
/// }
|
||||
/// ui.label(format!("Hello '{name}', age {age}"));
|
||||
/// });
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Errors
|
||||
/// This function can fail if we fail to set up a graphics context.
|
||||
#[deprecated = "Use run_ui_native instead"]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
|
||||
pub fn run_simple_native(
|
||||
app_name: &str,
|
||||
native_options: NativeOptions,
|
||||
update_fun: impl FnMut(&egui::Context, &mut Frame) + 'static,
|
||||
) -> Result {
|
||||
struct SimpleApp<U> {
|
||||
update_fun: U,
|
||||
}
|
||||
|
||||
impl<U: FnMut(&egui::Context, &mut Frame) + 'static> App for SimpleApp<U> {
|
||||
fn ui(&mut self, _ui: &mut egui::Ui, _frame: &mut Frame) {}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut Frame) {
|
||||
(self.update_fun)(ctx, frame);
|
||||
}
|
||||
}
|
||||
|
||||
run_native(
|
||||
app_name,
|
||||
native_options,
|
||||
Box::new(|_cc| Ok(Box::new(SimpleApp { update_fun }))),
|
||||
)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// The different problems that can occur when trying to run `eframe`.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use web_time::Instant;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use winit::event_loop::ActiveEventLoop;
|
||||
|
||||
use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _};
|
||||
@@ -171,7 +171,7 @@ impl EpiIntegration {
|
||||
#[allow(clippy::allow_attributes, clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
egui_ctx: egui::Context,
|
||||
window: &winit::window::Window,
|
||||
window: &Arc<winit::window::Window>,
|
||||
app_name: &str,
|
||||
native_options: &crate::NativeOptions,
|
||||
storage: Option<Box<dyn epi::Storage>>,
|
||||
@@ -192,6 +192,7 @@ impl EpiIntegration {
|
||||
glow_register_native_texture,
|
||||
#[cfg(feature = "wgpu_no_default_features")]
|
||||
wgpu_render_state,
|
||||
window: Some(Arc::clone(window)),
|
||||
raw_display_handle: window.display_handle().map(|h| h.as_raw()),
|
||||
raw_window_handle: window.window_handle().map(|h| h.as_raw()),
|
||||
};
|
||||
@@ -214,15 +215,17 @@ impl EpiIntegration {
|
||||
Self {
|
||||
frame,
|
||||
last_auto_save: Instant::now(),
|
||||
egui_ctx,
|
||||
pending_full_output: Default::default(),
|
||||
close: false,
|
||||
can_drag_window: false,
|
||||
#[cfg(feature = "persistence")]
|
||||
persist_window: native_options.persist_window,
|
||||
app_icon_setter,
|
||||
beginning: Instant::now(),
|
||||
beginning: Instant::now()
|
||||
.checked_sub(web_time::Duration::from_secs_f64(egui_ctx.time()))
|
||||
.unwrap_or_else(Instant::now),
|
||||
is_first_frame: true,
|
||||
egui_ctx,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,7 +262,7 @@ impl EpiIntegration {
|
||||
|
||||
/// Run user code - this can create immediate viewports, so hold no locks over this!
|
||||
///
|
||||
/// If `viewport_ui_cb` is None, we are in the root viewport and will call [`crate::App::update`].
|
||||
/// If `viewport_ui_cb` is None, we are in the root viewport and will call [`crate::App::ui`].
|
||||
pub fn update(
|
||||
&mut self,
|
||||
app: &mut dyn epi::App,
|
||||
@@ -287,12 +290,6 @@ impl EpiIntegration {
|
||||
}
|
||||
|
||||
if is_visible {
|
||||
{
|
||||
profiling::scope!("App::update");
|
||||
#[expect(deprecated)]
|
||||
app.update(ui.ctx(), &mut self.frame);
|
||||
}
|
||||
|
||||
{
|
||||
profiling::scope!("App::ui");
|
||||
app.ui(ui, &mut self.frame);
|
||||
|
||||
@@ -207,7 +207,7 @@ fn save_to_disk(file_path: &PathBuf, kv: &HashMap<String, String>) {
|
||||
profiling::scope!("ron::serialize");
|
||||
if let Err(err) = ron::Options::default()
|
||||
.to_io_writer_pretty(&mut writer, &kv, config)
|
||||
.and_then(|_| writer.flush().map_err(|err| err.into()))
|
||||
.and_then(|()| writer.flush().map_err(|err| err.into()))
|
||||
{
|
||||
log::warn!("Failed to serialize app state: {err}");
|
||||
} else {
|
||||
|
||||
@@ -55,6 +55,10 @@ pub struct GlowWinitApp<'app> {
|
||||
// re-initializing the `GlowWinitRunning` state on Android if the application
|
||||
// suspends and resumes.
|
||||
app_creator: Option<AppCreator<'app>>,
|
||||
|
||||
/// An optional pre-existing egui context. If `Some`, it is used instead of
|
||||
/// creating a new one via [`create_egui_context`]. Taken during initialization.
|
||||
egui_ctx: Option<egui::Context>,
|
||||
}
|
||||
|
||||
/// State that is initialized when the application is first starts running via
|
||||
@@ -128,6 +132,7 @@ impl<'app> GlowWinitApp<'app> {
|
||||
event_loop: &EventLoop<UserEvent>,
|
||||
app_name: &str,
|
||||
native_options: NativeOptions,
|
||||
egui_ctx: Option<egui::Context>,
|
||||
app_creator: AppCreator<'app>,
|
||||
) -> Self {
|
||||
profiling::function_scope!();
|
||||
@@ -137,6 +142,7 @@ impl<'app> GlowWinitApp<'app> {
|
||||
native_options,
|
||||
running: None,
|
||||
app_creator: Some(app_creator),
|
||||
egui_ctx,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +190,7 @@ impl<'app> GlowWinitApp<'app> {
|
||||
let painter = egui_glow::Painter::new(
|
||||
gl,
|
||||
"",
|
||||
native_options.shader_version,
|
||||
native_options.glow_options.shader_version,
|
||||
native_options.dithering,
|
||||
)?;
|
||||
|
||||
@@ -209,7 +215,10 @@ impl<'app> GlowWinitApp<'app> {
|
||||
)
|
||||
};
|
||||
|
||||
let egui_ctx = create_egui_context(storage.as_deref());
|
||||
let egui_ctx = self
|
||||
.egui_ctx
|
||||
.take()
|
||||
.unwrap_or_else(|| create_egui_context(storage.as_deref()));
|
||||
|
||||
let (mut glutin, painter) = Self::create_glutin_windowed_context(
|
||||
&egui_ctx,
|
||||
@@ -305,6 +314,7 @@ impl<'app> GlowWinitApp<'app> {
|
||||
get_proc_address: Some(Arc::new(get_proc_address)),
|
||||
#[cfg(feature = "wgpu_no_default_features")]
|
||||
wgpu_render_state: None,
|
||||
window: Some(Arc::clone(&window)),
|
||||
raw_display_handle: window.display_handle().map(|h| h.as_raw()),
|
||||
raw_window_handle: window.window_handle().map(|h| h.as_raw()),
|
||||
};
|
||||
@@ -689,7 +699,7 @@ impl GlowWinitRunning<'_> {
|
||||
let gl_surface = viewport.gl_surface.as_ref().unwrap();
|
||||
let egui_winit = viewport.egui_winit.as_mut().unwrap();
|
||||
|
||||
egui_winit.handle_platform_output(&window, platform_output);
|
||||
egui_winit.handle_platform_output_with_event_loop(&window, event_loop, platform_output);
|
||||
|
||||
if is_visible {
|
||||
let clipped_primitives = integration.egui_ctx.tessellate(shapes, pixels_per_point);
|
||||
@@ -971,12 +981,12 @@ impl GlutinWindowContext {
|
||||
|
||||
use glutin::prelude::*;
|
||||
// convert native options to glutin options
|
||||
let hardware_acceleration = match native_options.hardware_acceleration {
|
||||
crate::HardwareAcceleration::Required => Some(true),
|
||||
crate::HardwareAcceleration::Preferred => None,
|
||||
crate::HardwareAcceleration::Off => Some(false),
|
||||
let hardware_acceleration = match native_options.glow_options.hardware_acceleration {
|
||||
egui_glow::HardwareAcceleration::Required => Some(true),
|
||||
egui_glow::HardwareAcceleration::Preferred => None,
|
||||
egui_glow::HardwareAcceleration::Off => Some(false),
|
||||
};
|
||||
let swap_interval = if native_options.vsync {
|
||||
let swap_interval = if native_options.glow_options.vsync {
|
||||
glutin::surface::SwapInterval::Wait(NonZeroU32::MIN)
|
||||
} else {
|
||||
glutin::surface::SwapInterval::DontWait
|
||||
|
||||
@@ -399,6 +399,7 @@ fn run_and_exit(event_loop: EventLoop<UserEvent>, winit_app: impl WinitApp) -> R
|
||||
pub fn run_glow(
|
||||
app_name: &str,
|
||||
mut native_options: epi::NativeOptions,
|
||||
egui_ctx: Option<egui::Context>,
|
||||
app_creator: epi::AppCreator<'_>,
|
||||
) -> Result {
|
||||
use super::glow_integration::GlowWinitApp;
|
||||
@@ -406,13 +407,15 @@ pub fn run_glow(
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
if native_options.run_and_return {
|
||||
return with_event_loop(native_options, |event_loop, native_options| {
|
||||
let glow_eframe = GlowWinitApp::new(event_loop, app_name, native_options, app_creator);
|
||||
let glow_eframe =
|
||||
GlowWinitApp::new(event_loop, app_name, native_options, egui_ctx, app_creator);
|
||||
run_and_return(event_loop, glow_eframe)
|
||||
})?;
|
||||
}
|
||||
|
||||
let event_loop = create_event_loop(&mut native_options)?;
|
||||
let glow_eframe = GlowWinitApp::new(&event_loop, app_name, native_options, app_creator);
|
||||
let glow_eframe =
|
||||
GlowWinitApp::new(&event_loop, app_name, native_options, egui_ctx, app_creator);
|
||||
run_and_exit(event_loop, glow_eframe)
|
||||
}
|
||||
|
||||
@@ -425,7 +428,7 @@ pub fn create_glow<'a>(
|
||||
) -> impl ApplicationHandler<UserEvent> + 'a {
|
||||
use super::glow_integration::GlowWinitApp;
|
||||
|
||||
let glow_eframe = GlowWinitApp::new(event_loop, app_name, native_options, app_creator);
|
||||
let glow_eframe = GlowWinitApp::new(event_loop, app_name, native_options, None, app_creator);
|
||||
WinitAppWrapper::new(glow_eframe, true)
|
||||
}
|
||||
|
||||
@@ -435,6 +438,7 @@ pub fn create_glow<'a>(
|
||||
pub fn run_wgpu(
|
||||
app_name: &str,
|
||||
mut native_options: epi::NativeOptions,
|
||||
egui_ctx: Option<egui::Context>,
|
||||
app_creator: epi::AppCreator<'_>,
|
||||
) -> Result {
|
||||
use super::wgpu_integration::WgpuWinitApp;
|
||||
@@ -442,13 +446,15 @@ pub fn run_wgpu(
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
if native_options.run_and_return {
|
||||
return with_event_loop(native_options, |event_loop, native_options| {
|
||||
let wgpu_eframe = WgpuWinitApp::new(event_loop, app_name, native_options, app_creator);
|
||||
let wgpu_eframe =
|
||||
WgpuWinitApp::new(event_loop, app_name, native_options, egui_ctx, app_creator);
|
||||
run_and_return(event_loop, wgpu_eframe)
|
||||
})?;
|
||||
}
|
||||
|
||||
let event_loop = create_event_loop(&mut native_options)?;
|
||||
let wgpu_eframe = WgpuWinitApp::new(&event_loop, app_name, native_options, app_creator);
|
||||
let wgpu_eframe =
|
||||
WgpuWinitApp::new(&event_loop, app_name, native_options, egui_ctx, app_creator);
|
||||
run_and_exit(event_loop, wgpu_eframe)
|
||||
}
|
||||
|
||||
@@ -461,7 +467,7 @@ pub fn create_wgpu<'a>(
|
||||
) -> impl ApplicationHandler<UserEvent> + 'a {
|
||||
use super::wgpu_integration::WgpuWinitApp;
|
||||
|
||||
let wgpu_eframe = WgpuWinitApp::new(event_loop, app_name, native_options, app_creator);
|
||||
let wgpu_eframe = WgpuWinitApp::new(event_loop, app_name, native_options, None, app_creator);
|
||||
WinitAppWrapper::new(wgpu_eframe, true)
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,10 @@ pub struct WgpuWinitApp<'app> {
|
||||
|
||||
/// Set when we are actually up and running.
|
||||
running: Option<WgpuWinitRunning<'app>>,
|
||||
|
||||
/// An optional pre-existing egui context. If `Some`, it is used instead of
|
||||
/// creating a new one via [`winit_integration::create_egui_context`]. Taken during initialization.
|
||||
egui_ctx: Option<egui::Context>,
|
||||
}
|
||||
|
||||
/// State that is initialized when the application is first starts running via
|
||||
@@ -105,6 +109,7 @@ impl<'app> WgpuWinitApp<'app> {
|
||||
event_loop: &EventLoop<UserEvent>,
|
||||
app_name: &str,
|
||||
native_options: NativeOptions,
|
||||
egui_ctx: Option<egui::Context>,
|
||||
app_creator: AppCreator<'app>,
|
||||
) -> Self {
|
||||
profiling::function_scope!();
|
||||
@@ -121,6 +126,7 @@ impl<'app> WgpuWinitApp<'app> {
|
||||
native_options,
|
||||
running: None,
|
||||
app_creator: Some(app_creator),
|
||||
egui_ctx,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,6 +300,7 @@ impl<'app> WgpuWinitApp<'app> {
|
||||
#[cfg(feature = "glow")]
|
||||
get_proc_address: None,
|
||||
wgpu_render_state,
|
||||
window: Some(Arc::clone(&window)),
|
||||
raw_display_handle: window.display_handle().map(|h| h.as_raw()),
|
||||
raw_window_handle: window.window_handle().map(|h| h.as_raw()),
|
||||
};
|
||||
@@ -403,7 +410,7 @@ impl WinitApp for WgpuWinitApp<'_> {
|
||||
self.initialized_all_windows(event_loop);
|
||||
|
||||
if let Some(running) = &mut self.running {
|
||||
running.run_ui_and_paint(window_id)
|
||||
running.run_ui_and_paint(window_id, event_loop)
|
||||
} else {
|
||||
Ok(EventResult::Wait)
|
||||
}
|
||||
@@ -428,7 +435,10 @@ impl WinitApp for WgpuWinitApp<'_> {
|
||||
.unwrap_or(&self.app_name),
|
||||
)
|
||||
};
|
||||
let egui_ctx = winit_integration::create_egui_context(storage.as_deref());
|
||||
let egui_ctx = self
|
||||
.egui_ctx
|
||||
.take()
|
||||
.unwrap_or_else(|| winit_integration::create_egui_context(storage.as_deref()));
|
||||
let (window, builder) = create_window(
|
||||
&egui_ctx,
|
||||
event_loop,
|
||||
@@ -580,7 +590,11 @@ impl WgpuWinitRunning<'_> {
|
||||
}
|
||||
|
||||
/// This is called both for the root viewport, and all deferred viewports
|
||||
fn run_ui_and_paint(&mut self, window_id: WindowId) -> Result<EventResult> {
|
||||
fn run_ui_and_paint(
|
||||
&mut self,
|
||||
window_id: WindowId,
|
||||
event_loop: &ActiveEventLoop,
|
||||
) -> Result<EventResult> {
|
||||
profiling::function_scope!();
|
||||
|
||||
let Some(viewport_id) = self
|
||||
@@ -721,7 +735,7 @@ impl WgpuWinitRunning<'_> {
|
||||
return Ok(EventResult::Wait);
|
||||
};
|
||||
|
||||
egui_winit.handle_platform_output(window, platform_output);
|
||||
egui_winit.handle_platform_output_with_event_loop(window, event_loop, platform_output);
|
||||
|
||||
let vsync_secs = if is_visible {
|
||||
let clipped_primitives = egui_ctx.tessellate(shapes, pixels_per_point);
|
||||
@@ -742,6 +756,7 @@ impl WgpuWinitRunning<'_> {
|
||||
&clipped_primitives,
|
||||
&textures_delta,
|
||||
screenshot_commands,
|
||||
window,
|
||||
);
|
||||
|
||||
for action in viewport.actions_requested.drain(..) {
|
||||
@@ -1131,6 +1146,7 @@ fn render_immediate_viewport(
|
||||
&clipped_primitives,
|
||||
&textures_delta,
|
||||
vec![],
|
||||
window,
|
||||
);
|
||||
|
||||
egui_winit.handle_platform_output(window, platform_output);
|
||||
|
||||
@@ -284,9 +284,6 @@ impl AppRunner {
|
||||
self.app.logic(ui.ctx(), &mut self.frame);
|
||||
|
||||
if is_visible {
|
||||
#[expect(deprecated)]
|
||||
self.app.update(ui.ctx(), &mut self.frame);
|
||||
|
||||
self.app.ui(ui, &mut self.frame);
|
||||
}
|
||||
});
|
||||
@@ -371,7 +368,8 @@ impl AppRunner {
|
||||
let egui::PlatformOutput {
|
||||
commands,
|
||||
cursor_icon,
|
||||
events: _, // already handled
|
||||
cursor_image: _, // TODO(alextournai): support custom bitmap cursors on the web (via CSS `url(...)`)
|
||||
events: _, // already handled
|
||||
mutable_text_under_cursor: _, // TODO(#4569): https://github.com/emilk/egui/issues/4569
|
||||
ime,
|
||||
accesskit_update: _, // not currently implemented
|
||||
|
||||
@@ -1114,16 +1114,16 @@ fn get_display_size(resize_observer_entries: &js_sys::Array) -> Result<(u32, u32
|
||||
} else if JsValue::from_str("contentBoxSize").js_in(entry.as_ref()) {
|
||||
let content_box_size = entry.content_box_size();
|
||||
let idx0 = content_box_size.at(0);
|
||||
if !idx0.is_undefined() {
|
||||
let size: web_sys::ResizeObserverSize = idx0.dyn_into()?;
|
||||
width = size.inline_size();
|
||||
height = size.block_size();
|
||||
} else {
|
||||
if idx0.is_undefined() {
|
||||
// legacy
|
||||
let size = JsValue::clone(content_box_size.as_ref());
|
||||
let size: web_sys::ResizeObserverSize = size.dyn_into()?;
|
||||
width = size.inline_size();
|
||||
height = size.block_size();
|
||||
} else {
|
||||
let size: web_sys::ResizeObserverSize = idx0.dyn_into()?;
|
||||
width = size.inline_size();
|
||||
height = size.block_size();
|
||||
}
|
||||
if DEBUG_RESIZE {
|
||||
log::info!("contentBoxSize {width}x{height}");
|
||||
|
||||
@@ -31,11 +31,12 @@ pub fn primary_touch_pos(
|
||||
runner: &mut AppRunner,
|
||||
event: &web_sys::TouchEvent,
|
||||
) -> Option<(egui::Pos2, web_sys::Touch)> {
|
||||
let all_touches: Vec<_> = (0..event.touches().length())
|
||||
.filter_map(|i| event.touches().get(i))
|
||||
// On touchend we don't get anything in `touches`, but we still get `changed_touches`, so include those:
|
||||
.chain((0..event.changed_touches().length()).filter_map(|i| event.changed_touches().get(i)))
|
||||
.collect();
|
||||
// On touchend we don't get anything in `touches`, but we still get `changed_touches`, so include those:
|
||||
let all_touches: Vec<_> = std::iter::chain(
|
||||
(0..event.touches().length()).filter_map(|i| event.touches().get(i)),
|
||||
(0..event.changed_touches().length()).filter_map(|i| event.changed_touches().get(i)),
|
||||
)
|
||||
.collect();
|
||||
|
||||
if let Some(primary_touch) = runner.input.primary_touch {
|
||||
// Is the primary touch is gone?
|
||||
|
||||
@@ -9,7 +9,19 @@ pub fn local_storage_get(key: &str) -> Option<String> {
|
||||
|
||||
/// Write data to local storage.
|
||||
pub fn local_storage_set(key: &str, value: &str) {
|
||||
local_storage().map(|storage| storage.set_item(key, value));
|
||||
match local_storage() {
|
||||
Some(storage) => {
|
||||
if let Err(err) = storage.set_item(key, value) {
|
||||
log::warn!(
|
||||
"local_storage_set failed: key={key}, err={}",
|
||||
crate::web::string_from_js_value(&err)
|
||||
);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
log::warn!("local_storage unavailable");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "persistence")]
|
||||
|
||||
@@ -56,8 +56,13 @@ impl TextAgent {
|
||||
let input = input.clone();
|
||||
move |event: web_sys::InputEvent, runner: &mut AppRunner| {
|
||||
let text = input.value();
|
||||
// Fix android virtual keyboard Gboard
|
||||
// This removes the virtual keyboard's suggestion.
|
||||
// Workaround for an Android Gboard issue: after typing a word,
|
||||
// the user has to delete invisible characters (whose count
|
||||
// matches the length of the current suggestion) before actual
|
||||
// characters are deleted, unless the focus has been reset.
|
||||
//
|
||||
// this issue appears to have been fixed in Gboard sometime
|
||||
// between versions 14.7.09 and 17.0.12.
|
||||
if !event.is_composing() {
|
||||
input.blur().ok();
|
||||
input.focus().ok();
|
||||
@@ -75,11 +80,7 @@ impl TextAgent {
|
||||
};
|
||||
|
||||
let on_composition_start = {
|
||||
let input = input.clone();
|
||||
move |_: web_sys::CompositionEvent, runner: &mut AppRunner| {
|
||||
input.set_value("");
|
||||
let event = egui::Event::Ime(egui::ImeEvent::Enabled);
|
||||
runner.input.raw.events.push(event);
|
||||
// Repaint moves the text agent into place,
|
||||
// see `move_to` in `AppRunner::handle_platform_output`.
|
||||
runner.needs_repaint.repaint_asap();
|
||||
@@ -136,6 +137,12 @@ impl TextAgent {
|
||||
|
||||
let Some(ime) = ime else { return Ok(()) };
|
||||
|
||||
if ime.should_interrupt_composition {
|
||||
// no-op for now: currently, the text agent is sizeless, so any
|
||||
// click shifts focus to the canvas, which naturally interrupts the
|
||||
// composition.
|
||||
}
|
||||
|
||||
let mut canvas_rect = super::canvas_content_rect(canvas);
|
||||
// Fix for safari with virtual keyboard flapping position
|
||||
if is_mobile_safari() {
|
||||
|
||||
@@ -31,8 +31,13 @@ impl WebPainterGlow {
|
||||
#[allow(clippy::allow_attributes, clippy::arc_with_non_send_sync)] // For wasm
|
||||
let gl = std::sync::Arc::new(gl);
|
||||
|
||||
let painter = egui_glow::Painter::new(gl, shader_prefix, None, options.dithering)
|
||||
.map_err(|err| format!("Error starting glow painter: {err}"))?;
|
||||
let painter = egui_glow::Painter::new(
|
||||
gl,
|
||||
shader_prefix,
|
||||
options.glow_options.shader_version,
|
||||
options.dithering,
|
||||
)
|
||||
.map_err(|err| format!("Error starting glow painter: {err}"))?;
|
||||
|
||||
Ok(Self {
|
||||
canvas,
|
||||
|
||||
@@ -12,6 +12,7 @@ use super::web_painter::WebPainter;
|
||||
|
||||
pub(crate) struct WebPainterWgpu {
|
||||
canvas: HtmlCanvasElement,
|
||||
instance: wgpu::Instance,
|
||||
surface: wgpu::Surface<'static>,
|
||||
surface_configuration: wgpu::SurfaceConfiguration,
|
||||
render_state: Option<RenderState>,
|
||||
@@ -23,6 +24,7 @@ pub(crate) struct WebPainterWgpu {
|
||||
capture_rx: CaptureReceiver,
|
||||
ctx: egui::Context,
|
||||
needs_reconfigure: bool,
|
||||
needs_recreate: bool,
|
||||
}
|
||||
|
||||
/// Owned web display handle that is `Send + Sync`.
|
||||
@@ -118,7 +120,7 @@ impl WebPainterWgpu {
|
||||
|
||||
let surface_configuration = wgpu::SurfaceConfiguration {
|
||||
format: render_state.target_format,
|
||||
present_mode: wgpu_options.present_mode,
|
||||
present_mode: wgpu_options.surface.present_mode,
|
||||
view_formats: vec![render_state.target_format],
|
||||
..default_configuration
|
||||
};
|
||||
@@ -129,6 +131,7 @@ impl WebPainterWgpu {
|
||||
|
||||
Ok(Self {
|
||||
canvas,
|
||||
instance,
|
||||
render_state: Some(render_state),
|
||||
surface,
|
||||
surface_configuration,
|
||||
@@ -140,6 +143,7 @@ impl WebPainterWgpu {
|
||||
capture_rx,
|
||||
ctx,
|
||||
needs_reconfigure: false,
|
||||
needs_recreate: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -173,6 +177,24 @@ impl WebPainter for WebPainterWgpu {
|
||||
));
|
||||
};
|
||||
|
||||
// If the previous frame produced `CurrentSurfaceTexture::Lost`, drop and recreate the
|
||||
// surface from the canvas before re-borrowing `self.render_state` for the rest of paint.
|
||||
if self.needs_recreate {
|
||||
self.needs_recreate = false;
|
||||
match self
|
||||
.instance
|
||||
.create_surface(wgpu::SurfaceTarget::Canvas(self.canvas.clone()))
|
||||
{
|
||||
Ok(new_surface) => {
|
||||
new_surface.configure(&render_state.device, &self.surface_configuration);
|
||||
self.surface = new_surface;
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to recreate wgpu surface for canvas: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut encoder =
|
||||
render_state
|
||||
.device
|
||||
@@ -239,10 +261,18 @@ impl WebPainter for WebPainterWgpu {
|
||||
}
|
||||
other => {
|
||||
match (*self.on_surface_status)(&other) {
|
||||
SurfaceErrorAction::RecreateSurface => {
|
||||
SurfaceErrorAction::Reconfigure => {
|
||||
self.surface
|
||||
.configure(&render_state.device, &self.surface_configuration);
|
||||
}
|
||||
SurfaceErrorAction::RecreateSurface => {
|
||||
// Full recovery needs `&mut self`, which conflicts with the live
|
||||
// `render_state` / `self.surface` borrows here. Defer to the top
|
||||
// of the next paint via the `needs_recreate` flag, and request a
|
||||
// repaint so the next frame actually invokes `paint` to consume it.
|
||||
self.needs_recreate = true;
|
||||
self.ctx.request_repaint();
|
||||
}
|
||||
SurfaceErrorAction::SkipFrame => {}
|
||||
}
|
||||
return Ok(());
|
||||
@@ -335,7 +365,7 @@ impl WebPainter for WebPainterWgpu {
|
||||
// Submit the commands: both the main buffer and user-defined ones.
|
||||
render_state
|
||||
.queue
|
||||
.submit(user_cmd_bufs.into_iter().chain([encoder.finish()]));
|
||||
.submit(std::iter::chain(user_cmd_bufs, [encoder.finish()]));
|
||||
|
||||
if let Some((frame, capture_buffer)) = frame_and_capture_buffer {
|
||||
if let Some(capture_buffer) = capture_buffer
|
||||
|
||||
Reference in New Issue
Block a user