eframe error handling (#2433)
* eframe::run_native: return errors instead of crashing * Detect and handle glutin errors * egui_demo_app: silence wgpu log spam * Add trace logs for why eframe is shutting down * Fix: only save App state once on Mac * Handle Winit failure * Log where we load app state from * Don't panic on zero-sized window * Clamp loaded window size to not be too tiny to see * Simplify code: more shared code in window_builder * Improve code readability * Fix wasm32 build * fix android * Update changelog
This commit is contained in:
parent
1437ec8903
commit
cb77458f70
27 changed files with 249 additions and 162 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1325,6 +1325,7 @@ dependencies = [
|
|||
"raw-window-handle 0.5.0",
|
||||
"ron",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"tts",
|
||||
"wasm-bindgen",
|
||||
|
|
|
@ -6,6 +6,9 @@ NOTE: [`egui-winit`](../egui-winit/CHANGELOG.md), [`egui_glium`](../egui_glium/C
|
|||
|
||||
## Unreleased
|
||||
|
||||
#### Desktop/Native:
|
||||
* `eframe::run_native` now returns a `Result` ([#2433](https://github.com/emilk/egui/pull/2433)).
|
||||
|
||||
|
||||
## 0.20.1 - 2022-12-11
|
||||
* Fix docs.rs build ([#2420](https://github.com/emilk/egui/pull/2420)).
|
||||
|
|
|
@ -72,6 +72,7 @@ egui = { version = "0.20.0", path = "../egui", default-features = false, feature
|
|||
"bytemuck",
|
||||
"tracing",
|
||||
] }
|
||||
thiserror = "1.0.37"
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
|
||||
#! ### Optional dependencies
|
||||
|
|
|
@ -708,6 +708,7 @@ impl Frame {
|
|||
#[doc(alias = "exit")]
|
||||
#[doc(alias = "quit")]
|
||||
pub fn close(&mut self) {
|
||||
tracing::debug!("eframe::Frame::close called");
|
||||
self.output.close = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -127,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)
|
||||
|
@ -174,9 +174,16 @@ 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"))]
|
||||
|
@ -189,17 +196,41 @@ pub fn run_native(app_name: &str, native_options: NativeOptions, app_creator: Ap
|
|||
#[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 EframeError {
|
||||
#[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] wgpu::RequestDeviceError),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, EframeError>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Profiling macro for feature "puffin"
|
||||
|
|
|
@ -49,6 +49,7 @@ pub fn read_window_info(window: &winit::window::Window, pixels_per_point: f32) -
|
|||
}
|
||||
|
||||
pub fn window_builder(
|
||||
title: &str,
|
||||
native_options: &epi::NativeOptions,
|
||||
window_settings: &Option<WindowSettings>,
|
||||
) -> winit::window::WindowBuilder {
|
||||
|
@ -73,13 +74,17 @@ pub fn window_builder(
|
|||
let window_icon = icon_data.clone().and_then(load_icon);
|
||||
|
||||
let mut window_builder = winit::window::WindowBuilder::new()
|
||||
.with_title(title)
|
||||
.with_always_on_top(*always_on_top)
|
||||
.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 {
|
||||
|
@ -308,8 +313,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,
|
||||
|
@ -351,6 +363,7 @@ 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);
|
||||
|
@ -432,7 +445,9 @@ const STORAGE_WINDOW_KEY: &str = "window";
|
|||
pub fn load_window_settings(_storage: Option<&dyn epi::Storage>) -> Option<WindowSettings> {
|
||||
#[cfg(feature = "persistence")]
|
||||
{
|
||||
epi::get_value(_storage?, STORAGE_WINDOW_KEY)
|
||||
let mut settings: WindowSettings = epi::get_value(_storage?, STORAGE_WINDOW_KEY)?;
|
||||
settings.clamp_to_sane_values();
|
||||
Some(settings)
|
||||
}
|
||||
#[cfg(not(feature = "persistence"))]
|
||||
None
|
||||
|
|
|
@ -26,6 +26,7 @@ impl FileStorage {
|
|||
/// Store the state in this .ron file.
|
||||
pub fn from_ron_filepath(ron_filepath: impl Into<PathBuf>) -> Self {
|
||||
let ron_filepath: PathBuf = ron_filepath.into();
|
||||
tracing::debug!("Loading app state from {:?}…", ron_filepath);
|
||||
Self {
|
||||
kv: read_ron(&ron_filepath).unwrap_or_default(),
|
||||
ron_filepath,
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
//! 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};
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
use egui_winit::accesskit_winit;
|
||||
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 enum UserEvent {
|
||||
|
@ -60,7 +63,7 @@ trait WinitApp {
|
|||
&mut self,
|
||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
event: &winit::event::Event<'_, UserEvent>,
|
||||
) -> EventResult;
|
||||
) -> Result<EventResult>;
|
||||
}
|
||||
|
||||
fn create_event_loop_builder(
|
||||
|
@ -79,10 +82,10 @@ 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<UserEvent>, NativeOptions),
|
||||
) {
|
||||
f: impl FnOnce(&mut EventLoop<UserEvent>, NativeOptions) -> R,
|
||||
) -> R {
|
||||
use std::cell::RefCell;
|
||||
thread_local!(static EVENT_LOOP: RefCell<Option<EventLoop<UserEvent>>> = RefCell::new(None));
|
||||
|
||||
|
@ -93,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<UserEvent>, 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
|
||||
|
@ -137,7 +149,14 @@ fn run_and_return(event_loop: &mut EventLoop<UserEvent>, mut winit_app: impl Win
|
|||
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 {
|
||||
|
@ -155,10 +174,7 @@ fn run_and_return(event_loop: &mut EventLoop<UserEvent>, mut winit_app: impl Win
|
|||
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;
|
||||
}
|
||||
|
@ -187,16 +203,21 @@ fn run_and_return(event_loop: &mut EventLoop<UserEvent>, mut winit_app: impl Win
|
|||
event_loop.run_return(|_, _, control_flow| {
|
||||
control_flow.set_exit();
|
||||
});
|
||||
|
||||
returned_result
|
||||
}
|
||||
|
||||
fn run_and_exit(event_loop: EventLoop<UserEvent>, mut winit_app: impl WinitApp + 'static) -> ! {
|
||||
tracing::debug!("event_loop.run");
|
||||
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
|
||||
|
@ -215,7 +236,12 @@ fn run_and_exit(event_loop: EventLoop<UserEvent>, mut winit_app: impl WinitApp +
|
|||
..
|
||||
}) => 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 {
|
||||
|
@ -231,7 +257,7 @@ fn run_and_exit(event_loop: EventLoop<UserEvent>, mut winit_app: impl WinitApp +
|
|||
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);
|
||||
|
@ -279,6 +305,8 @@ fn center_window_pos(
|
|||
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
|
||||
|
@ -321,7 +349,7 @@ mod glow_integration {
|
|||
unsafe fn new(
|
||||
winit_window: winit::window::Window,
|
||||
native_options: &epi::NativeOptions,
|
||||
) -> Self {
|
||||
) -> Result<Self> {
|
||||
use glutin::prelude::*;
|
||||
use raw_window_handle::*;
|
||||
let hardware_acceleration = match native_options.hardware_acceleration {
|
||||
|
@ -351,8 +379,7 @@ mod glow_integration {
|
|||
#[cfg(target_os = "android")]
|
||||
let preference = glutin::display::DisplayApiPreference::Egl;
|
||||
|
||||
let gl_display = glutin::display::Display::new(raw_display_handle, preference)
|
||||
.expect("failed to create glutin display");
|
||||
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 {
|
||||
|
@ -383,64 +410,49 @@ mod glow_integration {
|
|||
// 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)
|
||||
.expect("failed to find even a single matching configuration")
|
||||
.find_configs(config_template.clone())?
|
||||
.next()
|
||||
.expect("failed to find a matching configuration for creating opengl context");
|
||||
.ok_or(crate::EframeError::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,
|
||||
std::num::NonZeroU32::new(width).unwrap(),
|
||||
std::num::NonZeroU32::new(height).unwrap(),
|
||||
);
|
||||
.build(raw_window_handle, width, height);
|
||||
// start creating the gl objects
|
||||
let gl_context = gl_display
|
||||
.create_context(&config, &context_attributes)
|
||||
.expect("failed to create opengl context");
|
||||
let gl_context = gl_display.create_context(&config, &context_attributes)?;
|
||||
|
||||
let gl_surface = gl_display
|
||||
.create_window_surface(&config, &surface_attributes)
|
||||
.expect("failed to create glutin window surface");
|
||||
let gl_context = gl_context
|
||||
.make_current(&gl_surface)
|
||||
.expect("failed to make gl context current");
|
||||
gl_surface
|
||||
.set_swap_interval(&gl_context, swap_interval)
|
||||
.expect("failed to set vsync swap interval");
|
||||
GlutinWindowContext {
|
||||
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;
|
||||
self.gl_surface.resize(
|
||||
&self.gl_context,
|
||||
physical_size
|
||||
.width
|
||||
.try_into()
|
||||
.expect("physical size must not be zero"),
|
||||
physical_size
|
||||
.height
|
||||
.try_into()
|
||||
.expect("physical size must not be zero"),
|
||||
);
|
||||
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)
|
||||
|
@ -484,25 +496,19 @@ mod glow_integration {
|
|||
fn create_glutin_windowed_context(
|
||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
storage: Option<&dyn epi::Storage>,
|
||||
title: &String,
|
||||
title: &str,
|
||||
native_options: &NativeOptions,
|
||||
) -> (GlutinWindowContext, glow::Context) {
|
||||
) -> Result<(GlutinWindowContext, glow::Context)> {
|
||||
crate::profile_function!();
|
||||
|
||||
let window_settings = epi_integration::load_window_settings(storage);
|
||||
|
||||
let window_builder = epi_integration::window_builder(native_options, &window_settings)
|
||||
.with_title(title)
|
||||
.with_transparent(native_options.transparent)
|
||||
// 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);
|
||||
let winit_window = window_builder
|
||||
.build(event_loop)
|
||||
.expect("failed to create winit window");
|
||||
let winit_window =
|
||||
epi_integration::window_builder(title, native_options, &window_settings)
|
||||
.build(event_loop)?;
|
||||
// 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) };
|
||||
unsafe { GlutinWindowContext::new(winit_window, native_options)? };
|
||||
let gl = unsafe {
|
||||
glow::Context::from_loader_function(|s| {
|
||||
let s = std::ffi::CString::new(s)
|
||||
|
@ -512,10 +518,10 @@ mod glow_integration {
|
|||
})
|
||||
};
|
||||
|
||||
(glutin_window_context, gl)
|
||||
Ok((glutin_window_context, gl))
|
||||
}
|
||||
|
||||
fn init_run_state(&mut self, event_loop: &EventLoopWindowTarget<UserEvent>) {
|
||||
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(
|
||||
|
@ -523,7 +529,7 @@ mod glow_integration {
|
|||
storage.as_deref(),
|
||||
&self.app_name,
|
||||
&self.native_options,
|
||||
);
|
||||
)?;
|
||||
let gl = Arc::new(gl);
|
||||
|
||||
let painter =
|
||||
|
@ -585,6 +591,8 @@ mod glow_integration {
|
|||
integration,
|
||||
app,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -726,11 +734,11 @@ mod glow_integration {
|
|||
&mut self,
|
||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
event: &winit::event::Event<'_, UserEvent>,
|
||||
) -> EventResult {
|
||||
match event {
|
||||
) -> 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
|
||||
}
|
||||
|
@ -793,7 +801,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);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
@ -832,7 +841,7 @@ mod glow_integration {
|
|||
}
|
||||
}
|
||||
_ => EventResult::Wait,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -840,7 +849,7 @@ 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 {
|
||||
|
@ -849,8 +858,8 @@ mod glow_integration {
|
|||
|
||||
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();
|
||||
|
||||
|
@ -923,38 +932,40 @@ mod wgpu_integration {
|
|||
fn create_window(
|
||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
storage: Option<&dyn epi::Storage>,
|
||||
title: &String,
|
||||
title: &str,
|
||||
native_options: &NativeOptions,
|
||||
) -> winit::window::Window {
|
||||
) -> Result<winit::window::Window> {
|
||||
let window_settings = epi_integration::load_window_settings(storage);
|
||||
epi_integration::window_builder(native_options, &window_settings)
|
||||
.with_title(title)
|
||||
// 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)
|
||||
.build(event_loop)
|
||||
.unwrap()
|
||||
Ok(
|
||||
epi_integration::window_builder(title, native_options, &window_settings)
|
||||
.build(event_loop)?,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
fn set_window(&mut self, window: winit::window::Window) {
|
||||
fn set_window(
|
||||
&mut self,
|
||||
window: winit::window::Window,
|
||||
) -> std::result::Result<(), wgpu::RequestDeviceError> {
|
||||
self.window = Some(window);
|
||||
if let Some(running) = &mut self.running {
|
||||
unsafe {
|
||||
running.painter.set_window(self.window.as_ref()).unwrap();
|
||||
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<(), wgpu::RequestDeviceError> {
|
||||
self.window = None;
|
||||
if let Some(running) = &mut self.running {
|
||||
unsafe {
|
||||
running.painter.set_window(None).unwrap();
|
||||
running.painter.set_window(None)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_run_state(
|
||||
|
@ -962,7 +973,7 @@ mod wgpu_integration {
|
|||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
storage: Option<Box<dyn epi::Storage>>,
|
||||
window: winit::window::Window,
|
||||
) {
|
||||
) -> std::result::Result<(), wgpu::RequestDeviceError> {
|
||||
#[allow(unsafe_code, unused_mut, unused_unsafe)]
|
||||
let painter = unsafe {
|
||||
let mut painter = egui_wgpu::winit::Painter::new(
|
||||
|
@ -970,7 +981,7 @@ mod wgpu_integration {
|
|||
self.native_options.multisampling.max(1) as _,
|
||||
self.native_options.depth_buffer,
|
||||
);
|
||||
painter.set_window(Some(&window)).unwrap();
|
||||
painter.set_window(Some(&window))?;
|
||||
painter
|
||||
};
|
||||
|
||||
|
@ -1028,6 +1039,8 @@ mod wgpu_integration {
|
|||
app,
|
||||
});
|
||||
self.window = Some(window);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1135,8 +1148,8 @@ mod wgpu_integration {
|
|||
&mut self,
|
||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
event: &winit::event::Event<'_, UserEvent>,
|
||||
) -> EventResult {
|
||||
match event {
|
||||
) -> Result<EventResult> {
|
||||
Ok(match event {
|
||||
winit::event::Event::Resumed => {
|
||||
if let Some(running) = &self.running {
|
||||
if self.window.is_none() {
|
||||
|
@ -1145,8 +1158,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);
|
||||
|
@ -1155,14 +1168,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
|
||||
}
|
||||
|
||||
|
@ -1212,7 +1225,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);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
@ -1250,7 +1264,7 @@ mod wgpu_integration {
|
|||
}
|
||||
}
|
||||
_ => EventResult::Wait,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1258,7 +1272,7 @@ 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 {
|
||||
|
@ -1267,8 +1281,8 @@ mod wgpu_integration {
|
|||
|
||||
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();
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ All notable changes to the `egui-wgpu` integration will be noted in this file.
|
|||
|
||||
|
||||
## Unreleased
|
||||
* Fix panic if we can't find a device ([#2428](https://github.com/emilk/egui/pull/2428))
|
||||
* Return `Err` instead of panic if we can't find a device ([#2428](https://github.com/emilk/egui/pull/2428)).
|
||||
|
||||
|
||||
## 0.20.0 - 2022-12-08 - web support
|
||||
|
|
|
@ -46,12 +46,13 @@ impl WindowSettings {
|
|||
&self,
|
||||
mut window: winit::window::WindowBuilder,
|
||||
) -> winit::window::WindowBuilder {
|
||||
if !cfg!(target_os = "windows") {
|
||||
// If the app last ran on two monitors and only one is now connected, then
|
||||
// the given position is invalid.
|
||||
// If this happens on Mac, the window is clamped into valid area.
|
||||
// If this happens on Windows, the window is hidden and very difficult to find.
|
||||
// So we don't restore window positions on Windows.
|
||||
let try_restore_position = !cfg!(target_os = "windows");
|
||||
if try_restore_position {
|
||||
if let Some(pos) = self.position {
|
||||
window = window.with_position(winit::dpi::PhysicalPosition {
|
||||
x: pos.x as f64,
|
||||
|
@ -74,4 +75,12 @@ impl WindowSettings {
|
|||
window
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clamp_to_sane_values(&mut self) {
|
||||
if let Some(size) = &mut self.inner_size_points {
|
||||
// Prevent ridiculously small windows
|
||||
let min_size = egui::Vec2::splat(64.0);
|
||||
*size = size.max(min_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,18 @@
|
|||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
// When compiling natively:
|
||||
fn main() {
|
||||
fn main() -> Result<(), eframe::EframeError> {
|
||||
{
|
||||
// Silence wgpu log spam (https://github.com/gfx-rs/wgpu/issues/3206)
|
||||
let mut rust_log = std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_owned());
|
||||
for loud_crate in ["naga", "wgpu_core", "wgpu_hal"] {
|
||||
if !rust_log.contains(&format!("{loud_crate}=")) {
|
||||
rust_log += &format!(",{loud_crate}=warn");
|
||||
}
|
||||
}
|
||||
std::env::set_var("RUST_LOG", rust_log);
|
||||
}
|
||||
|
||||
// Log to stdout (if you run with `RUST_LOG=debug`).
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
|
@ -21,5 +32,5 @@ fn main() {
|
|||
"egui demo app",
|
||||
options,
|
||||
Box::new(|cc| Box::new(egui_demo_app::WrapApp::new(cc))),
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
@ -223,9 +223,9 @@ fn create_display(
|
|||
height: 600.0,
|
||||
})
|
||||
.with_title("egui_glow example")
|
||||
.with_visible(false)
|
||||
.with_visible(false) // Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
|
||||
.build(event_loop)
|
||||
.unwrap(); // Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
|
||||
.unwrap();
|
||||
|
||||
// a lot of the code below has been lifted from glutin example in their repo.
|
||||
let glutin_window_context = unsafe { GlutinWindowContext::new(winit_window) };
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use eframe::egui;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), eframe::EframeError> {
|
||||
let options = eframe::NativeOptions {
|
||||
initial_window_size: Some(egui::vec2(320.0, 240.0)),
|
||||
..Default::default()
|
||||
|
@ -11,7 +11,7 @@ fn main() {
|
|||
"Confirm exit",
|
||||
options,
|
||||
Box::new(|_cc| Box::new(MyApp::default())),
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
|
@ -6,7 +6,7 @@ use eframe::egui;
|
|||
use egui::mutex::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), eframe::EframeError> {
|
||||
let options = eframe::NativeOptions {
|
||||
initial_window_size: Some(egui::vec2(350.0, 380.0)),
|
||||
multisampling: 8,
|
||||
|
@ -17,7 +17,7 @@ fn main() {
|
|||
"Custom 3D painting in eframe using glow",
|
||||
options,
|
||||
Box::new(|cc| Box::new(MyApp::new(cc))),
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
struct MyApp {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
use eframe::egui;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() {
|
||||
fn main() -> Result<(), eframe::EframeError> {
|
||||
let options = eframe::NativeOptions {
|
||||
initial_window_size: Some(egui::vec2(550.0, 610.0)),
|
||||
multisampling: 8,
|
||||
|
@ -16,7 +16,7 @@ fn main() {
|
|||
"Custom 3D painting in eframe!",
|
||||
options,
|
||||
Box::new(|cc| Box::new(MyApp::new(cc))),
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
pub struct MyApp {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use eframe::egui;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), eframe::EframeError> {
|
||||
let options = eframe::NativeOptions {
|
||||
initial_window_size: Some(egui::vec2(320.0, 240.0)),
|
||||
..Default::default()
|
||||
|
@ -11,7 +11,7 @@ fn main() {
|
|||
"egui example: custom font",
|
||||
options,
|
||||
Box::new(|cc| Box::new(MyApp::new(cc))),
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
fn setup_custom_fonts(ctx: &egui::Context) {
|
||||
|
|
|
@ -58,14 +58,14 @@ impl eframe::App for MyApp {
|
|||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), eframe::EframeError> {
|
||||
let options = eframe::NativeOptions::default();
|
||||
|
||||
eframe::run_native(
|
||||
"egui example: global font style",
|
||||
options,
|
||||
Box::new(|cc| Box::new(MyApp::new(cc))),
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
pub const LOREM_IPSUM: &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use eframe::egui;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), eframe::EframeError> {
|
||||
let options = eframe::NativeOptions {
|
||||
// Hide the OS-specific "chrome" around the window:
|
||||
decorated: false,
|
||||
|
@ -18,7 +18,7 @@ fn main() {
|
|||
"Custom window frame", // unused title
|
||||
options,
|
||||
Box::new(|_cc| Box::new(MyApp::default())),
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
|
@ -4,13 +4,13 @@ use eframe::egui;
|
|||
use egui_extras::RetainedImage;
|
||||
use poll_promise::Promise;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), eframe::EframeError> {
|
||||
let options = eframe::NativeOptions::default();
|
||||
eframe::run_native(
|
||||
"Download and show an image with eframe/egui",
|
||||
options,
|
||||
Box::new(|_cc| Box::new(MyApp::default())),
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use eframe::egui;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), eframe::EframeError> {
|
||||
let options = eframe::NativeOptions {
|
||||
drag_and_drop_support: true,
|
||||
initial_window_size: Some(egui::vec2(320.0, 240.0)),
|
||||
|
@ -12,7 +12,7 @@ fn main() {
|
|||
"Native file dialogs and drag-and-drop files",
|
||||
options,
|
||||
Box::new(|_cc| Box::new(MyApp::default())),
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use eframe::egui;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), eframe::EframeError> {
|
||||
// Log to stdout (if you run with `RUST_LOG=debug`).
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
|
@ -14,7 +14,7 @@ fn main() {
|
|||
"My egui App",
|
||||
options,
|
||||
Box::new(|_cc| Box::new(MyApp::default())),
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
struct MyApp {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use eframe::egui;
|
||||
use egui::*;
|
||||
fn main() {
|
||||
fn main() -> Result<(), eframe::EframeError> {
|
||||
// Log to stdout (if you run with `RUST_LOG=debug`).
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
|
@ -11,7 +11,7 @@ fn main() {
|
|||
"Keyboard events",
|
||||
options,
|
||||
Box::new(|_cc| Box::new(Content::default())),
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use eframe::egui;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), eframe::EframeError> {
|
||||
start_puffin_server(); // NOTE: you may only want to call this if the users specifies some flag or clicks a button!
|
||||
|
||||
let options = eframe::NativeOptions::default();
|
||||
|
@ -10,7 +10,7 @@ fn main() {
|
|||
"My egui App",
|
||||
options,
|
||||
Box::new(|_cc| Box::new(MyApp::default())),
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
use eframe::egui;
|
||||
use egui_extras::RetainedImage;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), eframe::EframeError> {
|
||||
let options = eframe::NativeOptions {
|
||||
initial_window_size: Some(egui::vec2(300.0, 900.0)),
|
||||
..Default::default()
|
||||
|
@ -13,7 +13,7 @@ fn main() {
|
|||
"Show an image with eframe/egui",
|
||||
options,
|
||||
Box::new(|_cc| Box::new(MyApp::default())),
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
struct MyApp {
|
||||
|
|
|
@ -6,13 +6,13 @@ use eframe::{
|
|||
};
|
||||
use itertools::Itertools as _;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), eframe::EframeError> {
|
||||
let options = eframe::NativeOptions::default();
|
||||
eframe::run_native(
|
||||
"Take screenshots and display with eframe/egui",
|
||||
options,
|
||||
Box::new(|_cc| Box::new(MyApp::default())),
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use eframe::egui;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), eframe::EframeError> {
|
||||
if cfg!(target_os = "macos") {
|
||||
eprintln!("WARNING: this example does not work on Mac! See https://github.com/emilk/egui/issues/1918");
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ fn main() {
|
|||
"First Window",
|
||||
options.clone(),
|
||||
Box::new(|_cc| Box::new(MyApp::default())),
|
||||
);
|
||||
)?;
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||
|
||||
|
@ -27,7 +27,7 @@ fn main() {
|
|||
"Second Window",
|
||||
options.clone(),
|
||||
Box::new(|_cc| Box::new(MyApp::default())),
|
||||
);
|
||||
)?;
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||
|
||||
|
@ -36,7 +36,7 @@ fn main() {
|
|||
"Third Window",
|
||||
options,
|
||||
Box::new(|_cc| Box::new(MyApp::default())),
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
use eframe::egui;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), eframe::EframeError> {
|
||||
let options = eframe::NativeOptions {
|
||||
initial_window_size: Some(egui::vec2(1000.0, 700.0)),
|
||||
..Default::default()
|
||||
|
@ -15,7 +15,7 @@ fn main() {
|
|||
"svg example",
|
||||
options,
|
||||
Box::new(|_cc| Box::new(MyApp::default())),
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
struct MyApp {
|
||||
|
|
Loading…
Reference in a new issue