1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-30 05:10:03 -04:00

Merge branch 'main' into ime-preedit-visuals

This commit is contained in:
Umaĵo
2026-05-27 07:48:06 +08:00
committed by GitHub
230 changed files with 5317 additions and 4687 deletions

View File

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

View File

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

View File

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

View File

@@ -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()),
};
@@ -670,7 +680,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);
@@ -952,12 +962,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

View File

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

View File

@@ -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,
@@ -560,7 +570,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
@@ -701,7 +715,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);

View File

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

View File

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

View File

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

View File

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