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

Add Context::request_repaint_after (#1694)

This commit is contained in:
Red Artist
2022-06-22 16:49:13 +05:30
committed by GitHub
parent 1a89cb35e1
commit 935913b1ec
13 changed files with 180 additions and 43 deletions

View File

@@ -28,7 +28,6 @@ impl Default for WrappedTextureManager {
}
// ----------------------------------------------------------------------------
#[derive(Default)]
struct ContextImpl {
/// `None` until the start of the first frame.
@@ -47,7 +46,9 @@ struct ContextImpl {
output: PlatformOutput,
paint_stats: PaintStats,
/// the duration backend will poll for new events, before forcing another egui update
/// even if there's no new events.
repaint_after: std::time::Duration,
/// While positive, keep requesting repaints. Decrement at the end of each frame.
repaint_requests: u32,
request_repaint_callbacks: Option<Box<dyn Fn() + Send + Sync>>,
@@ -574,6 +575,39 @@ impl Context {
}
}
/// Request repaint after the specified duration elapses in the case of no new input
/// events being received.
///
/// The function can be multiple times, but only the *smallest* duration will be considered.
/// So, if the function is called two times with `1 second` and `2 seconds`, egui will repaint
/// after `1 second`
///
/// This is primarily useful for applications who would like to save battery by avoiding wasted
/// redraws when the app is not in focus. But sometimes the GUI of the app might become stale
/// and outdated if it is not updated for too long.
///
/// Lets say, something like a stop watch widget that displays the time in seconds. You would waste
/// resources repainting multiple times within the same second (when you have no input),
/// just calculate the difference of duration between current time and next second change,
/// and call this function, to make sure that you are displaying the latest updated time, but
/// not wasting resources on needless repaints within the same second.
///
/// NOTE: only works if called before `Context::end_frame()`. to force egui to update,
/// use `Context::request_repaint()` instead.
///
/// ### Quirk:
/// Duration begins at the next frame. lets say for example that its a very inefficient app
/// and takes 500 milliseconds per frame at 2 fps. The widget / user might want a repaint in
/// next 500 milliseconds. Now, app takes 1000 ms per frame (1 fps) because the backend event
/// timeout takes 500 milli seconds AFTER the vsync swap buffer.
/// So, its not that we are requesting repaint within X duration. We are rather timing out
/// during app idle time where we are not receiving any new input events.
pub fn request_repaint_after(&self, duration: std::time::Duration) {
// Maybe we can check if duration is ZERO, and call self.request_repaint()?
let mut ctx = self.write();
ctx.repaint_after = ctx.repaint_after.min(duration);
}
/// For integrations: this callback will be called when an egui user calls [`Self::request_repaint`].
///
/// This lets you wake up a sleeping UI thread.
@@ -805,19 +839,26 @@ impl Context {
let platform_output: PlatformOutput = std::mem::take(&mut self.output());
let needs_repaint = if self.read().repaint_requests > 0 {
// if repaint_requests is greater than zero. just set the duration to zero for immediate
// repaint. if there's no repaint requests, then we can use the actual repaint_after instead.
let repaint_after = if self.read().repaint_requests > 0 {
self.write().repaint_requests -= 1;
true
std::time::Duration::ZERO
} else {
false
self.read().repaint_after
};
self.write().requested_repaint_last_frame = needs_repaint;
self.write().requested_repaint_last_frame = repaint_after.is_zero();
// make sure we reset the repaint_after duration.
// otherwise, if repaint_after is low, then any widget setting repaint_after next frame,
// will fail to overwrite the previous lower value. and thus, repaints will never
// go back to higher values.
self.write().repaint_after = std::time::Duration::MAX;
let shapes = self.drain_paint_lists();
FullOutput {
platform_output,
needs_repaint,
repaint_after,
textures_delta,
shapes,
}

View File

@@ -10,10 +10,15 @@ pub struct FullOutput {
/// Non-rendering related output.
pub platform_output: PlatformOutput,
/// If `true`, egui is requesting immediate repaint (i.e. on the next frame).
/// If `Duration::is_zero()`, 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,
///
/// If `Duration` is greater than zero, egui wants to be repainted at or before the specified
/// duration elapses. when in reactive mode, egui spends forever waiting for input and only then,
/// will it repaint itself. this can be used to make sure that backend will only wait for a
/// specified amount of time, and repaint egui without any new input.
pub repaint_after: std::time::Duration,
/// Texture changes since last frame (including the font texture).
///
@@ -32,13 +37,13 @@ impl FullOutput {
pub fn append(&mut self, newer: Self) {
let Self {
platform_output,
needs_repaint,
repaint_after,
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.repaint_after = repaint_after; // 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
}
@@ -49,7 +54,7 @@ impl FullOutput {
/// You can access (and modify) this with [`crate::Context::output`].
///
/// The backend should use this.
#[derive(Clone, Default, PartialEq)]
#[derive(Default, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct PlatformOutput {
/// Set the cursor to this icon.