Skip to content

Commit

Permalink
Introduce egui::FullOutput, returned from Context::run (#1292)
Browse files Browse the repository at this point in the history
* Introduce `egui::FullOutput`, returned from `Context::run`
* Rename `Output` to `PlatformOutput`
  • Loading branch information
emilk authored Feb 22, 2022
1 parent c5a9421 commit 31d3249
Show file tree
Hide file tree
Showing 17 changed files with 205 additions and 145 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
* Renamed `Ui::visible` to `Ui::is_visible`.
* Split `Event::Text` into `Event::Text` and `Event::Paste` ([#1058](https://github.com/emilk/egui/pull/1058)).
* 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)).
* 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)).
Expand Down
19 changes: 9 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ Missing an integration for the thing you're working on? Create one, it's easy!

### 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
let mut egui_ctx = egui::CtxRef::default();
Expand All @@ -201,20 +201,19 @@ let mut egui_ctx = egui::CtxRef::default();
loop {
// Gather input (mouse, touches, keyboard, screen size, etc):
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
});
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(clipped_meshes);
my_integration.free_egui_textures(&output.textures_delta.free);
my_integration.paint(&full_output.textures_delta, clipped_meshes);

my_integration.set_cursor_icon(output.cursor_icon);
if !output.copied_text.is_empty() {
my_integration.set_clipboard_text(output.copied_text);
let platform_output = full_output.platform_output;
my_integration.set_cursor_icon(platform_output.cursor_icon);
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
}
```

Expand Down
55 changes: 28 additions & 27 deletions egui-winit/src/epi.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use egui::Vec2;
use winit::dpi::LogicalSize;

pub fn points_to_size(points: Vec2) -> LogicalSize<f64> {
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,
Expand Down Expand Up @@ -222,6 +219,7 @@ pub struct EpiIntegration {
frame: epi::Frame,
persistence: crate::epi::Persistence,
pub egui_ctx: egui::Context,
pending_full_output: egui::FullOutput,
egui_winit: crate::State,
pub app: Box<dyn epi::App>,
/// When set, it is time to quit
Expand Down Expand Up @@ -267,6 +265,7 @@ impl EpiIntegration {
persistence,
egui_ctx,
egui_winit: crate::State::new(max_texture_side, window),
pending_full_output: Default::default(),
app,
quit: false,
can_drag_window: false,
Expand Down Expand Up @@ -295,8 +294,8 @@ impl EpiIntegration {
fn warm_up(&mut self, window: &winit::window::Window) {
let saved_memory: egui::Memory = self.egui_ctx.memory().clone();
self.egui_ctx.memory().set_everything_is_visible(true);
let (_, textures_delta, _) = self.update(window);
self.egui_ctx.output().textures_delta = textures_delta; // Handle it next frame
let full_output = self.update(window);
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.clear_animations();
}
Expand All @@ -323,37 +322,39 @@ impl EpiIntegration {
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,
) -> (bool, egui::TexturesDelta, Vec<egui::epaint::ClippedShape>) {
pub fn update(&mut self, window: &winit::window::Window) -> egui::FullOutput {
let frame_start = instant::Instant::now();

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

let needs_repaint = egui_output.needs_repaint;
let textures_delta = self
.egui_winit
.handle_output(window, &self.egui_ctx, egui_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;

if app_output.quit {
self.quit = self.app.on_exit_event();
self.pending_full_output.append(full_output);
let full_output = std::mem::take(&mut self.pending_full_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;
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);
}

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;
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) {
Expand Down
17 changes: 7 additions & 10 deletions egui-winit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -514,26 +514,25 @@ impl State {
/// * open any clicked urls
/// * update the IME
/// *
pub fn handle_output(
pub fn handle_platform_output(
&mut self,
window: &winit::window::Window,
egui_ctx: &egui::Context,
output: egui::Output,
) -> egui::TexturesDelta {
platform_output: egui::PlatformOutput,
) {
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,
open_url,
copied_text,
needs_repaint: _, // needs to be handled elsewhere
events: _, // handled above
mutable_text_under_cursor: _, // only used in egui_web
text_cursor_pos,
textures_delta,
} = output;
} = platform_output;

self.current_pixels_per_point = egui_ctx.pixels_per_point(); // someone can have changed it to scale the UI

Expand All @@ -550,8 +549,6 @@ impl State {
if let Some(egui::Pos2 { x, y }) = text_cursor_pos {
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) {
Expand Down
72 changes: 39 additions & 33 deletions egui/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// #![warn(missing_docs)]

use crate::{
animation_manager::AnimationManager, data::output::Output, frame_state::FrameState,
input_state::*, layers::GraphicLayers, memory::Options, TextureHandle, *,
animation_manager::AnimationManager, data::output::PlatformOutput, frame_state::FrameState,
input_state::*, layers::GraphicLayers, memory::Options, output::FullOutput, TextureHandle, *,
};
use epaint::{mutex::*, stats::*, text::Fonts, TessellationOptions, *};

Expand Down Expand Up @@ -42,7 +42,7 @@ struct ContextImpl {

// The output of a frame:
graphics: GraphicLayers,
output: Output,
output: PlatformOutput,

paint_stats: PaintStats,

Expand Down Expand Up @@ -108,7 +108,7 @@ impl ContextImpl {
/// Your handle to 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`] uses refcounting internally).
Expand All @@ -121,24 +121,24 @@ impl ContextImpl {
/// # Example:
///
/// ``` no_run
/// # fn handle_output(_: egui::Output) {}
/// # fn paint(_: Vec<egui::ClippedMesh>) {}
/// # fn handle_platform_output(_: egui::PlatformOutput) {}
/// # fn paint(textures_detla: egui::TexturesDelta, _: Vec<egui::ClippedMesh>) {}
/// let mut ctx = egui::Context::default();
///
/// // Game loop:
/// loop {
/// 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| {
/// ui.label("Hello world!");
/// if ui.button("Click me").clicked() {
/// // take some action here
/// }
/// });
/// });
/// let clipped_meshes = ctx.tessellate(shapes); // create triangles to paint
/// handle_output(output);
/// paint(clipped_meshes);
/// handle_platform_output(full_output.platform_output);
/// let clipped_meshes = ctx.tessellate(full_output.shapes); // create triangles to paint
/// paint(full_output.textures_delta, clipped_meshes);
/// }
/// ```
#[derive(Clone)]
Expand Down Expand Up @@ -185,19 +185,15 @@ impl Context {
///
/// // Each frame:
/// 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| {
/// ui.label("Hello egui!");
/// });
/// });
/// // handle output, paint shapes
/// // handle full_output
/// ```
#[must_use]
pub fn run(
&self,
new_input: RawInput,
run_ui: impl FnOnce(&Context),
) -> (Output, Vec<ClippedShape>) {
pub fn run(&self, new_input: RawInput, run_ui: impl FnOnce(&Context)) -> FullOutput {
self.begin_frame(new_input);
run_ui(self);
self.end_frame()
Expand All @@ -217,8 +213,8 @@ impl Context {
/// ui.label("Hello egui!");
/// });
///
/// let (output, shapes) = ctx.end_frame();
/// // handle output, paint shapes
/// let full_output = ctx.end_frame();
/// // handle full_output
/// ```
pub fn begin_frame(&self, new_input: RawInput) {
self.write().begin_frame_mut(new_input);
Expand Down Expand Up @@ -463,8 +459,13 @@ impl Context {
}

/// What egui outputs each frame.
///
/// ```
/// # let mut ctx = egui::Context::default();
/// ctx.output().cursor_icon = egui::CursorIcon::Progress;
/// ```
#[inline]
pub fn output(&self) -> RwLockWriteGuard<'_, Output> {
pub fn output(&self) -> RwLockWriteGuard<'_, PlatformOutput> {
RwLockWriteGuard::map(self.write(), |c| &mut c.output)
}

Expand Down Expand Up @@ -719,14 +720,13 @@ impl Context {

impl Context {
/// 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]
pub fn end_frame(&self) -> (Output, Vec<ClippedShape>) {
pub fn end_frame(&self) -> FullOutput {
if self.input().wants_repaint() {
self.request_repaint();
}

let textures_delta;
{
let ctx_impl = &mut *self.write();
ctx_impl
Expand All @@ -742,20 +742,26 @@ impl Context {
.set(TextureId::default(), font_image_delta);
}

ctx_impl
.output
.textures_delta
.append(ctx_impl.tex_manager.0.write().take_delta());
}
textures_delta = ctx_impl.tex_manager.0.write().take_delta();
};

let mut output: Output = std::mem::take(&mut self.output());
if self.read().repaint_requests > 0 {
let platform_output: PlatformOutput = std::mem::take(&mut self.output());

let needs_repaint = if self.read().repaint_requests > 0 {
self.write().repaint_requests -= 1;
output.needs_repaint = true;
}
true
} else {
false
};

let shapes = self.drain_paint_lists();
(output, shapes)

FullOutput {
platform_output,
needs_repaint,
textures_delta,
shapes,
}
}

fn drain_paint_lists(&self) -> Vec<ClippedShape> {
Expand Down
Loading

0 comments on commit 31d3249

Please sign in to comment.