Introduce egui::FullOutput, returned from Context::run (#1292)
* Introduce `egui::FullOutput`, returned from `Context::run` * Rename `Output` to `PlatformOutput`
This commit is contained in:
parent
c5a9421dbd
commit
31d324932c
17 changed files with 204 additions and 144 deletions
|
@ -43,7 +43,8 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
|
||||||
* Renamed `Ui::visible` to `Ui::is_visible`.
|
* Renamed `Ui::visible` to `Ui::is_visible`.
|
||||||
* Split `Event::Text` into `Event::Text` and `Event::Paste` ([#1058](https://github.com/emilk/egui/pull/1058)).
|
* Split `Event::Text` into `Event::Text` and `Event::Paste` ([#1058](https://github.com/emilk/egui/pull/1058)).
|
||||||
* For integrations:
|
* For integrations:
|
||||||
* `FontImage` has been replaced by `TexturesDelta` (found in `Output`), describing what textures were loaded and freed each frame ([#1110](https://github.com/emilk/egui/pull/1110)).
|
* `Output` has now been renamed `PlatformOutput` and `Context::run` now returns the new `FullOutput` ([#1292](https://github.com/emilk/egui/pull/1292)).
|
||||||
|
* `FontImage` has been replaced by `TexturesDelta` (found in `FullOutput`), describing what textures were loaded and freed each frame ([#1110](https://github.com/emilk/egui/pull/1110)).
|
||||||
* The painter must support partial texture updates ([#1149](https://github.com/emilk/egui/pull/1149)).
|
* The painter must support partial texture updates ([#1149](https://github.com/emilk/egui/pull/1149)).
|
||||||
* Added `RawInput::max_texture_side` which should be filled in with e.g. `GL_MAX_TEXTURE_SIZE` ([#1154](https://github.com/emilk/egui/pull/1154)).
|
* Added `RawInput::max_texture_side` which should be filled in with e.g. `GL_MAX_TEXTURE_SIZE` ([#1154](https://github.com/emilk/egui/pull/1154)).
|
||||||
* Replaced `Style::body_text_style` with more generic `Style::text_styles` ([#1154](https://github.com/emilk/egui/pull/1154)).
|
* Replaced `Style::body_text_style` with more generic `Style::text_styles` ([#1154](https://github.com/emilk/egui/pull/1154)).
|
||||||
|
|
19
README.md
19
README.md
|
@ -192,7 +192,7 @@ Missing an integration for the thing you're working on? Create one, it's easy!
|
||||||
|
|
||||||
### Writing your own egui integration
|
### Writing your own egui integration
|
||||||
|
|
||||||
You need to collect [`egui::RawInput`](https://docs.rs/egui/latest/egui/struct.RawInput.html), paint [`egui::ClippedMesh`](https://docs.rs/epaint/latest/epaint/struct.ClippedMesh.html):es and handle [`egui::Output`](https://docs.rs/egui/latest/egui/struct.Output.html). The basic structure is this:
|
You need to collect [`egui::RawInput`](https://docs.rs/egui/latest/egui/struct.RawInput.html) and handle [`egui::FullOutput`](https://docs.rs/egui/latest/egui/struct.FullOutput.html). The basic structure is this:
|
||||||
|
|
||||||
``` rust
|
``` rust
|
||||||
let mut egui_ctx = egui::CtxRef::default();
|
let mut egui_ctx = egui::CtxRef::default();
|
||||||
|
@ -201,20 +201,19 @@ let mut egui_ctx = egui::CtxRef::default();
|
||||||
loop {
|
loop {
|
||||||
// Gather input (mouse, touches, keyboard, screen size, etc):
|
// Gather input (mouse, touches, keyboard, screen size, etc):
|
||||||
let raw_input: egui::RawInput = my_integration.gather_input();
|
let raw_input: egui::RawInput = my_integration.gather_input();
|
||||||
let (output, shapes) = egui_ctx.run(raw_input, |egui_ctx| {
|
let full_output = egui_ctx.run(raw_input, |egui_ctx| {
|
||||||
my_app.ui(egui_ctx); // add panels, windows and widgets to `egui_ctx` here
|
my_app.ui(egui_ctx); // add panels, windows and widgets to `egui_ctx` here
|
||||||
});
|
});
|
||||||
let clipped_meshes = egui_ctx.tessellate(shapes); // creates triangles to paint
|
let clipped_meshes = egui_ctx.tessellate(full_output.shapes); // creates triangles to paint
|
||||||
|
|
||||||
my_integration.set_egui_textures(&output.textures_delta.set);
|
my_integration.paint(&full_output.textures_delta, clipped_meshes);
|
||||||
my_integration.paint(clipped_meshes);
|
|
||||||
my_integration.free_egui_textures(&output.textures_delta.free);
|
|
||||||
|
|
||||||
my_integration.set_cursor_icon(output.cursor_icon);
|
let platform_output = full_output.platform_output;
|
||||||
if !output.copied_text.is_empty() {
|
my_integration.set_cursor_icon(platform_output.cursor_icon);
|
||||||
my_integration.set_clipboard_text(output.copied_text);
|
if !platform_output.copied_text.is_empty() {
|
||||||
|
my_integration.set_clipboard_text(platform_output.copied_text);
|
||||||
}
|
}
|
||||||
// See `egui::Output` for more
|
// See `egui::FullOutput` and `egui::PlatformOutput` for more
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
use egui::Vec2;
|
pub fn points_to_size(points: egui::Vec2) -> winit::dpi::LogicalSize<f64> {
|
||||||
use winit::dpi::LogicalSize;
|
|
||||||
|
|
||||||
pub fn points_to_size(points: Vec2) -> LogicalSize<f64> {
|
|
||||||
winit::dpi::LogicalSize {
|
winit::dpi::LogicalSize {
|
||||||
width: points.x as f64,
|
width: points.x as f64,
|
||||||
height: points.y as f64,
|
height: points.y as f64,
|
||||||
|
@ -222,6 +219,7 @@ pub struct EpiIntegration {
|
||||||
frame: epi::Frame,
|
frame: epi::Frame,
|
||||||
persistence: crate::epi::Persistence,
|
persistence: crate::epi::Persistence,
|
||||||
pub egui_ctx: egui::Context,
|
pub egui_ctx: egui::Context,
|
||||||
|
pending_full_output: egui::FullOutput,
|
||||||
egui_winit: crate::State,
|
egui_winit: crate::State,
|
||||||
pub app: Box<dyn epi::App>,
|
pub app: Box<dyn epi::App>,
|
||||||
/// When set, it is time to quit
|
/// When set, it is time to quit
|
||||||
|
@ -267,6 +265,7 @@ impl EpiIntegration {
|
||||||
persistence,
|
persistence,
|
||||||
egui_ctx,
|
egui_ctx,
|
||||||
egui_winit: crate::State::new(max_texture_side, window),
|
egui_winit: crate::State::new(max_texture_side, window),
|
||||||
|
pending_full_output: Default::default(),
|
||||||
app,
|
app,
|
||||||
quit: false,
|
quit: false,
|
||||||
can_drag_window: false,
|
can_drag_window: false,
|
||||||
|
@ -295,8 +294,8 @@ impl EpiIntegration {
|
||||||
fn warm_up(&mut self, window: &winit::window::Window) {
|
fn warm_up(&mut self, window: &winit::window::Window) {
|
||||||
let saved_memory: egui::Memory = self.egui_ctx.memory().clone();
|
let saved_memory: egui::Memory = self.egui_ctx.memory().clone();
|
||||||
self.egui_ctx.memory().set_everything_is_visible(true);
|
self.egui_ctx.memory().set_everything_is_visible(true);
|
||||||
let (_, textures_delta, _) = self.update(window);
|
let full_output = self.update(window);
|
||||||
self.egui_ctx.output().textures_delta = textures_delta; // Handle it next frame
|
self.pending_full_output.append(full_output); // Handle it next frame
|
||||||
*self.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge.
|
*self.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge.
|
||||||
self.egui_ctx.clear_animations();
|
self.egui_ctx.clear_animations();
|
||||||
}
|
}
|
||||||
|
@ -323,37 +322,39 @@ impl EpiIntegration {
|
||||||
self.egui_winit.on_event(&self.egui_ctx, event);
|
self.egui_winit.on_event(&self.egui_ctx, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `needs_repaint` and shapes to paint.
|
pub fn update(&mut self, window: &winit::window::Window) -> egui::FullOutput {
|
||||||
pub fn update(
|
|
||||||
&mut self,
|
|
||||||
window: &winit::window::Window,
|
|
||||||
) -> (bool, egui::TexturesDelta, Vec<egui::epaint::ClippedShape>) {
|
|
||||||
let frame_start = instant::Instant::now();
|
let frame_start = instant::Instant::now();
|
||||||
|
|
||||||
let raw_input = self.egui_winit.take_egui_input(window);
|
let raw_input = self.egui_winit.take_egui_input(window);
|
||||||
let (egui_output, shapes) = self.egui_ctx.run(raw_input, |egui_ctx| {
|
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
|
||||||
self.app.update(egui_ctx, &self.frame);
|
self.app.update(egui_ctx, &self.frame);
|
||||||
});
|
});
|
||||||
|
self.pending_full_output.append(full_output);
|
||||||
|
let full_output = std::mem::take(&mut self.pending_full_output);
|
||||||
|
|
||||||
let needs_repaint = egui_output.needs_repaint;
|
{
|
||||||
let textures_delta = self
|
let mut app_output = self.frame.take_app_output();
|
||||||
.egui_winit
|
app_output.drag_window &= self.can_drag_window; // Necessary on Windows; see https://github.com/emilk/egui/pull/1108
|
||||||
.handle_output(window, &self.egui_ctx, egui_output);
|
self.can_drag_window = false;
|
||||||
|
if app_output.quit {
|
||||||
let mut app_output = self.frame.take_app_output();
|
self.quit = self.app.on_exit_event();
|
||||||
app_output.drag_window &= self.can_drag_window; // Necessary on Windows; see https://github.com/emilk/egui/pull/1108
|
}
|
||||||
self.can_drag_window = false;
|
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
||||||
|
|
||||||
if app_output.quit {
|
|
||||||
self.quit = self.app.on_exit_event();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
|
||||||
|
|
||||||
let frame_time = (instant::Instant::now() - frame_start).as_secs_f64() as f32;
|
let frame_time = (instant::Instant::now() - frame_start).as_secs_f64() as f32;
|
||||||
self.frame.lock().info.cpu_usage = Some(frame_time);
|
self.frame.lock().info.cpu_usage = Some(frame_time);
|
||||||
|
|
||||||
(needs_repaint, textures_delta, shapes)
|
full_output
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_platform_output(
|
||||||
|
&mut self,
|
||||||
|
window: &winit::window::Window,
|
||||||
|
platform_output: egui::PlatformOutput,
|
||||||
|
) {
|
||||||
|
self.egui_winit
|
||||||
|
.handle_platform_output(window, &self.egui_ctx, platform_output);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn maybe_autosave(&mut self, window: &winit::window::Window) {
|
pub fn maybe_autosave(&mut self, window: &winit::window::Window) {
|
||||||
|
|
|
@ -514,26 +514,25 @@ impl State {
|
||||||
/// * open any clicked urls
|
/// * open any clicked urls
|
||||||
/// * update the IME
|
/// * update the IME
|
||||||
/// *
|
/// *
|
||||||
pub fn handle_output(
|
pub fn handle_platform_output(
|
||||||
&mut self,
|
&mut self,
|
||||||
window: &winit::window::Window,
|
window: &winit::window::Window,
|
||||||
egui_ctx: &egui::Context,
|
egui_ctx: &egui::Context,
|
||||||
output: egui::Output,
|
platform_output: egui::PlatformOutput,
|
||||||
) -> egui::TexturesDelta {
|
) {
|
||||||
if egui_ctx.options().screen_reader {
|
if egui_ctx.options().screen_reader {
|
||||||
self.screen_reader.speak(&output.events_description());
|
self.screen_reader
|
||||||
|
.speak(&platform_output.events_description());
|
||||||
}
|
}
|
||||||
|
|
||||||
let egui::Output {
|
let egui::PlatformOutput {
|
||||||
cursor_icon,
|
cursor_icon,
|
||||||
open_url,
|
open_url,
|
||||||
copied_text,
|
copied_text,
|
||||||
needs_repaint: _, // needs to be handled elsewhere
|
|
||||||
events: _, // handled above
|
events: _, // handled above
|
||||||
mutable_text_under_cursor: _, // only used in egui_web
|
mutable_text_under_cursor: _, // only used in egui_web
|
||||||
text_cursor_pos,
|
text_cursor_pos,
|
||||||
textures_delta,
|
} = platform_output;
|
||||||
} = output;
|
|
||||||
|
|
||||||
self.current_pixels_per_point = egui_ctx.pixels_per_point(); // someone can have changed it to scale the UI
|
self.current_pixels_per_point = egui_ctx.pixels_per_point(); // someone can have changed it to scale the UI
|
||||||
|
|
||||||
|
@ -550,8 +549,6 @@ impl State {
|
||||||
if let Some(egui::Pos2 { x, y }) = text_cursor_pos {
|
if let Some(egui::Pos2 { x, y }) = text_cursor_pos {
|
||||||
window.set_ime_position(winit::dpi::LogicalPosition { x, y });
|
window.set_ime_position(winit::dpi::LogicalPosition { x, y });
|
||||||
}
|
}
|
||||||
|
|
||||||
textures_delta
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_cursor_icon(&mut self, window: &winit::window::Window, cursor_icon: egui::CursorIcon) {
|
fn set_cursor_icon(&mut self, window: &winit::window::Window, cursor_icon: egui::CursorIcon) {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
// #![warn(missing_docs)]
|
// #![warn(missing_docs)]
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
animation_manager::AnimationManager, data::output::Output, frame_state::FrameState,
|
animation_manager::AnimationManager, data::output::PlatformOutput, frame_state::FrameState,
|
||||||
input_state::*, layers::GraphicLayers, memory::Options, TextureHandle, *,
|
input_state::*, layers::GraphicLayers, memory::Options, output::FullOutput, TextureHandle, *,
|
||||||
};
|
};
|
||||||
use epaint::{mutex::*, stats::*, text::Fonts, TessellationOptions, *};
|
use epaint::{mutex::*, stats::*, text::Fonts, TessellationOptions, *};
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ struct ContextImpl {
|
||||||
|
|
||||||
// The output of a frame:
|
// The output of a frame:
|
||||||
graphics: GraphicLayers,
|
graphics: GraphicLayers,
|
||||||
output: Output,
|
output: PlatformOutput,
|
||||||
|
|
||||||
paint_stats: PaintStats,
|
paint_stats: PaintStats,
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ impl ContextImpl {
|
||||||
/// Your handle to egui.
|
/// Your handle to egui.
|
||||||
///
|
///
|
||||||
/// This is the first thing you need when working with egui.
|
/// This is the first thing you need when working with egui.
|
||||||
/// Contains the [`InputState`], [`Memory`], [`Output`], and more.
|
/// Contains the [`InputState`], [`Memory`], [`PlatformOutput`], and more.
|
||||||
///
|
///
|
||||||
/// [`Context`] is cheap to clone, and any clones refers to the same mutable data
|
/// [`Context`] is cheap to clone, and any clones refers to the same mutable data
|
||||||
/// ([`Context`] uses refcounting internally).
|
/// ([`Context`] uses refcounting internally).
|
||||||
|
@ -121,14 +121,14 @@ impl ContextImpl {
|
||||||
/// # Example:
|
/// # Example:
|
||||||
///
|
///
|
||||||
/// ``` no_run
|
/// ``` no_run
|
||||||
/// # fn handle_output(_: egui::Output) {}
|
/// # fn handle_platform_output(_: egui::PlatformOutput) {}
|
||||||
/// # fn paint(_: Vec<egui::ClippedMesh>) {}
|
/// # fn paint(textures_detla: egui::TexturesDelta, _: Vec<egui::ClippedMesh>) {}
|
||||||
/// let mut ctx = egui::Context::default();
|
/// let mut ctx = egui::Context::default();
|
||||||
///
|
///
|
||||||
/// // Game loop:
|
/// // Game loop:
|
||||||
/// loop {
|
/// loop {
|
||||||
/// let raw_input = egui::RawInput::default();
|
/// let raw_input = egui::RawInput::default();
|
||||||
/// let (output, shapes) = ctx.run(raw_input, |ctx| {
|
/// let full_output = ctx.run(raw_input, |ctx| {
|
||||||
/// egui::CentralPanel::default().show(&ctx, |ui| {
|
/// egui::CentralPanel::default().show(&ctx, |ui| {
|
||||||
/// ui.label("Hello world!");
|
/// ui.label("Hello world!");
|
||||||
/// if ui.button("Click me").clicked() {
|
/// if ui.button("Click me").clicked() {
|
||||||
|
@ -136,9 +136,9 @@ impl ContextImpl {
|
||||||
/// }
|
/// }
|
||||||
/// });
|
/// });
|
||||||
/// });
|
/// });
|
||||||
/// let clipped_meshes = ctx.tessellate(shapes); // create triangles to paint
|
/// handle_platform_output(full_output.platform_output);
|
||||||
/// handle_output(output);
|
/// let clipped_meshes = ctx.tessellate(full_output.shapes); // create triangles to paint
|
||||||
/// paint(clipped_meshes);
|
/// paint(full_output.textures_delta, clipped_meshes);
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -185,19 +185,15 @@ impl Context {
|
||||||
///
|
///
|
||||||
/// // Each frame:
|
/// // Each frame:
|
||||||
/// let input = egui::RawInput::default();
|
/// let input = egui::RawInput::default();
|
||||||
/// let (output, shapes) = ctx.run(input, |ctx| {
|
/// let full_output = ctx.run(input, |ctx| {
|
||||||
/// egui::CentralPanel::default().show(&ctx, |ui| {
|
/// egui::CentralPanel::default().show(&ctx, |ui| {
|
||||||
/// ui.label("Hello egui!");
|
/// ui.label("Hello egui!");
|
||||||
/// });
|
/// });
|
||||||
/// });
|
/// });
|
||||||
/// // handle output, paint shapes
|
/// // handle full_output
|
||||||
/// ```
|
/// ```
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn run(
|
pub fn run(&self, new_input: RawInput, run_ui: impl FnOnce(&Context)) -> FullOutput {
|
||||||
&self,
|
|
||||||
new_input: RawInput,
|
|
||||||
run_ui: impl FnOnce(&Context),
|
|
||||||
) -> (Output, Vec<ClippedShape>) {
|
|
||||||
self.begin_frame(new_input);
|
self.begin_frame(new_input);
|
||||||
run_ui(self);
|
run_ui(self);
|
||||||
self.end_frame()
|
self.end_frame()
|
||||||
|
@ -217,8 +213,8 @@ impl Context {
|
||||||
/// ui.label("Hello egui!");
|
/// ui.label("Hello egui!");
|
||||||
/// });
|
/// });
|
||||||
///
|
///
|
||||||
/// let (output, shapes) = ctx.end_frame();
|
/// let full_output = ctx.end_frame();
|
||||||
/// // handle output, paint shapes
|
/// // handle full_output
|
||||||
/// ```
|
/// ```
|
||||||
pub fn begin_frame(&self, new_input: RawInput) {
|
pub fn begin_frame(&self, new_input: RawInput) {
|
||||||
self.write().begin_frame_mut(new_input);
|
self.write().begin_frame_mut(new_input);
|
||||||
|
@ -463,8 +459,13 @@ impl Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What egui outputs each frame.
|
/// What egui outputs each frame.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # let mut ctx = egui::Context::default();
|
||||||
|
/// ctx.output().cursor_icon = egui::CursorIcon::Progress;
|
||||||
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn output(&self) -> RwLockWriteGuard<'_, Output> {
|
pub fn output(&self) -> RwLockWriteGuard<'_, PlatformOutput> {
|
||||||
RwLockWriteGuard::map(self.write(), |c| &mut c.output)
|
RwLockWriteGuard::map(self.write(), |c| &mut c.output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -719,14 +720,13 @@ impl Context {
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
/// Call at the end of each frame.
|
/// Call at the end of each frame.
|
||||||
/// Returns what has happened this frame [`crate::Output`] as well as what you need to paint.
|
|
||||||
/// You can transform the returned shapes into triangles with a call to [`Context::tessellate`].
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn end_frame(&self) -> (Output, Vec<ClippedShape>) {
|
pub fn end_frame(&self) -> FullOutput {
|
||||||
if self.input().wants_repaint() {
|
if self.input().wants_repaint() {
|
||||||
self.request_repaint();
|
self.request_repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let textures_delta;
|
||||||
{
|
{
|
||||||
let ctx_impl = &mut *self.write();
|
let ctx_impl = &mut *self.write();
|
||||||
ctx_impl
|
ctx_impl
|
||||||
|
@ -742,20 +742,26 @@ impl Context {
|
||||||
.set(TextureId::default(), font_image_delta);
|
.set(TextureId::default(), font_image_delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx_impl
|
textures_delta = ctx_impl.tex_manager.0.write().take_delta();
|
||||||
.output
|
};
|
||||||
.textures_delta
|
|
||||||
.append(ctx_impl.tex_manager.0.write().take_delta());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut output: Output = std::mem::take(&mut self.output());
|
let platform_output: PlatformOutput = std::mem::take(&mut self.output());
|
||||||
if self.read().repaint_requests > 0 {
|
|
||||||
|
let needs_repaint = if self.read().repaint_requests > 0 {
|
||||||
self.write().repaint_requests -= 1;
|
self.write().repaint_requests -= 1;
|
||||||
output.needs_repaint = true;
|
true
|
||||||
}
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
let shapes = self.drain_paint_lists();
|
let shapes = self.drain_paint_lists();
|
||||||
(output, shapes)
|
|
||||||
|
FullOutput {
|
||||||
|
platform_output,
|
||||||
|
needs_repaint,
|
||||||
|
textures_delta,
|
||||||
|
shapes,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drain_paint_lists(&self) -> Vec<ClippedShape> {
|
fn drain_paint_lists(&self) -> Vec<ClippedShape> {
|
||||||
|
|
|
@ -2,11 +2,56 @@
|
||||||
|
|
||||||
use crate::WidgetType;
|
use crate::WidgetType;
|
||||||
|
|
||||||
/// What egui emits each frame.
|
/// What egui emits each frame from [`crate::Context::run`].
|
||||||
|
///
|
||||||
|
/// The backend should use this.
|
||||||
|
#[derive(Clone, Default, PartialEq)]
|
||||||
|
pub struct FullOutput {
|
||||||
|
/// Non-rendering related output.
|
||||||
|
pub platform_output: PlatformOutput,
|
||||||
|
|
||||||
|
/// If `true`, egui is requesting immediate repaint (i.e. on the next frame).
|
||||||
|
///
|
||||||
|
/// This happens for instance when there is an animation, or if a user has called `Context::request_repaint()`.
|
||||||
|
pub needs_repaint: bool,
|
||||||
|
|
||||||
|
/// Texture changes since last frame (including the font texture).
|
||||||
|
///
|
||||||
|
/// The backend needs to apply [`crate::TexturesDelta::set`] _before_ painting,
|
||||||
|
/// and free any texture in [`crate::TexturesDelta::free`] _after_ painting.
|
||||||
|
pub textures_delta: epaint::textures::TexturesDelta,
|
||||||
|
|
||||||
|
/// What to paint.
|
||||||
|
///
|
||||||
|
/// You can use [`crate::Context::tessellate`] to turn this into triangles.
|
||||||
|
pub shapes: Vec<epaint::ClippedShape>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FullOutput {
|
||||||
|
/// Add on new output.
|
||||||
|
pub fn append(&mut self, newer: Self) {
|
||||||
|
let Self {
|
||||||
|
platform_output,
|
||||||
|
needs_repaint,
|
||||||
|
textures_delta,
|
||||||
|
shapes,
|
||||||
|
} = newer;
|
||||||
|
|
||||||
|
self.platform_output.append(platform_output);
|
||||||
|
self.needs_repaint = needs_repaint; // if the last frame doesn't need a repaint, then we don't need to repaint
|
||||||
|
self.textures_delta.append(textures_delta);
|
||||||
|
self.shapes = shapes; // Only paint the latest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The non-rendering part of what egui emits each frame.
|
||||||
|
///
|
||||||
|
/// You can access (and modify) this with [`crate::Context::output`].
|
||||||
|
///
|
||||||
/// The backend should use this.
|
/// The backend should use this.
|
||||||
#[derive(Clone, Default, PartialEq)]
|
#[derive(Clone, Default, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct Output {
|
pub struct PlatformOutput {
|
||||||
/// Set the cursor to this icon.
|
/// Set the cursor to this icon.
|
||||||
pub cursor_icon: CursorIcon,
|
pub cursor_icon: CursorIcon,
|
||||||
|
|
||||||
|
@ -18,14 +63,6 @@ pub struct Output {
|
||||||
/// This is often a response to [`crate::Event::Copy`] or [`crate::Event::Cut`].
|
/// This is often a response to [`crate::Event::Copy`] or [`crate::Event::Cut`].
|
||||||
pub copied_text: String,
|
pub copied_text: String,
|
||||||
|
|
||||||
/// If `true`, egui is requesting immediate repaint (i.e. on the next frame).
|
|
||||||
///
|
|
||||||
/// This happens for instance when there is an animation, or if a user has called `Context::request_repaint()`.
|
|
||||||
///
|
|
||||||
/// As an egui user: don't set this value directly.
|
|
||||||
/// Call `Context::request_repaint()` instead and it will do so for you.
|
|
||||||
pub needs_repaint: bool,
|
|
||||||
|
|
||||||
/// Events that may be useful to e.g. a screen reader.
|
/// Events that may be useful to e.g. a screen reader.
|
||||||
pub events: Vec<OutputEvent>,
|
pub events: Vec<OutputEvent>,
|
||||||
|
|
||||||
|
@ -35,12 +72,9 @@ pub struct Output {
|
||||||
|
|
||||||
/// Screen-space position of text edit cursor (used for IME).
|
/// Screen-space position of text edit cursor (used for IME).
|
||||||
pub text_cursor_pos: Option<crate::Pos2>,
|
pub text_cursor_pos: Option<crate::Pos2>,
|
||||||
|
|
||||||
/// Texture changes since last frame.
|
|
||||||
pub textures_delta: epaint::textures::TexturesDelta,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Output {
|
impl PlatformOutput {
|
||||||
/// Open the given url in a web browser.
|
/// Open the given url in a web browser.
|
||||||
/// If egui is running in a browser, the same tab will be reused.
|
/// If egui is running in a browser, the same tab will be reused.
|
||||||
pub fn open_url(&mut self, url: impl ToString) {
|
pub fn open_url(&mut self, url: impl ToString) {
|
||||||
|
@ -70,11 +104,9 @@ impl Output {
|
||||||
cursor_icon,
|
cursor_icon,
|
||||||
open_url,
|
open_url,
|
||||||
copied_text,
|
copied_text,
|
||||||
needs_repaint,
|
|
||||||
mut events,
|
mut events,
|
||||||
mutable_text_under_cursor,
|
mutable_text_under_cursor,
|
||||||
text_cursor_pos,
|
text_cursor_pos,
|
||||||
textures_delta,
|
|
||||||
} = newer;
|
} = newer;
|
||||||
|
|
||||||
self.cursor_icon = cursor_icon;
|
self.cursor_icon = cursor_icon;
|
||||||
|
@ -84,11 +116,9 @@ impl Output {
|
||||||
if !copied_text.is_empty() {
|
if !copied_text.is_empty() {
|
||||||
self.copied_text = copied_text;
|
self.copied_text = copied_text;
|
||||||
}
|
}
|
||||||
self.needs_repaint = needs_repaint; // if the last frame doesn't need a repaint, then we don't need to repaint
|
|
||||||
self.events.append(&mut events);
|
self.events.append(&mut events);
|
||||||
self.mutable_text_under_cursor = mutable_text_under_cursor;
|
self.mutable_text_under_cursor = mutable_text_under_cursor;
|
||||||
self.text_cursor_pos = text_cursor_pos.or(self.text_cursor_pos);
|
self.text_cursor_pos = text_cursor_pos.or(self.text_cursor_pos);
|
||||||
self.textures_delta.append(textures_delta);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Take everything ephemeral (everything except `cursor_icon` currently)
|
/// Take everything ephemeral (everything except `cursor_icon` currently)
|
||||||
|
@ -129,7 +159,7 @@ impl OpenUrl {
|
||||||
|
|
||||||
/// A mouse cursor icon.
|
/// A mouse cursor icon.
|
||||||
///
|
///
|
||||||
/// egui emits a [`CursorIcon`] in [`Output`] each frame as a request to the integration.
|
/// egui emits a [`CursorIcon`] in [`PlatformOutput`] each frame as a request to the integration.
|
||||||
///
|
///
|
||||||
/// Loosely based on <https://developer.mozilla.org/en-US/docs/Web/CSS/cursor>.
|
/// Loosely based on <https://developer.mozilla.org/en-US/docs/Web/CSS/cursor>.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
|
|
@ -110,16 +110,16 @@
|
||||||
//! To write your own integration for egui you need to do this:
|
//! To write your own integration for egui you need to do this:
|
||||||
//!
|
//!
|
||||||
//! ``` no_run
|
//! ``` no_run
|
||||||
//! # fn handle_output(_: egui::Output) {}
|
//! # fn handle_platform_output(_: egui::PlatformOutput) {}
|
||||||
//! # fn paint(_: Vec<egui::ClippedMesh>) {}
|
|
||||||
//! # fn gather_input() -> egui::RawInput { egui::RawInput::default() }
|
//! # fn gather_input() -> egui::RawInput { egui::RawInput::default() }
|
||||||
|
//! # fn paint(textures_detla: egui::TexturesDelta, _: Vec<egui::ClippedMesh>) {}
|
||||||
//! let mut ctx = egui::Context::default();
|
//! let mut ctx = egui::Context::default();
|
||||||
//!
|
//!
|
||||||
//! // Game loop:
|
//! // Game loop:
|
||||||
//! loop {
|
//! loop {
|
||||||
//! let raw_input: egui::RawInput = gather_input();
|
//! let raw_input: egui::RawInput = gather_input();
|
||||||
//!
|
//!
|
||||||
//! let (output, shapes) = ctx.run(raw_input, |ctx| {
|
//! let full_output = ctx.run(raw_input, |ctx| {
|
||||||
//! egui::CentralPanel::default().show(&ctx, |ui| {
|
//! egui::CentralPanel::default().show(&ctx, |ui| {
|
||||||
//! ui.label("Hello world!");
|
//! ui.label("Hello world!");
|
||||||
//! if ui.button("Click me").clicked() {
|
//! if ui.button("Click me").clicked() {
|
||||||
|
@ -127,10 +127,9 @@
|
||||||
//! }
|
//! }
|
||||||
//! });
|
//! });
|
||||||
//! });
|
//! });
|
||||||
//!
|
//! handle_platform_output(full_output.platform_output);
|
||||||
//! let clipped_meshes = ctx.tessellate(shapes); // create triangles to paint
|
//! let clipped_meshes = ctx.tessellate(full_output.shapes); // create triangles to paint
|
||||||
//! handle_output(output);
|
//! paint(full_output.textures_delta, clipped_meshes);
|
||||||
//! paint(clipped_meshes);
|
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
@ -403,7 +402,7 @@ pub use {
|
||||||
context::Context,
|
context::Context,
|
||||||
data::{
|
data::{
|
||||||
input::*,
|
input::*,
|
||||||
output::{self, CursorIcon, Output, WidgetInfo},
|
output::{self, CursorIcon, FullOutput, PlatformOutput, WidgetInfo},
|
||||||
},
|
},
|
||||||
grid::Grid,
|
grid::Grid,
|
||||||
id::{Id, IdMap},
|
id::{Id, IdMap},
|
||||||
|
|
|
@ -105,7 +105,7 @@ pub struct Options {
|
||||||
pub tessellation_options: epaint::TessellationOptions,
|
pub tessellation_options: epaint::TessellationOptions,
|
||||||
|
|
||||||
/// This does not at all change the behavior of egui,
|
/// This does not at all change the behavior of egui,
|
||||||
/// but is a signal to any backend that we want the [`crate::Output::events`] read out loud.
|
/// but is a signal to any backend that we want the [`crate::PlatformOutput::events`] read out loud.
|
||||||
/// Screen readers is an experimental feature of egui, and not supported on all platforms.
|
/// Screen readers is an experimental feature of egui, and not supported on all platforms.
|
||||||
pub screen_reader: bool,
|
pub screen_reader: bool,
|
||||||
|
|
||||||
|
|
|
@ -366,10 +366,10 @@ impl Ui {
|
||||||
self.ctx().data()
|
self.ctx().data()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The [`Output`] of the [`Context`] associated with this ui.
|
/// The [`PlatformOutput`] of the [`Context`] associated with this ui.
|
||||||
/// Equivalent to `.ctx().output()`.
|
/// Equivalent to `.ctx().output()`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn output(&self) -> RwLockWriteGuard<'_, Output> {
|
pub fn output(&self) -> RwLockWriteGuard<'_, PlatformOutput> {
|
||||||
self.ctx().output()
|
self.ctx().output()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,10 +13,10 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
||||||
// The most end-to-end benchmark.
|
// The most end-to-end benchmark.
|
||||||
c.bench_function("demo_with_tessellate__realistic", |b| {
|
c.bench_function("demo_with_tessellate__realistic", |b| {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let (_output, shapes) = ctx.run(RawInput::default(), |ctx| {
|
let full_output = ctx.run(RawInput::default(), |ctx| {
|
||||||
demo_windows.ui(ctx);
|
demo_windows.ui(ctx);
|
||||||
});
|
});
|
||||||
ctx.tessellate(shapes)
|
ctx.tessellate(full_output.shapes)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -28,11 +28,11 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let (_output, shapes) = ctx.run(RawInput::default(), |ctx| {
|
let full_output = ctx.run(RawInput::default(), |ctx| {
|
||||||
demo_windows.ui(ctx);
|
demo_windows.ui(ctx);
|
||||||
});
|
});
|
||||||
c.bench_function("demo_only_tessellate", |b| {
|
c.bench_function("demo_only_tessellate", |b| {
|
||||||
b.iter(|| ctx.tessellate(shapes.clone()))
|
b.iter(|| ctx.tessellate(full_output.shapes.clone()))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -145,10 +145,10 @@ fn test_egui_e2e() {
|
||||||
|
|
||||||
const NUM_FRAMES: usize = 5;
|
const NUM_FRAMES: usize = 5;
|
||||||
for _ in 0..NUM_FRAMES {
|
for _ in 0..NUM_FRAMES {
|
||||||
let (_output, shapes) = ctx.run(raw_input.clone(), |ctx| {
|
let full_output = ctx.run(raw_input.clone(), |ctx| {
|
||||||
demo_windows.ui(ctx);
|
demo_windows.ui(ctx);
|
||||||
});
|
});
|
||||||
let clipped_meshes = ctx.tessellate(shapes);
|
let clipped_meshes = ctx.tessellate(full_output.shapes);
|
||||||
assert!(!clipped_meshes.is_empty());
|
assert!(!clipped_meshes.is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,10 +164,10 @@ fn test_egui_zero_window_size() {
|
||||||
|
|
||||||
const NUM_FRAMES: usize = 5;
|
const NUM_FRAMES: usize = 5;
|
||||||
for _ in 0..NUM_FRAMES {
|
for _ in 0..NUM_FRAMES {
|
||||||
let (_output, shapes) = ctx.run(raw_input.clone(), |ctx| {
|
let full_output = ctx.run(raw_input.clone(), |ctx| {
|
||||||
demo_windows.ui(ctx);
|
demo_windows.ui(ctx);
|
||||||
});
|
});
|
||||||
let clipped_meshes = ctx.tessellate(shapes);
|
let clipped_meshes = ctx.tessellate(full_output.shapes);
|
||||||
assert!(clipped_meshes.is_empty(), "There should be nothing to show");
|
assert!(clipped_meshes.is_empty(), "There should be nothing to show");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,8 +67,15 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
||||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
let (needs_repaint, textures_delta, shapes) =
|
let egui::FullOutput {
|
||||||
integration.update(display.gl_window().window());
|
platform_output,
|
||||||
|
needs_repaint,
|
||||||
|
textures_delta,
|
||||||
|
shapes,
|
||||||
|
} = integration.update(display.gl_window().window());
|
||||||
|
|
||||||
|
integration.handle_platform_output(display.gl_window().window(), platform_output);
|
||||||
|
|
||||||
let clipped_meshes = integration.egui_ctx.tessellate(shapes);
|
let clipped_meshes = integration.egui_ctx.tessellate(shapes);
|
||||||
|
|
||||||
// paint:
|
// paint:
|
||||||
|
|
|
@ -141,16 +141,22 @@ impl EguiGlium {
|
||||||
let raw_input = self
|
let raw_input = self
|
||||||
.egui_winit
|
.egui_winit
|
||||||
.take_egui_input(display.gl_window().window());
|
.take_egui_input(display.gl_window().window());
|
||||||
let (egui_output, shapes) = self.egui_ctx.run(raw_input, run_ui);
|
let egui::FullOutput {
|
||||||
let needs_repaint = egui_output.needs_repaint;
|
platform_output,
|
||||||
let textures_delta = self.egui_winit.handle_output(
|
needs_repaint,
|
||||||
|
textures_delta,
|
||||||
|
shapes,
|
||||||
|
} = self.egui_ctx.run(raw_input, run_ui);
|
||||||
|
|
||||||
|
self.egui_winit.handle_platform_output(
|
||||||
display.gl_window().window(),
|
display.gl_window().window(),
|
||||||
&self.egui_ctx,
|
&self.egui_ctx,
|
||||||
egui_output,
|
platform_output,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.shapes = shapes;
|
self.shapes = shapes;
|
||||||
self.textures_delta.append(textures_delta);
|
self.textures_delta.append(textures_delta);
|
||||||
|
|
||||||
needs_repaint
|
needs_repaint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -83,7 +83,15 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
||||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
let (needs_repaint, textures_delta, shapes) = integration.update(gl_window.window());
|
let egui::FullOutput {
|
||||||
|
platform_output,
|
||||||
|
needs_repaint,
|
||||||
|
textures_delta,
|
||||||
|
shapes,
|
||||||
|
} = integration.update(gl_window.window());
|
||||||
|
|
||||||
|
integration.handle_platform_output(gl_window.window(), platform_output);
|
||||||
|
|
||||||
let clipped_meshes = integration.egui_ctx.tessellate(shapes);
|
let clipped_meshes = integration.egui_ctx.tessellate(shapes);
|
||||||
|
|
||||||
// paint:
|
// paint:
|
||||||
|
|
|
@ -156,11 +156,15 @@ impl EguiGlow {
|
||||||
run_ui: impl FnMut(&egui::Context),
|
run_ui: impl FnMut(&egui::Context),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let raw_input = self.egui_winit.take_egui_input(window);
|
let raw_input = self.egui_winit.take_egui_input(window);
|
||||||
let (egui_output, shapes) = self.egui_ctx.run(raw_input, run_ui);
|
let egui::FullOutput {
|
||||||
let needs_repaint = egui_output.needs_repaint;
|
platform_output,
|
||||||
let textures_delta = self
|
needs_repaint,
|
||||||
.egui_winit
|
textures_delta,
|
||||||
.handle_output(window, &self.egui_ctx, egui_output);
|
shapes,
|
||||||
|
} = self.egui_ctx.run(raw_input, run_ui);
|
||||||
|
|
||||||
|
self.egui_winit
|
||||||
|
.handle_platform_output(window, &self.egui_ctx, platform_output);
|
||||||
|
|
||||||
self.shapes = shapes;
|
self.shapes = shapes;
|
||||||
self.textures_delta.append(textures_delta);
|
self.textures_delta.append(textures_delta);
|
||||||
|
|
|
@ -267,14 +267,19 @@ impl AppRunner {
|
||||||
let canvas_size = canvas_size_in_points(self.canvas_id());
|
let canvas_size = canvas_size_in_points(self.canvas_id());
|
||||||
let raw_input = self.input.new_frame(canvas_size);
|
let raw_input = self.input.new_frame(canvas_size);
|
||||||
|
|
||||||
let (egui_output, shapes) = self.egui_ctx.run(raw_input, |egui_ctx| {
|
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
|
||||||
self.app.update(egui_ctx, &self.frame);
|
self.app.update(egui_ctx, &self.frame);
|
||||||
});
|
});
|
||||||
let clipped_meshes = self.egui_ctx.tessellate(shapes);
|
let egui::FullOutput {
|
||||||
|
platform_output,
|
||||||
|
needs_repaint,
|
||||||
|
textures_delta,
|
||||||
|
shapes,
|
||||||
|
} = full_output;
|
||||||
|
|
||||||
let needs_repaint = egui_output.needs_repaint;
|
self.handle_platform_output(platform_output);
|
||||||
let textures_delta = self.handle_egui_output(egui_output);
|
|
||||||
self.textures_delta.append(textures_delta);
|
self.textures_delta.append(textures_delta);
|
||||||
|
let clipped_meshes = self.egui_ctx.tessellate(shapes);
|
||||||
|
|
||||||
{
|
{
|
||||||
let app_output = self.frame.take_app_output();
|
let app_output = self.frame.take_app_output();
|
||||||
|
@ -306,21 +311,20 @@ impl AppRunner {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_egui_output(&mut self, output: egui::Output) -> egui::TexturesDelta {
|
fn handle_platform_output(&mut self, platform_output: egui::PlatformOutput) {
|
||||||
if self.egui_ctx.options().screen_reader {
|
if self.egui_ctx.options().screen_reader {
|
||||||
self.screen_reader.speak(&output.events_description());
|
self.screen_reader
|
||||||
|
.speak(&platform_output.events_description());
|
||||||
}
|
}
|
||||||
|
|
||||||
let egui::Output {
|
let egui::PlatformOutput {
|
||||||
cursor_icon,
|
cursor_icon,
|
||||||
open_url,
|
open_url,
|
||||||
copied_text,
|
copied_text,
|
||||||
needs_repaint: _, // handled elsewhere
|
events: _, // already handled
|
||||||
events: _, // already handled
|
|
||||||
mutable_text_under_cursor,
|
mutable_text_under_cursor,
|
||||||
text_cursor_pos,
|
text_cursor_pos,
|
||||||
textures_delta,
|
} = platform_output;
|
||||||
} = output;
|
|
||||||
|
|
||||||
set_cursor_icon(cursor_icon);
|
set_cursor_icon(cursor_icon);
|
||||||
if let Some(open) = open_url {
|
if let Some(open) = open_url {
|
||||||
|
@ -341,8 +345,6 @@ impl AppRunner {
|
||||||
text_agent::move_text_cursor(text_cursor_pos, self.canvas_id());
|
text_agent::move_text_cursor(text_cursor_pos, self.canvas_id());
|
||||||
self.text_cursor_pos = text_cursor_pos;
|
self.text_cursor_pos = text_cursor_pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
textures_delta
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -152,7 +152,7 @@ pub struct TexturesDelta {
|
||||||
/// New or changed textures. Apply before painting.
|
/// New or changed textures. Apply before painting.
|
||||||
pub set: AHashMap<TextureId, ImageDelta>,
|
pub set: AHashMap<TextureId, ImageDelta>,
|
||||||
|
|
||||||
/// Texture to free after painting.
|
/// Textures to free after painting.
|
||||||
pub free: Vec<TextureId>,
|
pub free: Vec<TextureId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue