mirror of
https://github.com/emilk/egui.git
synced 2026-08-31 05:40:03 -04:00
Merge branch 'master' into master
This commit is contained in:
@@ -6,6 +6,56 @@ NOTE: [`egui-winit`](../egui-winit/CHANGELOG.md), [`egui_glium`](../egui_glium/C
|
||||
This file is updated upon each release.
|
||||
Changes since the last release can be found by running the `scripts/generate_changelog.py` script.
|
||||
|
||||
|
||||
## 0.24.0 - 2023-11-23
|
||||
* Multiple viewports/windows [#3172](https://github.com/emilk/egui/pull/3172) (thanks [@konkitoman](https://github.com/konkitoman)!)
|
||||
* Replace `eframe::Frame` commands and `WindowInfo` with egui [#3564](https://github.com/emilk/egui/pull/3564)
|
||||
* Use `egui::ViewportBuilder` in `eframe::NativeOptions` [#3572](https://github.com/emilk/egui/pull/3572)
|
||||
* Remove warm-starting [#3574](https://github.com/emilk/egui/pull/3574)
|
||||
* Fix copy and cut on Safari [#3513](https://github.com/emilk/egui/pull/3513) (thanks [@lunixbochs](https://github.com/lunixbochs)!)
|
||||
* Update puffin to 0.18 [#3600](https://github.com/emilk/egui/pull/3600)
|
||||
* Update MSRV to Rust 1.72 [#3595](https://github.com/emilk/egui/pull/3595)
|
||||
|
||||
### Breaking changes:
|
||||
Most settings in `NativeOptions` have been moved to `NativeOptions::viewport`, which uses the new `egui::ViewportBuilder`:
|
||||
|
||||
```diff
|
||||
let native_options = eframe::nativeOptions {
|
||||
- initial_window_size: Some(egui::vec2(320.0, 240.0)),
|
||||
- drag_and_drop_support: true,
|
||||
+ viewport: egui::ViewportBuilder::default()
|
||||
+ .with_inner_size([320.0, 240.0])
|
||||
+ .with_drag_and_drop(true),
|
||||
..Default::default()
|
||||
};
|
||||
```
|
||||
|
||||
`NativeOptions::fullsize_content` has been replaced with four settings: `ViewportBuilder::with_fullsize_content_view`, `with_title_shown`, `with_titlebar_shown`, `with_titlebar_buttons_shown`
|
||||
|
||||
`frame.info().window_info` is gone, replaced with `ctx.input(|i| i.viewport())`.
|
||||
|
||||
`frame.info().native_pixels_per_point` is replaced with `ctx.input(|i| i.raw.native_pixels_per_point)`.
|
||||
|
||||
Most commands in `eframe::Frame` has been replaced with `egui::ViewportCommand`, so So `frame.close()` becomes `ctx.send_viewport_cmd(ViewportCommand::Close)`, etc.
|
||||
|
||||
`App::on_close_event` has been replaced with `ctx.input(|i| i.viewport().close_requested())` and `ctx.send_viewport_cmd(ViewportCommand::CancelClose)`.
|
||||
|
||||
`eframe::IconData` is now `egui::IconData`.
|
||||
|
||||
`eframe::IconData::try_from_png_bytes` is now `eframe::icon_data::from_png_bytes`.
|
||||
|
||||
`App::post_rendering` is gone. Screenshots are taken with `ctx.send_viewport_cmd(ViewportCommand::Screenshots)` and are returned in `egui::Event` which you can check with:
|
||||
``` rust
|
||||
ui.input(|i| {
|
||||
for event in &i.raw.events {
|
||||
if let egui::Event::Screenshot { viewport_id, image } = event {
|
||||
// handle it here
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
## 0.23.0 - 2023-09-27
|
||||
* Update MSRV to Rust 1.70.0 [#3310](https://github.com/emilk/egui/pull/3310)
|
||||
* Update to puffin 0.16 [#3144](https://github.com/emilk/egui/pull/3144)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
[package]
|
||||
name = "eframe"
|
||||
version = "0.23.0"
|
||||
version.workspace = true
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "egui framework - write GUI apps that compiles to web and/or natively"
|
||||
edition = "2021"
|
||||
rust-version = "1.72"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
homepage = "https://github.com/emilk/egui/tree/master/crates/eframe"
|
||||
license = "MIT OR Apache-2.0"
|
||||
license.workspace = true
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/emilk/egui/tree/master/crates/eframe"
|
||||
categories = ["gui", "game-development"]
|
||||
@@ -98,7 +98,7 @@ x11 = ["egui-winit/x11"]
|
||||
__screenshot = []
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.23.0", path = "../egui", default-features = false, features = [
|
||||
egui = { version = "0.24.0", path = "../egui", default-features = false, features = [
|
||||
"bytemuck",
|
||||
"log",
|
||||
] }
|
||||
@@ -111,7 +111,7 @@ thiserror.workspace = true
|
||||
## Enable this when generating docs.
|
||||
document-features = { version = "0.2", optional = true }
|
||||
|
||||
egui_glow = { version = "0.23.0", path = "../egui_glow", optional = true, default-features = false }
|
||||
egui_glow = { version = "0.24.0", path = "../egui_glow", optional = true, default-features = false }
|
||||
glow = { version = "0.12", optional = true }
|
||||
ron = { version = "0.8", optional = true, features = ["integer128"] }
|
||||
serde = { version = "1", optional = true, features = ["derive"] }
|
||||
@@ -119,7 +119,7 @@ serde = { version = "1", optional = true, features = ["derive"] }
|
||||
# -------------------------------------------
|
||||
# native:
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
egui-winit = { version = "0.23.0", path = "../egui-winit", default-features = false, features = [
|
||||
egui-winit = { version = "0.24.0", path = "../egui-winit", default-features = false, features = [
|
||||
"clipboard",
|
||||
"links",
|
||||
] }
|
||||
@@ -131,7 +131,7 @@ winit = { version = "0.29", default-features = false, features = ["rwh_05"] }
|
||||
|
||||
# optional native:
|
||||
directories-next = { version = "2", optional = true }
|
||||
egui-wgpu = { version = "0.23.0", path = "../egui-wgpu", optional = true, features = [
|
||||
egui-wgpu = { version = "0.24.0", path = "../egui-wgpu", optional = true, features = [
|
||||
"winit",
|
||||
] } # if wgpu is used, use it with winit
|
||||
pollster = { version = "0.3", optional = true } # needed for wgpu
|
||||
@@ -205,7 +205,7 @@ web-sys = { version = "0.3.58", features = [
|
||||
] }
|
||||
|
||||
# optional web:
|
||||
egui-wgpu = { version = "0.23.0", path = "../egui-wgpu", optional = true } # if wgpu is used, use it without (!) winit
|
||||
egui-wgpu = { version = "0.24.0", path = "../egui-wgpu", optional = true } # if wgpu is used, use it without (!) winit
|
||||
raw-window-handle = { workspace = true, optional = true }
|
||||
tts = { version = "0.25", optional = true, default-features = false }
|
||||
wgpu = { workspace = true, optional = true }
|
||||
|
||||
@@ -149,25 +149,10 @@ pub trait App {
|
||||
/// On native the path is picked using [`crate::storage_dir`].
|
||||
fn save(&mut self, _storage: &mut dyn Storage) {}
|
||||
|
||||
/// Called when the user attempts to close the desktop window and/or quit the application.
|
||||
///
|
||||
/// By returning `false` the closing will be aborted. To continue the closing return `true`.
|
||||
///
|
||||
/// A scenario where this method will be run is after pressing the close button on a native
|
||||
/// window, which allows you to ask the user whether they want to do something before exiting.
|
||||
/// See the example at <https://github.com/emilk/egui/blob/master/examples/confirm_exit/> for practical usage.
|
||||
///
|
||||
/// It will _not_ be called on the web or when the window is forcefully closed.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[doc(alias = "exit")]
|
||||
#[doc(alias = "quit")]
|
||||
fn on_close_event(&mut self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Called once on shutdown, after [`Self::save`].
|
||||
///
|
||||
/// If you need to abort an exit use [`Self::on_close_event`].
|
||||
/// If you need to abort an exit check `ctx.input(|i| i.viewport().close_requested())`
|
||||
/// and respond with [`egui::ViewportCommand::CancelClose`].
|
||||
///
|
||||
/// To get a [`glow`] context you need to compile with the `glow` feature flag,
|
||||
/// and run eframe with the glow backend.
|
||||
@@ -248,6 +233,9 @@ pub struct NativeOptions {
|
||||
/// Controls the native window of the root viewport.
|
||||
///
|
||||
/// This is where you set things like window title and size.
|
||||
///
|
||||
/// If you don't set an icon, a default egui icon will be used.
|
||||
/// 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.
|
||||
@@ -379,13 +367,7 @@ impl Clone for NativeOptions {
|
||||
impl Default for NativeOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
viewport: egui::ViewportBuilder {
|
||||
icon: Some(std::sync::Arc::new(
|
||||
crate::icon_data::from_png_bytes(&include_bytes!("../data/icon.png")[..])
|
||||
.unwrap(),
|
||||
)),
|
||||
..Default::default()
|
||||
},
|
||||
viewport: Default::default(),
|
||||
|
||||
vsync: true,
|
||||
multisampling: 0,
|
||||
|
||||
@@ -13,7 +13,13 @@ pub struct AppTitleIconSetter {
|
||||
}
|
||||
|
||||
impl AppTitleIconSetter {
|
||||
pub fn new(title: String, icon_data: Option<Arc<IconData>>) -> Self {
|
||||
pub fn new(title: String, mut icon_data: Option<Arc<IconData>>) -> Self {
|
||||
if let Some(icon) = &icon_data {
|
||||
if **icon == IconData::default() {
|
||||
icon_data = None;
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
title,
|
||||
icon_data,
|
||||
|
||||
@@ -12,6 +12,7 @@ use egui_winit::{EventResponse, WindowSettings};
|
||||
use crate::{epi, Theme};
|
||||
|
||||
pub fn viewport_builder<E>(
|
||||
egui_zoom_factor: f32,
|
||||
event_loop: &EventLoopWindowTarget<E>,
|
||||
native_options: &mut epi::NativeOptions,
|
||||
window_settings: Option<WindowSettings>,
|
||||
@@ -26,8 +27,9 @@ pub fn viewport_builder<E>(
|
||||
let inner_size_points = if let Some(mut window_settings) = window_settings {
|
||||
// Restore pos/size from previous session
|
||||
|
||||
window_settings.clamp_size_to_sane_values(largest_monitor_point_size(event_loop));
|
||||
window_settings.clamp_position_to_monitors(event_loop);
|
||||
window_settings
|
||||
.clamp_size_to_sane_values(largest_monitor_point_size(egui_zoom_factor, event_loop));
|
||||
window_settings.clamp_position_to_monitors(egui_zoom_factor, event_loop);
|
||||
|
||||
viewport_builder = window_settings.initialize_viewport_builder(viewport_builder);
|
||||
window_settings.inner_size_points()
|
||||
@@ -37,8 +39,8 @@ pub fn viewport_builder<E>(
|
||||
}
|
||||
|
||||
if let Some(initial_window_size) = viewport_builder.inner_size {
|
||||
let initial_window_size =
|
||||
initial_window_size.at_most(largest_monitor_point_size(event_loop));
|
||||
let initial_window_size = initial_window_size
|
||||
.at_most(largest_monitor_point_size(egui_zoom_factor, event_loop));
|
||||
viewport_builder = viewport_builder.with_inner_size(initial_window_size);
|
||||
}
|
||||
|
||||
@@ -49,9 +51,11 @@ pub fn viewport_builder<E>(
|
||||
if native_options.centered {
|
||||
crate::profile_scope!("center");
|
||||
if let Some(monitor) = event_loop.available_monitors().next() {
|
||||
let monitor_size = monitor.size().to_logical::<f32>(monitor.scale_factor());
|
||||
let monitor_size = monitor
|
||||
.size()
|
||||
.to_logical::<f32>(egui_zoom_factor as f64 * monitor.scale_factor());
|
||||
let inner_size = inner_size_points.unwrap_or(egui::Vec2 { x: 800.0, y: 600.0 });
|
||||
if monitor_size.width > 0.0 && monitor_size.height > 0.0 {
|
||||
if 0.0 < monitor_size.width && 0.0 < monitor_size.height {
|
||||
let x = (monitor_size.width - inner_size.x) / 2.0;
|
||||
let y = (monitor_size.height - inner_size.y) / 2.0;
|
||||
viewport_builder = viewport_builder.with_position([x, y]);
|
||||
@@ -76,7 +80,10 @@ pub fn apply_window_settings(
|
||||
}
|
||||
}
|
||||
|
||||
fn largest_monitor_point_size<E>(event_loop: &EventLoopWindowTarget<E>) -> egui::Vec2 {
|
||||
fn largest_monitor_point_size<E>(
|
||||
egui_zoom_factor: f32,
|
||||
event_loop: &EventLoopWindowTarget<E>,
|
||||
) -> egui::Vec2 {
|
||||
crate::profile_function!();
|
||||
|
||||
let mut max_size = egui::Vec2::ZERO;
|
||||
@@ -87,7 +94,9 @@ fn largest_monitor_point_size<E>(event_loop: &EventLoopWindowTarget<E>) -> egui:
|
||||
};
|
||||
|
||||
for monitor in available_monitors {
|
||||
let size = monitor.size().to_logical::<f32>(monitor.scale_factor());
|
||||
let size = monitor
|
||||
.size()
|
||||
.to_logical::<f32>(egui_zoom_factor as f64 * monitor.scale_factor());
|
||||
let size = egui::vec2(size.width, size.height);
|
||||
max_size = max_size.max(size);
|
||||
}
|
||||
@@ -137,21 +146,15 @@ pub struct EpiIntegration {
|
||||
impl EpiIntegration {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
egui_ctx: egui::Context,
|
||||
window: &winit::window::Window,
|
||||
system_theme: Option<Theme>,
|
||||
app_name: &str,
|
||||
native_options: &crate::NativeOptions,
|
||||
storage: Option<Box<dyn epi::Storage>>,
|
||||
is_desktop: bool,
|
||||
#[cfg(feature = "glow")] gl: Option<std::rc::Rc<glow::Context>>,
|
||||
#[cfg(feature = "wgpu")] wgpu_render_state: Option<egui_wgpu::RenderState>,
|
||||
) -> Self {
|
||||
let egui_ctx = egui::Context::default();
|
||||
egui_ctx.set_embed_viewports(!is_desktop);
|
||||
|
||||
let memory = load_egui_memory(storage.as_deref()).unwrap_or_default();
|
||||
egui_ctx.memory_mut(|mem| *mem = memory);
|
||||
|
||||
let frame = epi::Frame {
|
||||
info: epi::IntegrationInfo {
|
||||
system_theme,
|
||||
@@ -166,13 +169,19 @@ impl EpiIntegration {
|
||||
raw_window_handle: window.raw_window_handle(),
|
||||
};
|
||||
|
||||
let icon = native_options
|
||||
.viewport
|
||||
.icon
|
||||
.clone()
|
||||
.unwrap_or_else(|| std::sync::Arc::new(load_default_egui_icon()));
|
||||
|
||||
let app_icon_setter = super::app_icon::AppTitleIconSetter::new(
|
||||
native_options
|
||||
.viewport
|
||||
.title
|
||||
.clone()
|
||||
.unwrap_or_else(|| app_name.to_owned()),
|
||||
native_options.viewport.icon.clone(),
|
||||
Some(icon),
|
||||
);
|
||||
|
||||
Self {
|
||||
@@ -220,22 +229,14 @@ impl EpiIntegration {
|
||||
|
||||
pub fn on_window_event(
|
||||
&mut self,
|
||||
app: &mut dyn epi::App,
|
||||
event: &winit::event::WindowEvent,
|
||||
egui_winit: &mut egui_winit::State,
|
||||
viewport_id: ViewportId,
|
||||
) -> EventResponse {
|
||||
crate::profile_function!(egui_winit::short_window_event_description(event));
|
||||
|
||||
use winit::event::{ElementState, MouseButton, WindowEvent};
|
||||
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
if viewport_id == ViewportId::ROOT {
|
||||
self.close = app.on_close_event();
|
||||
log::debug!("App::on_close_event returned {}", self.close);
|
||||
}
|
||||
}
|
||||
WindowEvent::Destroyed => {
|
||||
log::debug!("Received WindowEvent::Destroyed");
|
||||
self.close = true;
|
||||
@@ -245,9 +246,6 @@ impl EpiIntegration {
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
} => self.can_drag_window = true,
|
||||
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
|
||||
egui_winit.egui_input_mut().native_pixels_per_point = Some(*scale_factor as _);
|
||||
}
|
||||
WindowEvent::ThemeChanged(winit_theme) if self.follow_system_theme => {
|
||||
let theme = theme_from_winit_theme(*winit_theme);
|
||||
self.frame.info.system_theme = Some(theme);
|
||||
@@ -275,23 +273,32 @@ impl EpiIntegration {
|
||||
) -> egui::FullOutput {
|
||||
raw_input.time = Some(self.beginning.elapsed().as_secs_f64());
|
||||
|
||||
let close_requested = raw_input.viewport().close_requested();
|
||||
|
||||
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
|
||||
if let Some(viewport_ui_cb) = viewport_ui_cb {
|
||||
// Child viewport
|
||||
crate::profile_scope!("viewport_callback");
|
||||
viewport_ui_cb(egui_ctx);
|
||||
} else {
|
||||
// Root viewport
|
||||
if egui_ctx.input(|i| i.viewport().close_requested()) {
|
||||
self.close = app.on_close_event();
|
||||
log::debug!("App::on_close_event returned {}", self.close);
|
||||
}
|
||||
|
||||
crate::profile_scope!("App::update");
|
||||
app.update(egui_ctx, &mut self.frame);
|
||||
}
|
||||
});
|
||||
|
||||
let is_root_viewport = viewport_ui_cb.is_none();
|
||||
if is_root_viewport && close_requested {
|
||||
let canceled = full_output.viewport_output[&ViewportId::ROOT]
|
||||
.commands
|
||||
.contains(&egui::ViewportCommand::CancelClose);
|
||||
if canceled {
|
||||
log::debug!("Closing of root viewport canceled with ViewportCommand::CancelClose");
|
||||
} else {
|
||||
log::debug!("Closing root viewport (ViewportCommand::CancelClose was not sent)");
|
||||
self.close = true;
|
||||
}
|
||||
}
|
||||
|
||||
self.pending_full_output.append(full_output);
|
||||
std::mem::take(&mut self.pending_full_output)
|
||||
}
|
||||
@@ -309,16 +316,6 @@ impl EpiIntegration {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_platform_output(
|
||||
&mut self,
|
||||
window: &winit::window::Window,
|
||||
viewport_id: ViewportId,
|
||||
platform_output: egui::PlatformOutput,
|
||||
egui_winit: &mut egui_winit::State,
|
||||
) {
|
||||
egui_winit.handle_platform_output(window, viewport_id, &self.egui_ctx, platform_output);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Persistence stuff:
|
||||
|
||||
@@ -346,7 +343,7 @@ impl EpiIntegration {
|
||||
epi::set_value(
|
||||
storage,
|
||||
STORAGE_WINDOW_KEY,
|
||||
&WindowSettings::from_display(window),
|
||||
&WindowSettings::from_window(self.egui_ctx.zoom_factor(), window),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -366,6 +363,11 @@ impl EpiIntegration {
|
||||
}
|
||||
}
|
||||
|
||||
fn load_default_egui_icon() -> egui::IconData {
|
||||
crate::profile_function!();
|
||||
crate::icon_data::from_png_bytes(&include_bytes!("../../data/icon.png")[..]).unwrap()
|
||||
}
|
||||
|
||||
#[cfg(feature = "persistence")]
|
||||
const STORAGE_EGUI_MEMORY_KEY: &str = "egui";
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ use egui_winit::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
native::epi_integration::EpiIntegration, App, AppCreator, CreationContext, NativeOptions,
|
||||
Result, Storage,
|
||||
native::{epi_integration::EpiIntegration, winit_integration::create_egui_context},
|
||||
App, AppCreator, CreationContext, NativeOptions, Result, Storage,
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -87,6 +87,8 @@ struct GlowWinitRunning {
|
||||
/// The setup is divided between the `new` fn and `on_resume` fn. we can just assume that `on_resume` is a continuation of
|
||||
/// `new` fn on all platforms. only on android, do we get multiple resumed events because app can be suspended.
|
||||
struct GlutinWindowContext {
|
||||
egui_ctx: egui::Context,
|
||||
|
||||
swap_interval: glutin::surface::SwapInterval,
|
||||
gl_config: glutin::config::Config,
|
||||
|
||||
@@ -139,6 +141,7 @@ impl GlowWinitApp {
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
fn create_glutin_windowed_context(
|
||||
egui_ctx: &egui::Context,
|
||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
storage: Option<&dyn Storage>,
|
||||
native_options: &mut NativeOptions,
|
||||
@@ -147,11 +150,16 @@ impl GlowWinitApp {
|
||||
|
||||
let window_settings = epi_integration::load_window_settings(storage);
|
||||
|
||||
let winit_window_builder =
|
||||
epi_integration::viewport_builder(event_loop, native_options, window_settings);
|
||||
let winit_window_builder = epi_integration::viewport_builder(
|
||||
egui_ctx.zoom_factor(),
|
||||
event_loop,
|
||||
native_options,
|
||||
window_settings,
|
||||
);
|
||||
|
||||
let mut glutin_window_context =
|
||||
unsafe { GlutinWindowContext::new(winit_window_builder, native_options, event_loop)? };
|
||||
let mut glutin_window_context = unsafe {
|
||||
GlutinWindowContext::new(egui_ctx, winit_window_builder, native_options, event_loop)?
|
||||
};
|
||||
|
||||
// Creates the window - must come before we create our glow context
|
||||
glutin_window_context.on_resume(event_loop)?;
|
||||
@@ -191,7 +199,10 @@ impl GlowWinitApp {
|
||||
.unwrap_or(&self.app_name),
|
||||
);
|
||||
|
||||
let egui_ctx = create_egui_context(storage.as_deref());
|
||||
|
||||
let (mut glutin, painter) = Self::create_glutin_windowed_context(
|
||||
&egui_ctx,
|
||||
event_loop,
|
||||
storage.as_deref(),
|
||||
&mut self.native_options,
|
||||
@@ -210,12 +221,12 @@ impl GlowWinitApp {
|
||||
winit_integration::system_theme(&glutin.window(ViewportId::ROOT), &self.native_options);
|
||||
|
||||
let integration = EpiIntegration::new(
|
||||
egui_ctx,
|
||||
&glutin.window(ViewportId::ROOT),
|
||||
system_theme,
|
||||
&self.app_name,
|
||||
&self.native_options,
|
||||
storage,
|
||||
winit_integration::IS_DESKTOP,
|
||||
Some(gl.clone()),
|
||||
#[cfg(feature = "wgpu")]
|
||||
None,
|
||||
@@ -498,7 +509,7 @@ impl GlowWinitRunning {
|
||||
let window = viewport.window.as_ref().unwrap();
|
||||
|
||||
let egui_winit = viewport.egui_winit.as_mut().unwrap();
|
||||
let mut raw_input = egui_winit.take_egui_input(window, viewport.ids);
|
||||
let mut raw_input = egui_winit.take_egui_input(window);
|
||||
let viewport_ui_cb = viewport.viewport_ui_cb.clone();
|
||||
|
||||
self.integration.pre_update();
|
||||
@@ -549,12 +560,13 @@ impl GlowWinitRunning {
|
||||
} = &mut *glutin;
|
||||
|
||||
let viewport = viewports.get_mut(&viewport_id).unwrap();
|
||||
viewport.info.events.clear(); // they should have been processed
|
||||
let window = viewport.window.as_ref().unwrap();
|
||||
let gl_surface = viewport.gl_surface.as_ref().unwrap();
|
||||
let egui_winit = viewport.egui_winit.as_mut().unwrap();
|
||||
|
||||
integration.post_update();
|
||||
integration.handle_platform_output(window, viewport_id, platform_output, egui_winit);
|
||||
egui_winit.handle_platform_output(window, &integration.egui_ctx, platform_output);
|
||||
|
||||
let clipped_primitives = integration.egui_ctx.tessellate(shapes, pixels_per_point);
|
||||
|
||||
@@ -632,7 +644,7 @@ impl GlowWinitRunning {
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
}
|
||||
|
||||
glutin.handle_viewport_output(viewport_output);
|
||||
glutin.handle_viewport_output(&integration.egui_ctx, viewport_output);
|
||||
|
||||
if integration.should_close() {
|
||||
EventResult::Exit
|
||||
@@ -728,12 +740,9 @@ impl GlowWinitRunning {
|
||||
};
|
||||
if let Some(viewport_id) = viewport_id {
|
||||
if let Some(viewport) = glutin.viewports.get_mut(&viewport_id) {
|
||||
event_response = self.integration.on_window_event(
|
||||
self.app.as_mut(),
|
||||
event,
|
||||
viewport.egui_winit.as_mut().unwrap(),
|
||||
viewport.ids.this,
|
||||
);
|
||||
event_response = self
|
||||
.integration
|
||||
.on_window_event(event, viewport.egui_winit.as_mut().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -752,6 +761,7 @@ impl GlowWinitRunning {
|
||||
impl GlutinWindowContext {
|
||||
#[allow(unsafe_code)]
|
||||
unsafe fn new(
|
||||
egui_ctx: &egui::Context,
|
||||
viewport_builder: ViewportBuilder,
|
||||
native_options: &NativeOptions,
|
||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
@@ -803,7 +813,11 @@ impl GlutinWindowContext {
|
||||
let display_builder = glutin_winit::DisplayBuilder::new()
|
||||
// we might want to expose this option to users in the future. maybe using an env var or using native_options.
|
||||
.with_preference(glutin_winit::ApiPreference::FallbackEgl) // https://github.com/emilk/egui/issues/2520#issuecomment-1367841150
|
||||
.with_window_builder(Some(create_winit_window_builder(viewport_builder.clone())));
|
||||
.with_window_builder(Some(create_winit_window_builder(
|
||||
egui_ctx,
|
||||
event_loop,
|
||||
viewport_builder.clone(),
|
||||
)));
|
||||
|
||||
let (window, gl_config) = {
|
||||
crate::profile_scope!("DisplayBuilder::build");
|
||||
@@ -899,6 +913,7 @@ impl GlutinWindowContext {
|
||||
// https://github.com/emilk/egui/pull/2541#issuecomment-1370767582
|
||||
|
||||
let mut slf = GlutinWindowContext {
|
||||
egui_ctx: egui_ctx.clone(),
|
||||
swap_interval,
|
||||
gl_config,
|
||||
current_gl_context: None,
|
||||
@@ -958,7 +973,7 @@ impl GlutinWindowContext {
|
||||
log::trace!("Window doesn't exist yet. Creating one now with finalize_window");
|
||||
let window = glutin_winit::finalize_window(
|
||||
event_loop,
|
||||
create_winit_window_builder(viewport.builder.clone()),
|
||||
create_winit_window_builder(&self.egui_ctx, event_loop, viewport.builder.clone()),
|
||||
&self.gl_config,
|
||||
)?;
|
||||
apply_viewport_builder_to_new_window(&window, &viewport.builder);
|
||||
@@ -1009,6 +1024,7 @@ impl GlutinWindowContext {
|
||||
|
||||
viewport.egui_winit.get_or_insert_with(|| {
|
||||
egui_winit::State::new(
|
||||
viewport_id,
|
||||
event_loop,
|
||||
Some(window.scale_factor() as f32),
|
||||
self.max_texture_side,
|
||||
@@ -1085,7 +1101,11 @@ impl GlutinWindowContext {
|
||||
self.gl_config.display().get_proc_address(addr)
|
||||
}
|
||||
|
||||
fn handle_viewport_output(&mut self, viewport_output: ViewportIdMap<ViewportOutput>) {
|
||||
fn handle_viewport_output(
|
||||
&mut self,
|
||||
egui_ctx: &egui::Context,
|
||||
viewport_output: ViewportIdMap<ViewportOutput>,
|
||||
) {
|
||||
crate::profile_function!();
|
||||
|
||||
let active_viewports_ids: ViewportIdSet = viewport_output.keys().copied().collect();
|
||||
@@ -1105,6 +1125,7 @@ impl GlutinWindowContext {
|
||||
let ids = ViewportIdPair::from_self_and_parent(viewport_id, parent);
|
||||
|
||||
let viewport = initialize_or_update_viewport(
|
||||
egui_ctx,
|
||||
&mut self.viewports,
|
||||
ids,
|
||||
class,
|
||||
@@ -1116,6 +1137,7 @@ impl GlutinWindowContext {
|
||||
if let Some(window) = &viewport.window {
|
||||
let is_viewport_focused = self.focused_viewport == Some(viewport_id);
|
||||
egui_winit::process_viewport_commands(
|
||||
egui_ctx,
|
||||
&mut viewport.info,
|
||||
commands,
|
||||
window,
|
||||
@@ -1148,14 +1170,15 @@ impl Viewport {
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize_or_update_viewport(
|
||||
viewports: &mut ViewportIdMap<Viewport>,
|
||||
fn initialize_or_update_viewport<'vp>(
|
||||
egu_ctx: &'_ egui::Context,
|
||||
viewports: &'vp mut ViewportIdMap<Viewport>,
|
||||
ids: ViewportIdPair,
|
||||
class: ViewportClass,
|
||||
mut builder: ViewportBuilder,
|
||||
viewport_ui_cb: Option<Arc<dyn Fn(&egui::Context) + Send + Sync>>,
|
||||
focused_viewport: Option<ViewportId>,
|
||||
) -> &mut Viewport {
|
||||
) -> &'vp mut Viewport {
|
||||
crate::profile_function!();
|
||||
|
||||
if builder.icon.is_none() {
|
||||
@@ -1190,19 +1213,20 @@ fn initialize_or_update_viewport(
|
||||
viewport.class = class;
|
||||
viewport.viewport_ui_cb = viewport_ui_cb;
|
||||
|
||||
let (delta_commands, recreate) = viewport.builder.patch(&builder);
|
||||
let (delta_commands, recreate) = viewport.builder.patch(builder);
|
||||
|
||||
if recreate {
|
||||
log::debug!(
|
||||
"Recreating window for viewport {:?} ({:?})",
|
||||
ids.this,
|
||||
builder.title
|
||||
viewport.builder.title
|
||||
);
|
||||
viewport.window = None;
|
||||
viewport.egui_winit = None;
|
||||
} else if let Some(window) = &viewport.window {
|
||||
let is_viewport_focused = focused_viewport == Some(ids.this);
|
||||
process_viewport_commands(
|
||||
egu_ctx,
|
||||
&mut viewport.info,
|
||||
delta_commands,
|
||||
window,
|
||||
@@ -1238,6 +1262,7 @@ fn render_immediate_viewport(
|
||||
let mut glutin = glutin.borrow_mut();
|
||||
|
||||
let viewport = initialize_or_update_viewport(
|
||||
egui_ctx,
|
||||
&mut glutin.viewports,
|
||||
ids,
|
||||
ViewportClass::Immediate,
|
||||
@@ -1267,7 +1292,7 @@ fn render_immediate_viewport(
|
||||
return;
|
||||
};
|
||||
|
||||
let mut raw_input = winit_state.take_egui_input(window, ids);
|
||||
let mut raw_input = winit_state.take_egui_input(window);
|
||||
raw_input.viewports = glutin
|
||||
.viewports
|
||||
.iter()
|
||||
@@ -1304,6 +1329,7 @@ fn render_immediate_viewport(
|
||||
let Some(viewport) = viewports.get_mut(&ids.this) else {
|
||||
return;
|
||||
};
|
||||
viewport.info.events.clear(); // they should have been processed
|
||||
|
||||
let Some(winit_state) = &mut viewport.egui_winit else {
|
||||
return;
|
||||
@@ -1351,9 +1377,9 @@ fn render_immediate_viewport(
|
||||
}
|
||||
}
|
||||
|
||||
winit_state.handle_platform_output(window, ids.this, egui_ctx, platform_output);
|
||||
winit_state.handle_platform_output(window, egui_ctx, platform_output);
|
||||
|
||||
glutin.handle_viewport_output(viewport_output);
|
||||
glutin.handle_viewport_output(egui_ctx, viewport_output);
|
||||
}
|
||||
|
||||
#[cfg(feature = "__screenshot")]
|
||||
|
||||
@@ -57,6 +57,7 @@ struct WgpuWinitRunning {
|
||||
///
|
||||
/// Wrapped in an `Rc<RefCell<…>>` so it can be re-entrantly shared via a weak-pointer.
|
||||
pub struct SharedState {
|
||||
egui_ctx: egui::Context,
|
||||
viewports: Viewports,
|
||||
painter: egui_wgpu::winit::Painter,
|
||||
viewport_from_window: HashMap<WindowId, ViewportId>,
|
||||
@@ -123,7 +124,12 @@ impl WgpuWinitApp {
|
||||
|
||||
for viewport in viewports.values_mut() {
|
||||
if viewport.window.is_none() {
|
||||
viewport.init_window(viewport_from_window, painter, event_loop);
|
||||
viewport.init_window(
|
||||
&running.integration.egui_ctx,
|
||||
viewport_from_window,
|
||||
painter,
|
||||
event_loop,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,6 +146,7 @@ impl WgpuWinitApp {
|
||||
|
||||
fn init_run_state(
|
||||
&mut self,
|
||||
egui_ctx: egui::Context,
|
||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
storage: Option<Box<dyn Storage>>,
|
||||
window: Window,
|
||||
@@ -163,12 +170,12 @@ impl WgpuWinitApp {
|
||||
|
||||
let system_theme = winit_integration::system_theme(&window, &self.native_options);
|
||||
let integration = EpiIntegration::new(
|
||||
egui_ctx.clone(),
|
||||
&window,
|
||||
system_theme,
|
||||
&self.app_name,
|
||||
&self.native_options,
|
||||
storage,
|
||||
winit_integration::IS_DESKTOP,
|
||||
#[cfg(feature = "glow")]
|
||||
None,
|
||||
wgpu_render_state.clone(),
|
||||
@@ -177,25 +184,25 @@ impl WgpuWinitApp {
|
||||
{
|
||||
let event_loop_proxy = self.repaint_proxy.clone();
|
||||
|
||||
integration
|
||||
.egui_ctx
|
||||
.set_request_repaint_callback(move |info| {
|
||||
log::trace!("request_repaint_callback: {info:?}");
|
||||
let when = Instant::now() + info.delay;
|
||||
let frame_nr = info.current_frame_nr;
|
||||
egui_ctx.set_request_repaint_callback(move |info| {
|
||||
log::trace!("request_repaint_callback: {info:?}");
|
||||
let when = Instant::now() + info.delay;
|
||||
let frame_nr = info.current_frame_nr;
|
||||
|
||||
event_loop_proxy
|
||||
.lock()
|
||||
.send_event(UserEvent::RequestRepaint {
|
||||
when,
|
||||
frame_nr,
|
||||
viewport_id: info.viewport_id,
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
event_loop_proxy
|
||||
.lock()
|
||||
.send_event(UserEvent::RequestRepaint {
|
||||
when,
|
||||
frame_nr,
|
||||
viewport_id: info.viewport_id,
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(unused_mut)] // used for accesskit
|
||||
let mut egui_winit = egui_winit::State::new(
|
||||
ViewportId::ROOT,
|
||||
event_loop,
|
||||
Some(window.scale_factor() as f32),
|
||||
painter.max_texture_side(),
|
||||
@@ -207,12 +214,12 @@ impl WgpuWinitApp {
|
||||
integration.init_accesskit(&mut egui_winit, &window, event_loop_proxy);
|
||||
}
|
||||
let theme = system_theme.unwrap_or(self.native_options.default_theme);
|
||||
integration.egui_ctx.set_visuals(theme.egui_visuals());
|
||||
egui_ctx.set_visuals(theme.egui_visuals());
|
||||
|
||||
let app_creator = std::mem::take(&mut self.app_creator)
|
||||
.expect("Single-use AppCreator has unexpectedly already been taken");
|
||||
let cc = CreationContext {
|
||||
egui_ctx: integration.egui_ctx.clone(),
|
||||
egui_ctx: egui_ctx.clone(),
|
||||
integration_info: integration.frame.info().clone(),
|
||||
storage: integration.frame.storage(),
|
||||
#[cfg(feature = "glow")]
|
||||
@@ -249,6 +256,7 @@ impl WgpuWinitApp {
|
||||
);
|
||||
|
||||
let shared = Rc::new(RefCell::new(SharedState {
|
||||
egui_ctx,
|
||||
viewport_from_window,
|
||||
viewports,
|
||||
painter,
|
||||
@@ -262,20 +270,14 @@ impl WgpuWinitApp {
|
||||
|
||||
let event_loop: *const EventLoopWindowTarget<UserEvent> = event_loop;
|
||||
|
||||
egui::Context::set_immediate_viewport_renderer(move |egui_ctx, immediate_viewport| {
|
||||
egui::Context::set_immediate_viewport_renderer(move |_egui_ctx, immediate_viewport| {
|
||||
if let Some(shared) = shared.upgrade() {
|
||||
// SAFETY: the event loop lives longer than
|
||||
// the Rc:s we just upgraded above.
|
||||
#[allow(unsafe_code)]
|
||||
let event_loop = unsafe { event_loop.as_ref().unwrap() };
|
||||
|
||||
render_immediate_viewport(
|
||||
event_loop,
|
||||
egui_ctx,
|
||||
beginning,
|
||||
&shared,
|
||||
immediate_viewport,
|
||||
);
|
||||
render_immediate_viewport(event_loop, beginning, &shared, immediate_viewport);
|
||||
} else {
|
||||
log::warn!("render_sync_callback called after window closed");
|
||||
}
|
||||
@@ -373,9 +375,14 @@ impl WinitApp for WgpuWinitApp {
|
||||
.as_ref()
|
||||
.unwrap_or(&self.app_name),
|
||||
);
|
||||
let (window, builder) =
|
||||
create_window(event_loop, storage.as_deref(), &mut self.native_options)?;
|
||||
self.init_run_state(event_loop, storage, window, builder)?
|
||||
let egui_ctx = winit_integration::create_egui_context(storage.as_deref());
|
||||
let (window, builder) = create_window(
|
||||
&egui_ctx,
|
||||
event_loop,
|
||||
storage.as_deref(),
|
||||
&mut self.native_options,
|
||||
)?;
|
||||
self.init_run_state(egui_ctx, event_loop, storage, window, builder)?
|
||||
};
|
||||
|
||||
EventResult::RepaintNow(
|
||||
@@ -504,7 +511,6 @@ impl WgpuWinitRunning {
|
||||
viewport.update_viewport_info();
|
||||
|
||||
let Viewport {
|
||||
ids,
|
||||
viewport_ui_cb,
|
||||
window,
|
||||
egui_winit,
|
||||
@@ -525,10 +531,7 @@ impl WgpuWinitRunning {
|
||||
}
|
||||
}
|
||||
|
||||
let mut raw_input = egui_winit.as_mut().unwrap().take_egui_input(
|
||||
window,
|
||||
ViewportIdPair::from_self_and_parent(viewport_id, ids.parent),
|
||||
);
|
||||
let mut raw_input = egui_winit.as_mut().unwrap().take_egui_input(window);
|
||||
|
||||
integration.pre_update();
|
||||
|
||||
@@ -552,6 +555,7 @@ impl WgpuWinitRunning {
|
||||
let mut shared = shared.borrow_mut();
|
||||
|
||||
let SharedState {
|
||||
egui_ctx,
|
||||
viewports,
|
||||
painter,
|
||||
viewport_from_window,
|
||||
@@ -562,6 +566,8 @@ impl WgpuWinitRunning {
|
||||
return EventResult::Wait;
|
||||
};
|
||||
|
||||
viewport.info.events.clear(); // they should have been processed
|
||||
|
||||
let Viewport {
|
||||
window: Some(window),
|
||||
egui_winit: Some(egui_winit),
|
||||
@@ -581,16 +587,16 @@ impl WgpuWinitRunning {
|
||||
viewport_output,
|
||||
} = full_output;
|
||||
|
||||
integration.handle_platform_output(window, viewport_id, platform_output, egui_winit);
|
||||
egui_winit.handle_platform_output(window, egui_ctx, platform_output);
|
||||
|
||||
{
|
||||
let clipped_primitives = integration.egui_ctx.tessellate(shapes, pixels_per_point);
|
||||
let clipped_primitives = egui_ctx.tessellate(shapes, pixels_per_point);
|
||||
|
||||
let screenshot_requested = std::mem::take(&mut viewport.screenshot_requested);
|
||||
let screenshot = painter.paint_and_update_textures(
|
||||
viewport_id,
|
||||
pixels_per_point,
|
||||
app.clear_color(&integration.egui_ctx.style().visuals),
|
||||
app.clear_color(&egui_ctx.style().visuals),
|
||||
&clipped_primitives,
|
||||
&textures_delta,
|
||||
screenshot_requested,
|
||||
@@ -610,7 +616,12 @@ impl WgpuWinitRunning {
|
||||
|
||||
let active_viewports_ids: ViewportIdSet = viewport_output.keys().copied().collect();
|
||||
|
||||
handle_viewport_output(viewport_output, viewports, *focused_viewport);
|
||||
handle_viewport_output(
|
||||
&integration.egui_ctx,
|
||||
viewport_output,
|
||||
viewports,
|
||||
*focused_viewport,
|
||||
);
|
||||
|
||||
// Prune dead viewports:
|
||||
viewports.retain(|id, _| active_viewports_ids.contains(id));
|
||||
@@ -649,8 +660,8 @@ impl WgpuWinitRunning {
|
||||
|
||||
let Self {
|
||||
integration,
|
||||
app,
|
||||
shared,
|
||||
..
|
||||
} = self;
|
||||
let mut shared = shared.borrow_mut();
|
||||
|
||||
@@ -725,9 +736,10 @@ impl WgpuWinitRunning {
|
||||
let event_response = viewport_id
|
||||
.and_then(|viewport_id| {
|
||||
shared.viewports.get_mut(&viewport_id).and_then(|viewport| {
|
||||
viewport.egui_winit.as_mut().map(|egui_winit| {
|
||||
integration.on_window_event(app.as_mut(), event, egui_winit, viewport_id)
|
||||
})
|
||||
viewport
|
||||
.egui_winit
|
||||
.as_mut()
|
||||
.map(|egui_winit| integration.on_window_event(event, egui_winit))
|
||||
})
|
||||
})
|
||||
.unwrap_or_default();
|
||||
@@ -749,6 +761,7 @@ impl WgpuWinitRunning {
|
||||
impl Viewport {
|
||||
fn init_window(
|
||||
&mut self,
|
||||
egui_ctx: &egui::Context,
|
||||
windows_id: &mut HashMap<WindowId, ViewportId>,
|
||||
painter: &mut egui_wgpu::winit::Painter,
|
||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
@@ -757,7 +770,9 @@ impl Viewport {
|
||||
|
||||
let viewport_id = self.ids.this;
|
||||
|
||||
match create_winit_window_builder(self.builder.clone()).build(event_loop) {
|
||||
match create_winit_window_builder(egui_ctx, event_loop, self.builder.clone())
|
||||
.build(event_loop)
|
||||
{
|
||||
Ok(window) => {
|
||||
apply_viewport_builder_to_new_window(&window, &self.builder);
|
||||
|
||||
@@ -769,6 +784,7 @@ impl Viewport {
|
||||
}
|
||||
|
||||
self.egui_winit = Some(egui_winit::State::new(
|
||||
viewport_id,
|
||||
event_loop,
|
||||
Some(window.scale_factor() as f32),
|
||||
painter.max_texture_side(),
|
||||
@@ -799,6 +815,7 @@ impl Viewport {
|
||||
}
|
||||
|
||||
fn create_window(
|
||||
egui_ctx: &egui::Context,
|
||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
storage: Option<&dyn Storage>,
|
||||
native_options: &mut NativeOptions,
|
||||
@@ -806,11 +823,16 @@ fn create_window(
|
||||
crate::profile_function!();
|
||||
|
||||
let window_settings = epi_integration::load_window_settings(storage);
|
||||
let viewport_builder =
|
||||
epi_integration::viewport_builder(event_loop, native_options, window_settings);
|
||||
let viewport_builder = epi_integration::viewport_builder(
|
||||
egui_ctx.zoom_factor(),
|
||||
event_loop,
|
||||
native_options,
|
||||
window_settings,
|
||||
);
|
||||
let window = {
|
||||
crate::profile_scope!("WindowBuilder::build");
|
||||
create_winit_window_builder(viewport_builder.clone()).build(event_loop)?
|
||||
create_winit_window_builder(egui_ctx, event_loop, viewport_builder.clone())
|
||||
.build(event_loop)?
|
||||
};
|
||||
apply_viewport_builder_to_new_window(&window, &viewport_builder);
|
||||
epi_integration::apply_window_settings(&window, window_settings);
|
||||
@@ -819,7 +841,6 @@ fn create_window(
|
||||
|
||||
fn render_immediate_viewport(
|
||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
egui_ctx: &egui::Context,
|
||||
beginning: Instant,
|
||||
shared: &RefCell<SharedState>,
|
||||
immediate_viewport: ImmediateViewport<'_>,
|
||||
@@ -834,6 +855,7 @@ fn render_immediate_viewport(
|
||||
|
||||
let input = {
|
||||
let SharedState {
|
||||
egui_ctx,
|
||||
viewports,
|
||||
painter,
|
||||
viewport_from_window,
|
||||
@@ -841,6 +863,7 @@ fn render_immediate_viewport(
|
||||
} = &mut *shared.borrow_mut();
|
||||
|
||||
let viewport = initialize_or_update_viewport(
|
||||
egui_ctx,
|
||||
viewports,
|
||||
ids,
|
||||
ViewportClass::Immediate,
|
||||
@@ -849,7 +872,7 @@ fn render_immediate_viewport(
|
||||
None,
|
||||
);
|
||||
if viewport.window.is_none() {
|
||||
viewport.init_window(viewport_from_window, painter, event_loop);
|
||||
viewport.init_window(egui_ctx, viewport_from_window, painter, event_loop);
|
||||
}
|
||||
viewport.update_viewport_info();
|
||||
|
||||
@@ -857,7 +880,7 @@ fn render_immediate_viewport(
|
||||
return;
|
||||
};
|
||||
|
||||
let mut input = winit_state.take_egui_input(window, ids);
|
||||
let mut input = winit_state.take_egui_input(window);
|
||||
input.viewports = viewports
|
||||
.iter()
|
||||
.map(|(id, viewport)| (*id, viewport.info.clone()))
|
||||
@@ -866,6 +889,8 @@ fn render_immediate_viewport(
|
||||
input
|
||||
};
|
||||
|
||||
let egui_ctx = shared.borrow().egui_ctx.clone();
|
||||
|
||||
// ------------------------------------------
|
||||
|
||||
// Run the user code, which could re-entrantly call this function again (!).
|
||||
@@ -893,6 +918,7 @@ fn render_immediate_viewport(
|
||||
let Some(viewport) = viewports.get_mut(&ids.this) else {
|
||||
return;
|
||||
};
|
||||
viewport.info.events.clear(); // they should have been processed
|
||||
let Some(winit_state) = &mut viewport.egui_winit else {
|
||||
return;
|
||||
};
|
||||
@@ -917,13 +943,14 @@ fn render_immediate_viewport(
|
||||
false,
|
||||
);
|
||||
|
||||
winit_state.handle_platform_output(window, ids.this, egui_ctx, platform_output);
|
||||
winit_state.handle_platform_output(window, &egui_ctx, platform_output);
|
||||
|
||||
handle_viewport_output(viewport_output, viewports, *focused_viewport);
|
||||
handle_viewport_output(&egui_ctx, viewport_output, viewports, *focused_viewport);
|
||||
}
|
||||
|
||||
/// Add new viewports, and update existing ones:
|
||||
fn handle_viewport_output(
|
||||
egui_ctx: &egui::Context,
|
||||
viewport_output: ViewportIdMap<ViewportOutput>,
|
||||
viewports: &mut ViewportIdMap<Viewport>,
|
||||
focused_viewport: Option<ViewportId>,
|
||||
@@ -943,6 +970,7 @@ fn handle_viewport_output(
|
||||
let ids = ViewportIdPair::from_self_and_parent(viewport_id, parent);
|
||||
|
||||
let viewport = initialize_or_update_viewport(
|
||||
egui_ctx,
|
||||
viewports,
|
||||
ids,
|
||||
class,
|
||||
@@ -954,6 +982,7 @@ fn handle_viewport_output(
|
||||
if let Some(window) = viewport.window.as_ref() {
|
||||
let is_viewport_focused = focused_viewport == Some(viewport_id);
|
||||
egui_winit::process_viewport_commands(
|
||||
egui_ctx,
|
||||
&mut viewport.info,
|
||||
commands,
|
||||
window,
|
||||
@@ -964,14 +993,15 @@ fn handle_viewport_output(
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize_or_update_viewport(
|
||||
viewports: &mut Viewports,
|
||||
fn initialize_or_update_viewport<'vp>(
|
||||
egui_ctx: &egui::Context,
|
||||
viewports: &'vp mut Viewports,
|
||||
ids: ViewportIdPair,
|
||||
class: ViewportClass,
|
||||
mut builder: ViewportBuilder,
|
||||
viewport_ui_cb: Option<Arc<dyn Fn(&egui::Context) + Send + Sync>>,
|
||||
focused_viewport: Option<ViewportId>,
|
||||
) -> &mut Viewport {
|
||||
) -> &'vp mut Viewport {
|
||||
if builder.icon.is_none() {
|
||||
// Inherit icon from parent
|
||||
builder.icon = viewports
|
||||
@@ -1003,19 +1033,20 @@ fn initialize_or_update_viewport(
|
||||
viewport.ids.parent = ids.parent;
|
||||
viewport.viewport_ui_cb = viewport_ui_cb;
|
||||
|
||||
let (delta_commands, recreate) = viewport.builder.patch(&builder);
|
||||
let (delta_commands, recreate) = viewport.builder.patch(builder);
|
||||
|
||||
if recreate {
|
||||
log::debug!(
|
||||
"Recreating window for viewport {:?} ({:?})",
|
||||
ids.this,
|
||||
builder.title
|
||||
viewport.builder.title
|
||||
);
|
||||
viewport.window = None;
|
||||
viewport.egui_winit = None;
|
||||
} else if let Some(window) = &viewport.window {
|
||||
let is_viewport_focused = focused_viewport == Some(ids.this);
|
||||
process_viewport_commands(
|
||||
egui_ctx,
|
||||
&mut viewport.info,
|
||||
delta_commands,
|
||||
window,
|
||||
|
||||
@@ -11,13 +11,27 @@ use egui_winit::accesskit_winit;
|
||||
|
||||
use super::epi_integration::EpiIntegration;
|
||||
|
||||
pub const IS_DESKTOP: bool = cfg!(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "linux",
|
||||
target_os = "macos",
|
||||
target_os = "openbsd",
|
||||
target_os = "windows",
|
||||
));
|
||||
/// Create an egui context, restoring it from storage if possible.
|
||||
pub fn create_egui_context(storage: Option<&dyn crate::Storage>) -> egui::Context {
|
||||
crate::profile_function!();
|
||||
|
||||
pub const IS_DESKTOP: bool = cfg!(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "linux",
|
||||
target_os = "macos",
|
||||
target_os = "openbsd",
|
||||
target_os = "windows",
|
||||
));
|
||||
|
||||
let egui_ctx = egui::Context::default();
|
||||
|
||||
egui_ctx.set_embed_viewports(!IS_DESKTOP);
|
||||
|
||||
let memory = crate::native::epi_integration::load_egui_memory(storage).unwrap_or_default();
|
||||
egui_ctx.memory_mut(|mem| *mem = memory);
|
||||
|
||||
egui_ctx
|
||||
}
|
||||
|
||||
/// The custom even `eframe` uses with the [`winit`] event loop.
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use egui::TexturesDelta;
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
use crate::{epi, App};
|
||||
|
||||
@@ -17,7 +16,10 @@ pub struct AppRunner {
|
||||
screen_reader: super::screen_reader::ScreenReader,
|
||||
pub(crate) text_cursor_pos: Option<egui::Pos2>,
|
||||
pub(crate) mutable_text_under_cursor: bool,
|
||||
|
||||
// Output for the last run:
|
||||
textures_delta: TexturesDelta,
|
||||
clipped_primitives: Option<Vec<egui::ClippedPrimitive>>,
|
||||
}
|
||||
|
||||
impl Drop for AppRunner {
|
||||
@@ -58,6 +60,12 @@ impl AppRunner {
|
||||
));
|
||||
super::storage::load_memory(&egui_ctx);
|
||||
|
||||
egui_ctx.options_mut(|o| {
|
||||
// On web, the browser controls the zoom factor:
|
||||
o.zoom_with_keyboard = false;
|
||||
o.zoom_factor = 1.0;
|
||||
});
|
||||
|
||||
let theme = system_theme.unwrap_or(web_options.default_theme);
|
||||
egui_ctx.set_visuals(theme.egui_visuals());
|
||||
|
||||
@@ -109,10 +117,17 @@ impl AppRunner {
|
||||
text_cursor_pos: None,
|
||||
mutable_text_under_cursor: false,
|
||||
textures_delta: Default::default(),
|
||||
clipped_primitives: None,
|
||||
};
|
||||
|
||||
runner.input.raw.max_texture_side = Some(runner.painter.max_texture_side());
|
||||
runner.input.raw.native_pixels_per_point = Some(super::native_pixels_per_point());
|
||||
runner
|
||||
.input
|
||||
.raw
|
||||
.viewports
|
||||
.entry(egui::ViewportId::ROOT)
|
||||
.or_default()
|
||||
.native_pixels_per_point = Some(super::native_pixels_per_point());
|
||||
|
||||
Ok(runner)
|
||||
}
|
||||
@@ -158,8 +173,26 @@ impl AppRunner {
|
||||
self.painter.destroy();
|
||||
}
|
||||
|
||||
/// Call [`Self::paint`] later to paint
|
||||
pub fn logic(&mut self) -> Vec<egui::ClippedPrimitive> {
|
||||
/// Runs the user code and paints the UI.
|
||||
///
|
||||
/// If there is already an outstanding frame of output,
|
||||
/// that is painted instead.
|
||||
pub fn run_and_paint(&mut self) {
|
||||
if self.clipped_primitives.is_none() {
|
||||
// Run user code, and paint the results:
|
||||
self.logic();
|
||||
self.paint();
|
||||
} else {
|
||||
// We have already run the logic, e.g. in an on-click event,
|
||||
// so let's only present the results:
|
||||
self.paint();
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs the logic, but doesn't paint the result.
|
||||
///
|
||||
/// The result can be painted later with a call to [`Self::run_and_paint`] or [`Self::paint`].
|
||||
pub fn logic(&mut self) {
|
||||
let frame_start = now_sec();
|
||||
|
||||
super::resize_canvas_to_screen_size(self.canvas_id(), self.web_options.max_size_points);
|
||||
@@ -191,25 +224,26 @@ impl AppRunner {
|
||||
|
||||
self.handle_platform_output(platform_output);
|
||||
self.textures_delta.append(textures_delta);
|
||||
let clipped_primitives = self.egui_ctx.tessellate(shapes, pixels_per_point);
|
||||
self.clipped_primitives = Some(self.egui_ctx.tessellate(shapes, pixels_per_point));
|
||||
|
||||
self.frame.info.cpu_usage = Some((now_sec() - frame_start) as f32);
|
||||
|
||||
clipped_primitives
|
||||
}
|
||||
|
||||
/// Paint the results of the last call to [`Self::logic`].
|
||||
pub fn paint(&mut self, clipped_primitives: &[egui::ClippedPrimitive]) -> Result<(), JsValue> {
|
||||
pub fn paint(&mut self) {
|
||||
let textures_delta = std::mem::take(&mut self.textures_delta);
|
||||
let clipped_primitives = std::mem::take(&mut self.clipped_primitives);
|
||||
|
||||
self.painter.paint_and_update_textures(
|
||||
self.app.clear_color(&self.egui_ctx.style().visuals),
|
||||
clipped_primitives,
|
||||
self.egui_ctx.pixels_per_point(),
|
||||
&textures_delta,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
if let Some(clipped_primitives) = clipped_primitives {
|
||||
if let Err(err) = self.painter.paint_and_update_textures(
|
||||
self.app.clear_color(&self.egui_ctx.style().visuals),
|
||||
&clipped_primitives,
|
||||
self.egui_ctx.pixels_per_point(),
|
||||
&textures_delta,
|
||||
) {
|
||||
log::error!("Failed to paint: {}", super::string_from_js_value(&err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_platform_output(&mut self, platform_output: egui::PlatformOutput) {
|
||||
|
||||
@@ -23,12 +23,17 @@ pub(crate) struct WebInput {
|
||||
|
||||
impl WebInput {
|
||||
pub fn new_frame(&mut self, canvas_size: egui::Vec2) -> egui::RawInput {
|
||||
egui::RawInput {
|
||||
let mut raw_input = egui::RawInput {
|
||||
screen_rect: Some(egui::Rect::from_min_size(Default::default(), canvas_size)),
|
||||
pixels_per_point: Some(super::native_pixels_per_point()), // We ALWAYS use the native pixels-per-point
|
||||
time: Some(super::now_sec()),
|
||||
..self.raw.take()
|
||||
}
|
||||
};
|
||||
raw_input
|
||||
.viewports
|
||||
.entry(egui::ViewportId::ROOT)
|
||||
.or_default()
|
||||
.native_pixels_per_point = Some(super::native_pixels_per_point());
|
||||
raw_input
|
||||
}
|
||||
|
||||
pub fn on_web_page_focus_change(&mut self, focused: bool) {
|
||||
@@ -68,6 +73,10 @@ impl NeedRepaint {
|
||||
*repaint_time = repaint_time.min(super::now_sec() + num_seconds);
|
||||
}
|
||||
|
||||
pub fn needs_repaint(&self) -> bool {
|
||||
self.when_to_repaint() <= super::now_sec()
|
||||
}
|
||||
|
||||
pub fn repaint_asap(&self) {
|
||||
*self.0.lock() = f64::NEG_INFINITY;
|
||||
}
|
||||
|
||||
@@ -8,22 +8,19 @@ use super::*;
|
||||
fn paint_and_schedule(runner_ref: &WebRunner) -> Result<(), JsValue> {
|
||||
// Only paint and schedule if there has been no panic
|
||||
if let Some(mut runner_lock) = runner_ref.try_lock() {
|
||||
paint_if_needed(&mut runner_lock)?;
|
||||
paint_if_needed(&mut runner_lock);
|
||||
drop(runner_lock);
|
||||
request_animation_frame(runner_ref.clone())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn paint_if_needed(runner: &mut AppRunner) -> Result<(), JsValue> {
|
||||
if runner.needs_repaint.when_to_repaint() <= now_sec() {
|
||||
fn paint_if_needed(runner: &mut AppRunner) {
|
||||
if runner.needs_repaint.needs_repaint() {
|
||||
runner.needs_repaint.clear();
|
||||
let clipped_primitives = runner.logic();
|
||||
runner.paint(&clipped_primitives)?;
|
||||
runner.auto_save_if_needed();
|
||||
runner.run_and_paint();
|
||||
}
|
||||
Ok(())
|
||||
runner.auto_save_if_needed();
|
||||
}
|
||||
|
||||
pub(crate) fn request_animation_frame(runner_ref: WebRunner) -> Result<(), JsValue> {
|
||||
@@ -177,10 +174,14 @@ pub(crate) fn install_document_events(runner_ref: &WebRunner) -> Result<(), JsVa
|
||||
"cut",
|
||||
|event: web_sys::ClipboardEvent, runner| {
|
||||
runner.input.raw.events.push(egui::Event::Cut);
|
||||
|
||||
// In Safari we are only allowed to write to the clipboard during the
|
||||
// event callback, which is why we run the app logic here and now:
|
||||
runner.logic(); // we ignore the returned triangles, but schedule a repaint right after
|
||||
runner.logic();
|
||||
|
||||
// Make sure we paint the output of the above logic call asap:
|
||||
runner.needs_repaint.repaint_asap();
|
||||
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
},
|
||||
@@ -192,10 +193,14 @@ pub(crate) fn install_document_events(runner_ref: &WebRunner) -> Result<(), JsVa
|
||||
"copy",
|
||||
|event: web_sys::ClipboardEvent, runner| {
|
||||
runner.input.raw.events.push(egui::Event::Copy);
|
||||
|
||||
// In Safari we are only allowed to write to the clipboard during the
|
||||
// event callback, which is why we run the app logic here and now:
|
||||
runner.logic(); // we ignore the returned triangles, but schedule a repaint right after
|
||||
runner.logic();
|
||||
|
||||
// Make sure we paint the output of the above logic call asap:
|
||||
runner.needs_repaint.repaint_asap();
|
||||
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
},
|
||||
@@ -281,6 +286,12 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu
|
||||
pressed: true,
|
||||
modifiers,
|
||||
});
|
||||
|
||||
// In Safari we are only allowed to write to the clipboard during the
|
||||
// event callback, which is why we run the app logic here and now:
|
||||
runner.logic();
|
||||
|
||||
// Make sure we paint the output of the above logic call asap:
|
||||
runner.needs_repaint.repaint_asap();
|
||||
}
|
||||
event.stop_propagation();
|
||||
@@ -310,6 +321,12 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu
|
||||
pressed: false,
|
||||
modifiers,
|
||||
});
|
||||
|
||||
// In Safari we are only allowed to write to the clipboard during the
|
||||
// event callback, which is why we run the app logic here and now:
|
||||
runner.logic();
|
||||
|
||||
// Make sure we paint the output of the above logic call asap:
|
||||
runner.needs_repaint.repaint_asap();
|
||||
|
||||
text_agent::update_text_agent(runner);
|
||||
|
||||
@@ -49,6 +49,10 @@ use crate::Theme;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub(crate) fn string_from_js_value(value: &JsValue) -> String {
|
||||
value.as_string().unwrap_or_else(|| format!("{value:#?}"))
|
||||
}
|
||||
|
||||
/// Current time in seconds (since undefined point in time).
|
||||
///
|
||||
/// Monotonically increasing.
|
||||
@@ -196,7 +200,7 @@ fn set_clipboard_text(s: &str) {
|
||||
let future = wasm_bindgen_futures::JsFuture::from(promise);
|
||||
let future = async move {
|
||||
if let Err(err) = future.await {
|
||||
log::error!("Copy/cut action failed: {err:?}");
|
||||
log::error!("Copy/cut action failed: {}", string_from_js_value(&err));
|
||||
}
|
||||
};
|
||||
wasm_bindgen_futures::spawn_local(future);
|
||||
|
||||
@@ -95,7 +95,10 @@ impl WebRunner {
|
||||
log::debug!("Unsubscribing from {} events", events_to_unsubscribe.len());
|
||||
for x in events_to_unsubscribe {
|
||||
if let Err(err) = x.unsubscribe() {
|
||||
log::warn!("Failed to unsubscribe from event: {err:?}");
|
||||
log::warn!(
|
||||
"Failed to unsubscribe from event: {}",
|
||||
super::string_from_js_value(&err)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user