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

Multiple viewports/windows (#3172)

* Closes #1044

---
(new PR description written by @emilk)

## Overview
This PR introduces the concept of `Viewports`, which on the native
eframe backend corresponds to native OS windows.

You can spawn a new viewport using `Context::show_viewport` and
`Cotext::show_viewport_immediate`.
These needs to be called every frame the viewport should be visible.

This is implemented by the native `eframe` backend, but not the web one.

## Viewport classes
The viewports form a tree of parent-child relationships.

There are different classes of viewports.

### Root vieport
The root viewport is the original viewport, and cannot be closed without
closing the application.

### Deferred viewports
These are created with `Context::show_viewport`.
Deferred viewports take a closure that is called by the integration at a
later time, perhaps multiple times.
Deferred viewports are repainted independenantly of the parent viewport.
This means communication with them need to done via channels, or
`Arc/Mutex`.

This is the most performant type of child viewport, though a bit more
cumbersome to work with compared to immediate viewports.

### Immediate viewports
These are created with `Context::show_viewport_immediate`.
Immediate viewports take a `FnOnce` closure, similar to other egui
functions, and is called immediately. This makes communication with them
much simpler than with deferred viewports, but this simplicity comes at
a cost: whenever tha parent viewports needs to be repainted, so will the
child viewport, and vice versa. This means that if you have `N`
viewports you are poentially doing `N` times as much CPU work. However,
if all your viewports are showing animations, and thus are repainting
constantly anyway, this doesn't matter.

In short: immediate viewports are simpler to use, but can waste a lot of
CPU time.

### Embedded viewports
These are not real, independenant viewports, but is a fallback mode for
when the integration does not support real viewports. In your callback
is called with `ViewportClass::Embedded` it means you need to create an
`egui::Window` to wrap your ui in, which will then be embedded in the
parent viewport, unable to escape it.


## Using the viewports
Only one viewport is active at any one time, identified wth
`Context::viewport_id`.
You can send commands to other viewports using
`Context::send_viewport_command_to`.

There is an example in
<https://github.com/emilk/egui/tree/master/examples/multiple_viewports/src/main.rs>.

## For integrations
There are several changes relevant to integrations.

* There is a [`crate::RawInput::viewport`] with information about the
current viewport.
* The repaint callback set by `Context::set_request_repaint_callback`
now points to which viewport should be repainted.
* `Context::run` now returns a list of viewports in `FullOutput` which
should result in their own independant windows
* There is a new `Context::set_immediate_viewport_renderer` for setting
up the immediate viewport integration
* If you support viewports, you need to call
`Context::set_embed_viewports(false)`, or all new viewports will be
embedded (the default behavior).


## Future work
* Make it easy to wrap child viewports in the same chrome as
`egui::Window`
* Automatically show embedded viewports using `egui::Window`
* Use the new `ViewportBuilder` in `eframe::NativeOptions`
* Automatically position new viewport windows (they currently cover each
other)
* Add a `Context` method for listing all existing viewports

Find more at https://github.com/emilk/egui/issues/3556




---

<details>
<summary>
Outdated PR description by @konkitoman
</summary>


## Inspiration
- Godot because the app always work desktop or single_window because of
embedding
- Dear ImGui viewport system

## What is a Viewport

A Viewport is a egui isolated component!
Can be used by the egui integration to create native windows!

When you create a Viewport is possible that the backend do not supports
that!
So you need to check if the Viewport was created or you are in the
normal egui context!
This is how you can do that:
```rust
if ctx.viewport_id() != ctx.parent_viewport_id() {
    // In here you add the code for the viewport context, like
    egui::CentralPanel::default().show(ctx, |ui|{
        ui.label("This is in a native window!");
    });
}else{
    // In here you add the code for when viewport cannot be created!
   // You cannot use CentralPanel in here because you will override the app CentralPanel
   egui::Window::new("Virtual Viewport").show(ctx, |ui|{
       ui.label("This is without a native window!\nThis is in a embedded viewport");
   });
}
```

This PR do not support for drag and drop between Viewports!

After this PR is accepted i will begin work to intregrate the Viewport
system in `egui::Window`!
The `egui::Window` i want to behave the same on desktop and web
The `egui::Window` will be like Godot Window

## Changes and new

These are only public structs and functions!

<details>
<summary>

## New
</summary>

- `egui::ViewportId`
- `egui::ViewportBuilder`
This is like winit WindowBuilder

- `egui::ViewportCommand`
With this you can set any winit property on a viewport, when is a native
window!

- `egui::Context::new`
- `egui::Context::create_viewport`
- `egui::Context::create_viewport_sync`
- `egui::Context::viewport_id`
- `egui::Context::parent_viewport_id`
- `egui::Context::viewport_id_pair`
- `egui::Context::set_render_sync_callback`
- `egui::Context::is_desktop`
- `egui::Context::force_embedding`
- `egui::Context::set_force_embedding`
- `egui::Context::viewport_command`
- `egui::Context::send_viewport_command_to`
- `egui::Context::input_for`
- `egui::Context::input_mut_for`
- `egui::Context::frame_nr_for`
- `egui::Context::request_repaint_for`
- `egui::Context::request_repaint_after_for`
- `egui::Context::requested_repaint_last_frame`
- `egui::Context::requested_repaint_last_frame_for`
- `egui::Context::requested_repaint`
- `egui::Context::requested_repaint_for`
- `egui::Context::inner_rect`
- `egui::Context::outer_rect`

- `egui::InputState::inner_rect`
- `egui::InputState::outer_rect`

- `egui::WindowEvent`

</details>

<details>
<summary>

## Changes
</summary>

- `egui::Context::run`
Now needs the viewport that we want to render!

- `egui::Context::begin_frame`
Now needs the viewport that we want to render!

- `egui::Context::tessellate`
Now needs the viewport that we want to render!

- `egui::FullOutput`
```diff
- repaint_after
+ viewports
+ viewport_commands
```

- `egui::RawInput`
```diff
+ inner_rect
+ outer_rect
```

- `egui::Event`
```diff
+ WindowEvent
```
</details>

### Async Viewport

Async means that is independent from other viewports!

Is created by `egui::Context::create_viewport`

To be used you will need to wrap your state in `Arc<RwLock<T>>`
Look at viewports example to understand how to use it!

### Sync Viewport

Sync means that is dependent on his parent!

Is created by `egui::Context::create_viewport_sync`

This will pause the parent then render itself the resumes his parent!

#### ⚠️ This currently will make the fps/2 for every sync
viewport

### Common

#### ⚠️ Attention

You will need to do this when you render your content
```rust
ctx.create_viewport(ViewportBuilder::new("Simple Viewport"), | ctx | {
    let content = |ui: &mut egui::Ui|{
        ui.label("Content");
    };

    // This will make the content a popup if cannot create a native window
    if ctx.viewport_id() != ctx.parent_viewport_id() {
        egui::CentralPanel::default().show(ctx, content);
    } else {
        egui::Area::new("Simple Viewport").show(ctx, |ui| {
            egui::Frame::popup(ui.style()).show(ui, content);
        });
    };
});
````

## What you need to know as egui user

### If you are using eframe

You don't need to change anything!

### If you have a manual implementation

Now `egui::run` or `egui::begin` and `egui::tessellate` will need the
current viewport id!
You cannot create a `ViewportId` only `ViewportId::MAIN`

If you make a single window app you will set the viewport id to be
`egui::ViewportId::MAIN` or see the `examples/pure_glow`
If you want to have multiples window support look at `crates/eframe`
glow or wgpu implementations!

## If you want to try this

- cargo run -p viewports

## This before was wanted to change

This will probably be in feature PR's

### egui::Window

To create a native window when embedded was set to false
You can try that in viewports example before:
[78a0ae8](78a0ae879e)

### egui popups, context_menu, tooltip

To be a native window

</details>

---------

Co-authored-by: Konkitoman <konkitoman@users.noreply.github.com>
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
Co-authored-by: Pablo Sichert <mail@pablosichert.com>
This commit is contained in:
Konkitoman
2023-11-16 12:25:05 +02:00
committed by GitHub
parent fb8fa67afd
commit 83aa3109d3
43 changed files with 5096 additions and 1282 deletions

View File

@@ -44,7 +44,7 @@ pub type EventLoopBuilderHook = Box<dyn FnOnce(&mut EventLoopBuilder<UserEvent>)
/// done by `eframe`.
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu"))]
pub type WindowBuilderHook = Box<dyn FnOnce(WindowBuilder) -> WindowBuilder>;
pub type WindowBuilderHook = Box<dyn FnOnce(egui::ViewportBuilder) -> egui::ViewportBuilder>;
/// This is how your app is created.
///
@@ -120,6 +120,10 @@ pub trait App {
/// The [`egui::Context`] can be cloned and saved if you like.
///
/// To force a repaint, call [`egui::Context::request_repaint`] at any time (e.g. from another thread).
///
/// This is called for the root viewport ([`egui::ViewportId::ROOT`]).
/// Use [`egui::Context::show_viewport`] to spawn additional viewports (windows).
/// (A "viewport" in egui means an native OS window).
fn update(&mut self, ctx: &egui::Context, frame: &mut Frame);
/// Get a handle to the app.

View File

@@ -313,6 +313,11 @@ pub enum Error {
#[error("Found no glutin configs matching the template: {0:?}. Error: {1:?}")]
NoGlutinConfigs(glutin::config::ConfigTemplate, Box<dyn std::error::Error>),
/// An error from [`glutin`] when using [`glow`].
#[cfg(feature = "glow")]
#[error("egui_glow: {0}")]
OpenGL(#[from] egui_glow::PainterError),
/// An error from [`wgpu`].
#[cfg(feature = "wgpu")]
#[error("WGPU error: {0}")]
@@ -320,7 +325,7 @@ pub enum Error {
}
/// Short for `Result<T, eframe::Error>`.
pub type Result<T> = std::result::Result<T, Error>;
pub type Result<T, E = Error> = std::result::Result<T, E>;
// ---------------------------------------------------------------------------

View File

@@ -1,15 +1,10 @@
use winit::event_loop::EventLoopWindowTarget;
use std::time::Instant;
#[cfg(target_os = "macos")]
use winit::platform::macos::WindowBuilderExtMacOS as _;
use winit::event_loop::EventLoopWindowTarget;
use raw_window_handle::{HasRawDisplayHandle as _, HasRawWindowHandle as _};
#[cfg(feature = "accesskit")]
use egui::accesskit;
use egui::NumExt as _;
#[cfg(feature = "accesskit")]
use egui_winit::accesskit_winit;
use egui::{DeferredViewportUiCallback, NumExt as _, ViewportBuilder, ViewportId, ViewportIdPair};
use egui_winit::{native_pixels_per_point, EventResponse, WindowSettings};
use crate::{epi, Theme, WindowInfo};
@@ -22,13 +17,6 @@ pub struct WindowState {
pub maximized: bool,
}
pub fn points_to_size(points: egui::Vec2) -> winit::dpi::LogicalSize<f64> {
winit::dpi::LogicalSize {
width: points.x as f64,
height: points.y as f64,
}
}
pub fn read_window_info(
window: &winit::window::Window,
pixels_per_point: f32,
@@ -77,7 +65,7 @@ pub fn window_builder<E>(
title: &str,
native_options: &mut epi::NativeOptions,
window_settings: Option<WindowSettings>,
) -> winit::window::WindowBuilder {
) -> ViewportBuilder {
let epi::NativeOptions {
maximized,
decorated,
@@ -97,24 +85,29 @@ pub fn window_builder<E>(
..
} = native_options;
let window_icon = icon_data.clone().and_then(load_icon);
let mut window_builder = winit::window::WindowBuilder::new()
let mut viewport_builder = egui::ViewportBuilder::default()
.with_title(title)
.with_decorations(*decorated)
.with_fullscreen(fullscreen.then(|| winit::window::Fullscreen::Borderless(None)))
.with_fullscreen(*fullscreen)
.with_maximized(*maximized)
.with_resizable(*resizable)
.with_transparent(*transparent)
.with_window_icon(window_icon)
.with_active(*active)
// 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);
if let Some(icon_data) = icon_data {
viewport_builder =
viewport_builder.with_window_icon(egui::ColorImage::from_rgba_premultiplied(
[icon_data.width as usize, icon_data.height as usize],
&icon_data.rgba,
));
}
#[cfg(target_os = "macos")]
if *fullsize_content {
window_builder = window_builder
viewport_builder = viewport_builder
.with_title_hidden(true)
.with_titlebar_transparent(true)
.with_fullsize_content_view(true);
@@ -122,21 +115,20 @@ pub fn window_builder<E>(
#[cfg(all(feature = "wayland", target_os = "linux"))]
{
use winit::platform::wayland::WindowBuilderExtWayland as _;
match &native_options.app_id {
Some(app_id) => window_builder = window_builder.with_name(app_id, ""),
None => window_builder = window_builder.with_name(title, ""),
}
viewport_builder = match &native_options.app_id {
Some(app_id) => viewport_builder.with_name(app_id, ""),
None => viewport_builder.with_name(title, ""),
};
}
if let Some(min_size) = *min_window_size {
window_builder = window_builder.with_min_inner_size(points_to_size(min_size));
viewport_builder = viewport_builder.with_min_inner_size(min_size);
}
if let Some(max_size) = *max_window_size {
window_builder = window_builder.with_max_inner_size(points_to_size(max_size));
viewport_builder = viewport_builder.with_max_inner_size(max_size);
}
window_builder = window_builder_drag_and_drop(window_builder, *drag_and_drop_support);
viewport_builder = viewport_builder.with_drag_and_drop(*drag_and_drop_support);
// Always use the default window size / position on iOS. Trying to restore the previous position
// causes the window to be shown too small.
@@ -147,20 +139,17 @@ pub fn window_builder<E>(
window_settings.clamp_size_to_sane_values(largest_monitor_point_size(event_loop));
window_settings.clamp_position_to_monitors(event_loop);
window_builder = window_settings.initialize_window_builder(window_builder);
viewport_builder = window_settings.initialize_viewport_builder(viewport_builder);
window_settings.inner_size_points()
} else {
if let Some(pos) = *initial_window_pos {
window_builder = window_builder.with_position(winit::dpi::LogicalPosition {
x: pos.x as f64,
y: pos.y as f64,
});
viewport_builder = viewport_builder.with_position(pos);
}
if let Some(initial_window_size) = *initial_window_size {
let initial_window_size =
initial_window_size.at_most(largest_monitor_point_size(event_loop));
window_builder = window_builder.with_inner_size(points_to_size(initial_window_size));
viewport_builder = viewport_builder.with_inner_size(initial_window_size);
}
*initial_window_size
@@ -169,19 +158,19 @@ pub fn window_builder<E>(
#[cfg(not(target_os = "ios"))]
if *centered {
if let Some(monitor) = event_loop.available_monitors().next() {
let monitor_size = monitor.size().to_logical::<f64>(monitor.scale_factor());
let monitor_size = monitor.size().to_logical::<f32>(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 {
let x = (monitor_size.width - inner_size.x as f64) / 2.0;
let y = (monitor_size.height - inner_size.y as f64) / 2.0;
window_builder = window_builder.with_position(winit::dpi::LogicalPosition { x, y });
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]);
}
}
}
match std::mem::take(&mut native_options.window_builder) {
Some(hook) => hook(window_builder),
None => window_builder,
Some(hook) => hook(viewport_builder),
None => viewport_builder,
}
}
@@ -219,34 +208,14 @@ fn largest_monitor_point_size<E>(event_loop: &EventLoopWindowTarget<E>) -> egui:
}
}
fn load_icon(icon_data: epi::IconData) -> Option<winit::window::Icon> {
winit::window::Icon::from_rgba(icon_data.rgba, icon_data.width, icon_data.height).ok()
}
#[cfg(target_os = "windows")]
fn window_builder_drag_and_drop(
window_builder: winit::window::WindowBuilder,
enable: bool,
) -> winit::window::WindowBuilder {
use winit::platform::windows::WindowBuilderExtWindows as _;
window_builder.with_drag_and_drop(enable)
}
#[cfg(not(target_os = "windows"))]
fn window_builder_drag_and_drop(
window_builder: winit::window::WindowBuilder,
_enable: bool,
) -> winit::window::WindowBuilder {
// drag and drop can only be disabled on windows
window_builder
}
pub fn handle_app_output(
window: &winit::window::Window,
current_pixels_per_point: f32,
app_output: epi::backend::AppOutput,
window_state: &mut WindowState,
) {
crate::profile_function!();
let epi::backend::AppOutput {
close: _,
window_size,
@@ -294,7 +263,7 @@ pub fn handle_app_output(
}
if drag_window {
let _ = window.drag_window();
window.drag_window().ok();
}
if let Some(always_on_top) = always_on_top {
@@ -346,10 +315,11 @@ pub fn create_storage(_app_name: &str) -> Option<Box<dyn epi::Storage>> {
/// Everything needed to make a winit-based integration for [`epi`].
pub struct EpiIntegration {
pub frame: epi::Frame,
last_auto_save: std::time::Instant,
last_auto_save: Instant,
pub beginning: Instant,
pub frame_start: Instant,
pub egui_ctx: egui::Context,
pending_full_output: egui::FullOutput,
egui_winit: egui_winit::State,
/// When set, it is time to close the native window.
close: bool,
@@ -364,18 +334,18 @@ pub struct EpiIntegration {
impl EpiIntegration {
#[allow(clippy::too_many_arguments)]
pub fn new<E>(
event_loop: &EventLoopWindowTarget<E>,
max_texture_side: usize,
pub fn new(
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::sync::Arc<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);
@@ -408,10 +378,6 @@ impl EpiIntegration {
raw_window_handle: window.raw_window_handle(),
};
let mut egui_winit = egui_winit::State::new(event_loop);
egui_winit.set_max_texture_side(max_texture_side);
egui_winit.set_pixels_per_point(native_pixels_per_point);
let app_icon_setter = super::app_icon::AppTitleIconSetter::new(
app_name.to_owned(),
native_options.icon_data.clone(),
@@ -419,9 +385,8 @@ impl EpiIntegration {
Self {
frame,
last_auto_save: std::time::Instant::now(),
last_auto_save: Instant::now(),
egui_ctx,
egui_winit,
pending_full_output: Default::default(),
close: false,
can_drag_window: false,
@@ -430,34 +395,47 @@ impl EpiIntegration {
#[cfg(feature = "persistence")]
persist_window: native_options.persist_window,
app_icon_setter,
beginning: Instant::now(),
frame_start: Instant::now(),
}
}
#[cfg(feature = "accesskit")]
pub fn init_accesskit<E: From<accesskit_winit::ActionRequestEvent> + Send>(
pub fn init_accesskit<E: From<egui_winit::accesskit_winit::ActionRequestEvent> + Send>(
&mut self,
egui_winit: &mut egui_winit::State,
window: &winit::window::Window,
event_loop_proxy: winit::event_loop::EventLoopProxy<E>,
) {
crate::profile_function!();
let egui_ctx = self.egui_ctx.clone();
self.egui_winit
.init_accesskit(window, event_loop_proxy, move || {
// This function is called when an accessibility client
// (e.g. screen reader) makes its first request. If we got here,
// we know that an accessibility tree is actually wanted.
egui_ctx.enable_accesskit();
// Enqueue a repaint so we'll receive a full tree update soon.
egui_ctx.request_repaint();
egui_ctx.accesskit_placeholder_tree_update()
});
egui_winit.init_accesskit(window, event_loop_proxy, move || {
// This function is called when an accessibility client
// (e.g. screen reader) makes its first request. If we got here,
// we know that an accessibility tree is actually wanted.
egui_ctx.enable_accesskit();
// Enqueue a repaint so we'll receive a full tree update soon.
egui_ctx.request_repaint();
egui_ctx.accesskit_placeholder_tree_update()
});
}
pub fn warm_up(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
pub fn warm_up(
&mut self,
app: &mut dyn epi::App,
window: &winit::window::Window,
egui_winit: &mut egui_winit::State,
) {
crate::profile_function!();
let saved_memory: egui::Memory = self.egui_ctx.memory(|mem| mem.clone());
self.egui_ctx
.memory_mut(|mem| mem.set_everything_is_visible(true));
let full_output = self.update(app, window);
let raw_input = egui_winit.take_egui_input(window, ViewportIdPair::ROOT);
self.pre_update(window);
let full_output = self.update(app, None, raw_input);
self.post_update(app, window);
self.pending_full_output.append(full_output); // Handle it next frame
self.egui_ctx.memory_mut(|mem| *mem = saved_memory); // We don't want to remember that windows were huge.
self.egui_ctx.clear_animations();
@@ -472,6 +450,8 @@ impl EpiIntegration {
&mut self,
app: &mut dyn epi::App,
event: &winit::event::WindowEvent<'_>,
egui_winit: &mut egui_winit::State,
viewport_id: ViewportId,
) -> EventResponse {
crate::profile_function!();
@@ -480,7 +460,7 @@ impl EpiIntegration {
match event {
WindowEvent::CloseRequested => {
log::debug!("Received WindowEvent::CloseRequested");
self.close = app.on_close_event();
self.close = app.on_close_event() && viewport_id == ViewportId::ROOT;
log::debug!("App::on_close_event returned {}", self.close);
}
WindowEvent::Destroyed => {
@@ -503,37 +483,47 @@ impl EpiIntegration {
_ => {}
}
self.egui_winit.on_event(&self.egui_ctx, event)
egui_winit.on_event(&self.egui_ctx, event)
}
#[cfg(feature = "accesskit")]
pub fn on_accesskit_action_request(&mut self, request: accesskit::ActionRequest) {
self.egui_winit.on_accesskit_action_request(request);
}
pub fn update(
&mut self,
app: &mut dyn epi::App,
window: &winit::window::Window,
) -> egui::FullOutput {
let frame_start = std::time::Instant::now();
pub fn pre_update(&mut self, window: &winit::window::Window) {
self.frame_start = Instant::now();
self.app_icon_setter.update();
self.frame.info.window_info =
read_window_info(window, self.egui_ctx.pixels_per_point(), &self.window_state);
let raw_input = self.egui_winit.take_egui_input(window);
}
/// Run user code - this can create immediate viewports, so hold no locks over this!
///
/// If `viewport_ui_cb` is None, we are in the root viewport and will call [`crate::App::update`].
pub fn update(
&mut self,
app: &mut dyn epi::App,
viewport_ui_cb: Option<&DeferredViewportUiCallback>,
mut raw_input: egui::RawInput,
) -> egui::FullOutput {
raw_input.time = Some(self.beginning.elapsed().as_secs_f64());
// Run user code:
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
crate::profile_scope!("App::update");
app.update(egui_ctx, &mut self.frame);
if let Some(viewport_ui_cb) = viewport_ui_cb {
// Child viewport
crate::profile_scope!("viewport_callback");
viewport_ui_cb(egui_ctx);
} else {
// Root viewport
crate::profile_scope!("App::update");
app.update(egui_ctx, &mut self.frame);
}
});
self.pending_full_output.append(full_output);
let full_output = std::mem::take(&mut self.pending_full_output);
std::mem::take(&mut self.pending_full_output)
}
{
pub fn post_update(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
let app_output = {
let mut app_output = self.frame.take_app_output();
app_output.drag_window &= self.can_drag_window; // Necessary on Windows; see https://github.com/emilk/egui/pull/1108
self.can_drag_window = false;
@@ -546,29 +536,30 @@ impl EpiIntegration {
if self.frame.output.attention.is_some() {
self.frame.output.attention = None;
}
handle_app_output(
window,
self.egui_ctx.pixels_per_point(),
app_output,
&mut self.window_state,
);
}
app_output
};
let frame_time = frame_start.elapsed().as_secs_f64() as f32;
handle_app_output(
window,
self.egui_ctx.pixels_per_point(),
app_output,
&mut self.window_state,
);
let frame_time = self.frame_start.elapsed().as_secs_f64() as f32;
self.frame.info.cpu_usage = Some(frame_time);
full_output
}
pub fn post_rendering(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
crate::profile_function!();
let inner_size = window.inner_size();
let window_size_px = [inner_size.width, inner_size.height];
app.post_rendering(window_size_px, &self.frame);
}
pub fn post_present(&mut self, window: &winit::window::Window) {
if let Some(visible) = self.frame.output.visible.take() {
crate::profile_scope!("window.set_visible");
window.set_visible(visible);
}
}
@@ -576,19 +567,24 @@ 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,
) {
self.egui_winit
.handle_platform_output(window, &self.egui_ctx, platform_output);
egui_winit.handle_platform_output(window, viewport_id, &self.egui_ctx, platform_output);
}
// ------------------------------------------------------------------------
// Persistence stuff:
pub fn maybe_autosave(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
let now = std::time::Instant::now();
pub fn maybe_autosave(
&mut self,
app: &mut dyn epi::App,
window: Option<&winit::window::Window>,
) {
let now = Instant::now();
if now - self.last_auto_save > app.auto_save_interval() {
self.save(app, Some(window));
self.save(app, window);
self.last_auto_save = now;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -94,7 +94,7 @@ impl AppRunner {
{
let needs_repaint = needs_repaint.clone();
egui_ctx.set_request_repaint_callback(move |info| {
needs_repaint.repaint_after(info.after.as_secs_f64());
needs_repaint.repaint_after(info.delay.as_secs_f64());
});
}
@@ -170,10 +170,8 @@ impl AppRunner {
self.painter.destroy();
}
/// Returns how long to wait until the next repaint.
///
/// Call [`Self::paint`] later to paint
pub fn logic(&mut self) -> (std::time::Duration, Vec<egui::ClippedPrimitive>) {
pub fn logic(&mut self) -> Vec<egui::ClippedPrimitive> {
let frame_start = now_sec();
super::resize_canvas_to_screen_size(self.canvas_id(), self.web_options.max_size_points);
@@ -185,14 +183,20 @@ impl AppRunner {
});
let egui::FullOutput {
platform_output,
repaint_after,
textures_delta,
shapes,
pixels_per_point,
viewport_output,
} = full_output;
if viewport_output.len() > 1 {
log::warn!("Multiple viewports not yet supported on the web");
}
// TODO(emilk): handle some of the command in `viewport_output`, like setting the title and icon?
self.handle_platform_output(platform_output);
self.textures_delta.append(textures_delta);
let clipped_primitives = self.egui_ctx.tessellate(shapes);
let clipped_primitives = self.egui_ctx.tessellate(shapes, pixels_per_point);
{
let app_output = self.frame.take_app_output();
@@ -201,7 +205,7 @@ impl AppRunner {
self.frame.info.cpu_usage = Some((now_sec() - frame_start) as f32);
(repaint_after, clipped_primitives)
clipped_primitives
}
/// Paint the results of the last call to [`Self::logic`].

View File

@@ -19,11 +19,8 @@ fn paint_and_schedule(runner_ref: &WebRunner) -> Result<(), JsValue> {
fn paint_if_needed(runner: &mut AppRunner) -> Result<(), JsValue> {
if runner.needs_repaint.when_to_repaint() <= now_sec() {
runner.needs_repaint.clear();
let (repaint_after, clipped_primitives) = runner.logic();
let clipped_primitives = runner.logic();
runner.paint(&clipped_primitives)?;
runner
.needs_repaint
.repaint_after(repaint_after.as_secs_f64());
runner.auto_save_if_needed();
}
Ok(())

View File

@@ -208,8 +208,7 @@ impl WebPainter for WebPainterWgpu {
let frame = match self.surface.get_current_texture() {
Ok(frame) => frame,
#[allow(clippy::single_match_else)]
Err(e) => match (*self.on_surface_error)(e) {
Err(err) => match (*self.on_surface_error)(err) {
SurfaceErrorAction::RecreateSurface => {
self.surface
.configure(&render_state.device, &self.surface_configuration);
@@ -271,11 +270,9 @@ impl WebPainter for WebPainterWgpu {
}
// Submit the commands: both the main buffer and user-defined ones.
render_state.queue.submit(
user_cmd_bufs
.into_iter()
.chain(std::iter::once(encoder.finish())),
);
render_state
.queue
.submit(user_cmd_bufs.into_iter().chain([encoder.finish()]));
if let Some(frame) = frame {
frame.present();