1
0
mirror of https://github.com/emilk/egui.git synced 2026-09-01 22:30:03 -04:00

eframe: Replace Frame::update with fn logic and fn ui (#7775)

* Part of https://github.com/emilk/egui/issues/5113
* Part of https://github.com/emilk/egui/issues/3524

## What
This deprecates `eframe::App::update` and replaces it with two new
functions:

```rs
pub trait App {
	/// Called just before `ui`, and in the future this will
    /// also be called for background apps when needed.
	fn logic(&mut self, ctx: &egui::Context, frame: &mut Frame) { }
	
    /// Show your user interface to the user.
	fn ui(&mut self, ui: &mut egui::Ui, frame: &mut Frame);

	…
}
```

Similarly, `Context::run` is deprecated in favor of `Context::run_ui`.

`Plugin`s are now handed a `Ui` instead of just a `Context` in
`on_begin/end_frame`.

## TODO
…either in this PR or a later one
* [x] Deprecate `App::update`
* [x] Deprecate `Context::run`
* [x] Change plugins to get a `Ui`
* [x] Update kittest
* [x] Change viewports to get UI:s (`show_viewport_immediate` etc)
  - https://github.com/emilk/egui/pull/7779

## Later PRs
* [ ] Deprecate `Panel::show`
* [ ] Deprecate `CentralPanel::show`
* [ ] Deprecate `CentralPanel` ?
This commit is contained in:
Emil Ernerfeldt
2025-12-16 17:05:50 +01:00
committed by GitHub
parent 9487dc35ec
commit 2f6fe9c572
52 changed files with 417 additions and 321 deletions

View File

@@ -291,13 +291,13 @@ impl<'a> PanelSizer<'a> {
/// See the [module level docs](crate::containers::panel) for more details.
///
/// ```
/// # egui::__run_test_ctx(|ctx| {
/// egui::Panel::left("my_left_panel").show(ctx, |ui| {
/// # egui::__run_test_ui(|ui| {
/// egui::Panel::left("my_left_panel").show_inside(ui, |ui| {
/// ui.label("Hello World!");
/// });
/// # });
/// ```
#[must_use = "You should call .show()"]
#[must_use = "You should call .show_inside()"]
pub struct Panel {
side: PanelSide,
id: Id,
@@ -963,16 +963,16 @@ impl Panel {
/// See the [module level docs](crate::containers::panel) for more details.
///
/// ```
/// # egui::__run_test_ctx(|ctx| {
/// egui::Panel::top("my_panel").show(ctx, |ui| {
/// # egui::__run_test_ui(|ui| {
/// egui::Panel::top("my_panel").show_inside(ui, |ui| {
/// ui.label("Hello World! From `Panel`, that must be before `CentralPanel`!");
/// });
/// egui::CentralPanel::default().show(ctx, |ui| {
/// egui::CentralPanel::default().show_inside(ui, |ui| {
/// ui.label("Hello World!");
/// });
/// # });
/// ```
#[must_use = "You should call .show()"]
#[must_use = "You should call .show_inside()"]
#[derive(Default)]
pub struct CentralPanel {
frame: Option<Frame>,

View File

@@ -790,9 +790,13 @@ impl Context {
#[must_use]
fn run_ui_dyn(&self, new_input: RawInput, run_ui: &mut dyn FnMut(&mut Ui)) -> FullOutput {
let plugins = self.read(|ctx| ctx.plugins.ordered_plugins());
#[expect(deprecated)]
self.run(new_input, |ctx| {
crate::CentralPanel::no_frame().show(ctx, |ui| {
plugins.on_begin_pass(ui);
run_ui(ui);
plugins.on_end_pass(ui);
});
})
}
@@ -824,6 +828,7 @@ impl Context {
/// ## See also
/// * [`Self::run_ui`]
#[must_use]
#[deprecated = "Call run_ui instead"]
pub fn run(&self, new_input: RawInput, mut run_ui: impl FnMut(&Self)) -> FullOutput {
self.run_dyn(new_input, &mut run_ui)
}
@@ -924,9 +929,6 @@ impl Context {
plugins.on_input(&mut new_input);
self.write(|ctx| ctx.begin_pass(new_input));
// Plugins run just after the pass starts:
plugins.on_begin_pass(self);
}
/// See [`Self::begin_pass`].
@@ -2378,15 +2380,14 @@ impl Context {
crate::gui_zoom::zoom_with_keyboard(self);
}
// Plugins run just before the pass ends.
let plugins = self.read(|ctx| ctx.plugins.ordered_plugins());
plugins.on_end_pass(self);
#[cfg(debug_assertions)]
self.debug_painting();
let mut output = self.write(|ctx| ctx.end_pass());
let plugins = self.read(|ctx| ctx.plugins.ordered_plugins());
plugins.on_output(&mut output);
output
}
@@ -4039,10 +4040,8 @@ impl Context {
viewport.class = ViewportClass::Deferred;
viewport.builder = viewport_builder;
viewport.used = true;
viewport.viewport_ui_cb = Some(Arc::new(move |ctx| {
crate::CentralPanel::no_frame().show(ctx, |ui| {
(viewport_ui_cb)(ui, ViewportClass::Deferred);
});
viewport.viewport_ui_cb = Some(Arc::new(move |ui| {
(viewport_ui_cb)(ui, ViewportClass::Deferred);
}));
});
}
@@ -4118,10 +4117,8 @@ impl Context {
let viewport = ImmediateViewport {
ids,
builder,
viewport_ui_cb: Box::new(move |ctx| {
crate::CentralPanel::no_frame().show(ctx, |ui| {
*out = Some((viewport_ui_cb)(ui, ViewportClass::Immediate));
});
viewport_ui_cb: Box::new(move |ui| {
*out = Some((viewport_ui_cb)(ui, ViewportClass::Immediate));
}),
};
@@ -4246,11 +4243,11 @@ mod test {
// A single call, no request to discard:
{
let mut num_calls = 0;
let output = ctx.run(Default::default(), |ctx| {
let output = ctx.run_ui(Default::default(), |ui| {
num_calls += 1;
assert_eq!(ctx.output(|o| o.num_completed_passes), 0);
assert!(!ctx.output(|o| o.requested_discard()));
assert!(!ctx.will_discard());
assert_eq!(ui.output(|o| o.num_completed_passes), 0);
assert!(!ui.output(|o| o.requested_discard()));
assert!(!ui.will_discard());
});
assert_eq!(num_calls, 1);
assert_eq!(output.platform_output.num_completed_passes, 1);
@@ -4260,10 +4257,10 @@ mod test {
// A single call, with a denied request to discard:
{
let mut num_calls = 0;
let output = ctx.run(Default::default(), |ctx| {
let output = ctx.run_ui(Default::default(), |ui| {
num_calls += 1;
ctx.request_discard("test");
assert!(!ctx.will_discard(), "The request should have been denied");
ui.request_discard("test");
assert!(!ui.will_discard(), "The request should have been denied");
});
assert_eq!(num_calls, 1);
assert_eq!(output.platform_output.num_completed_passes, 1);
@@ -4291,10 +4288,10 @@ mod test {
// Normal single pass:
{
let mut num_calls = 0;
let output = ctx.run(Default::default(), |ctx| {
assert_eq!(ctx.output(|o| o.num_completed_passes), 0);
assert!(!ctx.output(|o| o.requested_discard()));
assert!(!ctx.will_discard());
let output = ctx.run_ui(Default::default(), |ui| {
assert_eq!(ui.output(|o| o.num_completed_passes), 0);
assert!(!ui.output(|o| o.requested_discard()));
assert!(!ui.will_discard());
num_calls += 1;
});
assert_eq!(num_calls, 1);
@@ -4305,13 +4302,13 @@ mod test {
// Request discard once:
{
let mut num_calls = 0;
let output = ctx.run(Default::default(), |ctx| {
assert_eq!(ctx.output(|o| o.num_completed_passes), num_calls);
let output = ctx.run_ui(Default::default(), |ui| {
assert_eq!(ui.output(|o| o.num_completed_passes), num_calls);
assert!(!ctx.will_discard());
assert!(!ui.will_discard());
if num_calls == 0 {
ctx.request_discard("test");
assert!(ctx.will_discard());
ui.request_discard("test");
assert!(ui.will_discard());
}
num_calls += 1;
@@ -4327,15 +4324,15 @@ mod test {
// Request discard twice:
{
let mut num_calls = 0;
let output = ctx.run(Default::default(), |ctx| {
assert_eq!(ctx.output(|o| o.num_completed_passes), num_calls);
let output = ctx.run_ui(Default::default(), |ui| {
assert_eq!(ui.output(|o| o.num_completed_passes), num_calls);
assert!(!ctx.will_discard());
ctx.request_discard("test");
assert!(!ui.will_discard());
ui.request_discard("test");
if num_calls == 0 {
assert!(ctx.will_discard(), "First request granted");
assert!(ui.will_discard(), "First request granted");
} else {
assert!(!ctx.will_discard(), "Second request should be denied");
assert!(!ui.will_discard(), "Second request should be denied");
}
num_calls += 1;
@@ -4357,13 +4354,13 @@ mod test {
// Request discard three times:
{
let mut num_calls = 0;
let output = ctx.run(Default::default(), |ctx| {
assert_eq!(ctx.output(|o| o.num_completed_passes), num_calls);
let output = ctx.run_ui(Default::default(), |ui| {
assert_eq!(ui.output(|o| o.num_completed_passes), num_calls);
assert!(!ctx.will_discard());
assert!(!ui.will_discard());
if num_calls <= 2 {
ctx.request_discard("test");
assert!(ctx.will_discard());
ui.request_discard("test");
assert!(ui.will_discard());
}
num_calls += 1;

View File

@@ -5,7 +5,7 @@
//! to get callbacks on certain events ([`Plugin::on_begin_pass`], [`Plugin::on_end_pass`]).
use crate::{
Align, Align2, Color32, Context, FontFamily, FontId, Plugin, Rect, Shape, Vec2, WidgetText,
Align, Align2, Color32, Context, FontFamily, FontId, Plugin, Rect, Shape, Ui, Vec2, WidgetText,
text,
};
@@ -57,9 +57,9 @@ impl Plugin for DebugTextPlugin {
"DebugTextPlugin"
}
fn on_end_pass(&mut self, ctx: &Context) {
fn on_end_pass(&mut self, ui: &mut Ui) {
let entries = std::mem::take(&mut self.entries);
Self::paint_entries(ctx, entries);
Self::paint_entries(ui, entries);
}
}

View File

@@ -1,6 +1,6 @@
use std::{any::Any, sync::Arc};
use crate::{Context, CursorIcon, Plugin};
use crate::{Context, CursorIcon, Plugin, Ui};
/// Plugin for tracking drag-and-drop payload.
///
@@ -32,12 +32,12 @@ impl Plugin for DragAndDrop {
/// Interrupt drag-and-drop if the user presses the escape key.
///
/// This needs to happen at frame start so we can properly capture the escape key.
fn on_begin_pass(&mut self, ctx: &Context) {
fn on_begin_pass(&mut self, ui: &mut Ui) {
let has_any_payload = self.payload.is_some();
if has_any_payload {
let abort_dnd_due_to_escape_key =
ctx.input_mut(|i| i.consume_key(crate::Modifiers::NONE, crate::Key::Escape));
ui.input_mut(|i| i.consume_key(crate::Modifiers::NONE, crate::Key::Escape));
if abort_dnd_due_to_escape_key {
self.payload = None;
@@ -50,18 +50,18 @@ impl Plugin for DragAndDrop {
/// This is a catch-all safety net in case user code doesn't capture the drag payload itself.
/// This must happen at end-of-frame such that we don't shadow the mouse release event from user
/// code.
fn on_end_pass(&mut self, ctx: &Context) {
fn on_end_pass(&mut self, ui: &mut Ui) {
let has_any_payload = self.payload.is_some();
if has_any_payload {
let abort_dnd_due_to_mouse_release = ctx.input_mut(|i| i.pointer.any_released());
let abort_dnd_due_to_mouse_release = ui.input_mut(|i| i.pointer.any_released());
if abort_dnd_due_to_mouse_release {
self.payload = None;
} else {
// We set the cursor icon only if its default, as the user code might have
// explicitly set it already.
ctx.output_mut(|o| {
ui.output_mut(|o| {
if o.cursor_icon == CursorIcon::Default {
o.cursor_icon = CursorIcon::Grabbing;
}

View File

@@ -43,23 +43,6 @@
//! In some GUI frameworks this would require defining multiple types and functions with callbacks or message handlers,
//! but thanks to `egui` being immediate mode everything is one self-contained function!
//!
//! ### Getting a [`Ui`]
//!
//! Use one of [`Panel`], [`CentralPanel`], [`Window`] or [`Area`] to
//! get access to an [`Ui`] where you can put widgets. For example:
//!
//! ```
//! # egui::__run_test_ctx(|ctx| {
//! egui::CentralPanel::default().show(&ctx, |ui| {
//! ui.add(egui::Label::new("Hello World!"));
//! ui.label("A shorter and more convenient way to add a label.");
//! if ui.button("Click me").clicked() {
//! // take some action here
//! }
//! });
//! # });
//! ```
//!
//! ### Quick start
//!
//! ```

View File

@@ -1,4 +1,4 @@
use crate::{Context, FullOutput, RawInput};
use crate::{Context, FullOutput, RawInput, Ui};
use ahash::HashMap;
use epaint::mutex::{Mutex, MutexGuard};
use std::sync::Arc;
@@ -23,13 +23,13 @@ pub trait Plugin: Send + Sync + std::any::Any + 'static {
/// Called at the start of each pass.
///
/// Can be used to show ui, e.g. a [`crate::Window`] or [`crate::SidePanel`].
fn on_begin_pass(&mut self, ctx: &Context) {}
/// Can be used to show ui, e.g. a [`crate::Window`] or [`crate::Panel`].
fn on_begin_pass(&mut self, ui: &mut Ui) {}
/// Called at the end of each pass.
///
/// Can be used to show ui, e.g. a [`crate::Window`].
fn on_end_pass(&mut self, ctx: &Context) {}
fn on_end_pass(&mut self, ui: &mut Ui) {}
/// Called just before the input is processed.
///
@@ -147,17 +147,17 @@ impl PluginsOrdered {
}
}
pub fn on_begin_pass(&self, ctx: &Context) {
pub fn on_begin_pass(&self, ui: &mut Ui) {
profiling::scope!("plugins", "on_begin_pass");
self.for_each_dyn(|p| {
p.on_begin_pass(ctx);
p.on_begin_pass(ui);
});
}
pub fn on_end_pass(&self, ctx: &Context) {
pub fn on_end_pass(&self, ui: &mut Ui) {
profiling::scope!("plugins", "on_end_pass");
self.for_each_dyn(|p| {
p.on_end_pass(ctx);
p.on_end_pass(ui);
});
}
@@ -214,7 +214,7 @@ impl Plugins {
}
/// Generic event callback.
pub type ContextCallback = Arc<dyn Fn(&Context) + Send + Sync>;
pub type ContextCallback = Arc<dyn Fn(&mut Ui) + Send + Sync>;
#[derive(Default)]
pub(crate) struct CallbackPlugin {
@@ -227,21 +227,21 @@ impl Plugin for CallbackPlugin {
"CallbackPlugins"
}
fn on_begin_pass(&mut self, ctx: &Context) {
fn on_begin_pass(&mut self, ui: &mut Ui) {
profiling::function_scope!();
for (_debug_name, cb) in &self.on_begin_plugins {
profiling::scope!("on_begin_pass", *_debug_name);
(cb)(ctx);
(cb)(ui);
}
}
fn on_end_pass(&mut self, ctx: &Context) {
fn on_end_pass(&mut self, ui: &mut Ui) {
profiling::function_scope!();
for (_debug_name, cb) in &self.on_end_plugins {
profiling::scope!("on_end_pass", *_debug_name);
(cb)(ctx);
(cb)(ui);
}
}
}

View File

@@ -128,8 +128,8 @@ impl Plugin for LabelSelectionState {
"LabelSelectionState"
}
fn on_begin_pass(&mut self, ctx: &Context) {
if ctx.input(|i| i.pointer.any_pressed() && !i.modifiers.shift) {
fn on_begin_pass(&mut self, ui: &mut Ui) {
if ui.input(|i| i.pointer.any_pressed() && !i.modifiers.shift) {
// Maybe a new selection is about to begin, but the old one is over:
// state.selection = None; // TODO(emilk): this makes sense, but doesn't work as expected.
}
@@ -145,9 +145,9 @@ impl Plugin for LabelSelectionState {
self.painted_selections.clear();
}
fn on_end_pass(&mut self, ctx: &Context) {
fn on_end_pass(&mut self, ui: &mut Ui) {
if self.is_dragging {
ctx.set_cursor_icon(CursorIcon::Text);
ui.set_cursor_icon(CursorIcon::Text);
}
if !self.has_reached_primary || !self.has_reached_secondary {
@@ -159,7 +159,7 @@ impl Plugin for LabelSelectionState {
if let Some(selection) = prev_selection {
// This was the first frame of glitch, so hide the
// glitching by removing all painted selections:
ctx.graphics_mut(|layers| {
ui.graphics_mut(|layers| {
if let Some(list) = layers.get_mut(selection.layer_id) {
for (shape_idx, row_selections) in self.painted_selections.drain(..) {
list.mutate_shape(shape_idx, |shape| {
@@ -190,21 +190,21 @@ impl Plugin for LabelSelectionState {
}
}
let pressed_escape = ctx.input(|i| i.key_pressed(crate::Key::Escape));
let clicked_something_else = ctx.input(|i| i.pointer.any_pressed()) && !self.any_hovered;
let pressed_escape = ui.input(|i| i.key_pressed(crate::Key::Escape));
let clicked_something_else = ui.input(|i| i.pointer.any_pressed()) && !self.any_hovered;
let delected_everything = pressed_escape || clicked_something_else;
if delected_everything {
self.selection = None;
}
if ctx.input(|i| i.pointer.any_released()) {
if ui.input(|i| i.pointer.any_released()) {
self.is_dragging = false;
}
let text_to_copy = std::mem::take(&mut self.text_to_copy);
if !text_to_copy.is_empty() {
ctx.copy_text(text_to_copy);
ui.copy_text(text_to_copy);
}
}
}

View File

@@ -73,7 +73,7 @@ use std::sync::Arc;
use epaint::{Pos2, Vec2};
use crate::{Context, Id};
use crate::{Context, Id, Ui};
// ----------------------------------------------------------------------------
@@ -263,7 +263,7 @@ impl ViewportIdPair {
}
/// The user-code that shows the ui in the viewport, used for deferred viewports.
pub type DeferredViewportUiCallback = dyn Fn(&Context) + Sync + Send;
pub type DeferredViewportUiCallback = dyn Fn(&mut Ui) + Sync + Send;
/// Render the given viewport, calling the given ui callback.
pub type ImmediateViewportRendererCallback = dyn for<'a> Fn(&Context, ImmediateViewport<'a>);
@@ -1251,5 +1251,5 @@ pub struct ImmediateViewport<'a> {
pub builder: ViewportBuilder,
/// The user-code that shows the GUI.
pub viewport_ui_cb: Box<dyn FnMut(&Context) + 'a>,
pub viewport_ui_cb: Box<dyn FnMut(&mut Ui) + 'a>,
}