Skip to content

Commit c328121

Browse files
committed
refactor RunMode: move it from backend to the demo App
This simplifies the egui_glium and egui_web backends substantially, reduces the scope of RunMode to a single file, and removes duplicated code. Basically: this is how I should have written it from the beginning.
1 parent 0ea80ae commit c328121

File tree

8 files changed

+67
-63
lines changed

8 files changed

+67
-63
lines changed

demo_glium/src/main.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
#![deny(warnings)]
22
#![warn(clippy::all)]
33

4-
use egui_glium::{storage::FileStorage, RunMode};
4+
use egui_glium::storage::FileStorage;
55

66
fn main() {
77
let title = "Egui glium demo";
88
let storage = FileStorage::from_path(".egui_demo_glium.json".into());
99
let app: egui::DemoApp = egui::app::get_value(&storage, egui::app::APP_KEY).unwrap_or_default();
10-
egui_glium::run(title, RunMode::Reactive, storage, app);
10+
egui_glium::run(title, storage, app);
1111
}

demo_web/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use wasm_bindgen::prelude::*;
66
/// This is the entry-point for all the web-assembly.
77
#[wasm_bindgen]
88
pub fn start(canvas_id: &str) -> Result<(), wasm_bindgen::JsValue> {
9-
let backend = egui_web::WebBackend::new(canvas_id, egui_web::RunMode::Reactive)?;
9+
let backend = egui_web::WebBackend::new(canvas_id)?;
1010
let app = Box::new(egui::DemoApp::default());
1111
let runner = egui_web::AppRunner::new(backend, app)?;
1212
egui_web::run(runner)?;

egui/src/app.rs

-17
Original file line numberDiff line numberDiff line change
@@ -19,29 +19,12 @@ pub trait App {
1919
fn on_exit(&mut self, _storage: &mut dyn Storage) {}
2020
}
2121

22-
// TODO: replace with manually calling `egui::Context::request_repaint()` each frame.
23-
/// How the backend runs the app
24-
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25-
pub enum RunMode {
26-
/// Repaint the UI all the time (at the display refresh rate of e.g. 60 Hz).
27-
/// This is good for games where things are constantly moving.
28-
/// This can also be achieved with `RunMode::Reactive` combined with calling `egui::Context::request_repaint()` each frame.
29-
Continuous,
30-
31-
/// Only repaint when there are animations or input (mouse movement, keyboard input etc).
32-
/// This saves CPU.
33-
Reactive,
34-
}
35-
3622
pub struct WebInfo {
3723
/// e.g. "#fragment" part of "www.example.com/index.html#fragment"
3824
pub web_location_hash: String,
3925
}
4026

4127
pub trait Backend {
42-
fn run_mode(&self) -> RunMode;
43-
fn set_run_mode(&mut self, run_mode: RunMode);
44-
4528
/// If the app is running in a Web context, this returns information about the environment.
4629
fn web_info(&self) -> Option<WebInfo> {
4730
None

egui/src/demos/app.rs

+54-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,49 @@ use crate::{app, color::*, containers::*, demos::*, paint::*, widgets::*, *};
44

55
// ----------------------------------------------------------------------------
66

7+
/// How often we repaint the demo app by default
8+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9+
enum RunMode {
10+
/// This is the default for the demo.
11+
///
12+
/// If this is selected, Egui is only updated if are input events
13+
/// (like mouse movements) or there are some animations in the GUI.
14+
///
15+
/// Reactive mode saves CPU.
16+
///
17+
/// The downside is that the UI can become out-of-date if something it is supposed to monitor changes.
18+
/// For instance, a GUI for a thermostat need to repaint each time the temperature changes.
19+
/// To ensure the UI is up to date you need to call `egui::Context::request_repaint()` each
20+
/// time such an event happens. You can also chose to call `request_repaint()` once every second
21+
/// or after every single frame - this is called `Continuous` mode,
22+
/// and for games and interactive tools that need repainting every frame anyway, this should be the default.
23+
Reactive,
24+
25+
/// This will call `egui::Context::request_repaint()` at the end of each frame
26+
/// to request the backend to repaint as soon as possible.
27+
///
28+
/// On most platforms this will mean that Egui will run at the display refresh rate of e.g. 60 Hz.
29+
///
30+
/// For this demo it is not any reason to do so except to
31+
/// demonstrate how quickly Egui runs.
32+
///
33+
/// For games or other interactive apps, this is probably what you want to do.
34+
/// It will guarantee that Egui is always up-to-date.
35+
Continuous,
36+
}
37+
38+
/// Default for demo is Reactive since
39+
/// 1) We want to use minimal CPU
40+
/// 2) There are no external events that could invalidate the UI
41+
/// so there are no events to miss.
42+
impl Default for RunMode {
43+
fn default() -> Self {
44+
RunMode::Reactive
45+
}
46+
}
47+
48+
// ----------------------------------------------------------------------------
49+
750
/// Demonstrates how to make an app using Egui.
851
///
952
/// Implements `egui::app::App` so it can be used with
@@ -12,6 +55,8 @@ use crate::{app, color::*, containers::*, demos::*, paint::*, widgets::*, *};
1255
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1356
#[cfg_attr(feature = "serde", serde(default))]
1457
pub struct DemoApp {
58+
#[cfg_attr(feature = "serde", serde(skip))] // go back to `Continuous` mode each time we start
59+
run_mode: RunMode,
1560
previous_web_location_hash: String,
1661
open_windows: OpenWindows,
1762
demo_window: DemoWindow,
@@ -172,16 +217,15 @@ impl DemoApp {
172217
ui.separator();
173218

174219
ui.horizontal(|ui| {
175-
let mut run_mode = backend.run_mode();
220+
let run_mode = &mut self.run_mode;
176221
ui.label("Run mode:");
177-
ui.radio_value("Continuous", &mut run_mode, app::RunMode::Continuous)
222+
ui.radio_value("Continuous", run_mode, RunMode::Continuous)
178223
.tooltip_text("Repaint everything each frame");
179-
ui.radio_value("Reactive", &mut run_mode, app::RunMode::Reactive)
224+
ui.radio_value("Reactive", run_mode, RunMode::Reactive)
180225
.tooltip_text("Repaint when there are animations or input (e.g. mouse movement)");
181-
backend.set_run_mode(run_mode);
182226
});
183227

184-
if backend.run_mode() == app::RunMode::Continuous {
228+
if self.run_mode == RunMode::Continuous {
185229
ui.add(
186230
label!("Repainting the UI each frame. FPS: {:.1}", backend.fps())
187231
.text_style(TextStyle::Monospace),
@@ -232,6 +276,11 @@ impl app::App for DemoApp {
232276
.map(|info| info.web_location_hash.as_str())
233277
.unwrap_or_default();
234278
self.ui(ui, web_location_hash);
279+
280+
if self.run_mode == RunMode::Continuous {
281+
// Tell the backend to repaint as soon as possible
282+
ui.ctx().request_repaint();
283+
}
235284
}
236285

237286
#[cfg(feature = "serde_json")]

egui_glium/src/backend.rs

+5-23
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::{
66
};
77

88
pub use egui::{
9-
app::{App, Backend, RunMode, Storage},
9+
app::{App, Backend, Storage},
1010
Srgba,
1111
};
1212

@@ -16,30 +16,20 @@ const WINDOW_KEY: &str = "window";
1616
pub struct GliumBackend {
1717
frame_times: egui::MovementTracker<f32>,
1818
quit: bool,
19-
run_mode: RunMode,
2019
painter: Painter,
2120
}
2221

2322
impl GliumBackend {
24-
pub fn new(run_mode: RunMode, painter: Painter) -> Self {
23+
pub fn new(painter: Painter) -> Self {
2524
Self {
2625
frame_times: egui::MovementTracker::new(1000, 1.0),
2726
quit: false,
28-
run_mode,
2927
painter,
3028
}
3129
}
3230
}
3331

3432
impl Backend for GliumBackend {
35-
fn run_mode(&self) -> RunMode {
36-
self.run_mode
37-
}
38-
39-
fn set_run_mode(&mut self, run_mode: RunMode) {
40-
self.run_mode = run_mode;
41-
}
42-
4333
fn cpu_time(&self) -> f32 {
4434
self.frame_times.average().unwrap_or_default()
4535
}
@@ -62,12 +52,7 @@ impl Backend for GliumBackend {
6252
}
6353

6454
/// Run an egui app
65-
pub fn run(
66-
title: &str,
67-
run_mode: RunMode,
68-
mut storage: FileStorage,
69-
mut app: impl App + 'static,
70-
) -> ! {
55+
pub fn run(title: &str, mut storage: FileStorage, mut app: impl App + 'static) -> ! {
7156
let event_loop = glutin::event_loop::EventLoop::new();
7257
let mut window = glutin::window::WindowBuilder::new()
7358
.with_decorations(true)
@@ -98,7 +83,7 @@ pub fn run(
9883

9984
// used to keep track of time for animations
10085
let start_time = Instant::now();
101-
let mut runner = GliumBackend::new(run_mode, Painter::new(&display));
86+
let mut runner = GliumBackend::new(Painter::new(&display));
10287
let mut clipboard = init_clipboard();
10388

10489
event_loop.run(move |event, _, control_flow| {
@@ -120,13 +105,10 @@ pub fn run(
120105

121106
*control_flow = if runner.quit {
122107
glutin::event_loop::ControlFlow::Exit
123-
} else if runner.run_mode() == RunMode::Continuous {
108+
} else if output.needs_repaint {
124109
display.gl_window().window().request_redraw();
125110
glutin::event_loop::ControlFlow::Poll
126111
} else {
127-
if output.needs_repaint {
128-
display.gl_window().window().request_redraw();
129-
}
130112
glutin::event_loop::ControlFlow::Wait
131113
};
132114

egui_web/src/backend.rs

+2-12
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::*;
22

33
pub use egui::{
4-
app::{App, Backend, RunMode, WebInfo},
4+
app::{App, Backend, WebInfo},
55
Srgba,
66
};
77

@@ -12,20 +12,18 @@ pub struct WebBackend {
1212
painter: webgl::Painter,
1313
frame_times: egui::MovementTracker<f32>,
1414
frame_start: Option<f64>,
15-
run_mode: RunMode,
1615
last_save_time: Option<f64>,
1716
}
1817

1918
impl WebBackend {
20-
pub fn new(canvas_id: &str, run_mode: RunMode) -> Result<Self, JsValue> {
19+
pub fn new(canvas_id: &str) -> Result<Self, JsValue> {
2120
let ctx = egui::Context::new();
2221
load_memory(&ctx);
2322
Ok(Self {
2423
ctx,
2524
painter: webgl::Painter::new(canvas_id)?,
2625
frame_times: egui::MovementTracker::new(1000, 1.0),
2726
frame_start: None,
28-
run_mode,
2927
last_save_time: None,
3028
})
3129
}
@@ -83,14 +81,6 @@ impl WebBackend {
8381
}
8482

8583
impl Backend for WebBackend {
86-
fn run_mode(&self) -> RunMode {
87-
self.run_mode
88-
}
89-
90-
fn set_run_mode(&mut self, run_mode: RunMode) {
91-
self.run_mode = run_mode;
92-
}
93-
9484
fn web_info(&self) -> Option<WebInfo> {
9585
Some(WebInfo {
9686
web_location_hash: location_hash().unwrap_or_default(),

egui_web/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ pub struct AppRunnerRef(Arc<Mutex<AppRunner>>);
229229
fn paint_and_schedule(runner_ref: AppRunnerRef) -> Result<(), JsValue> {
230230
fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
231231
let mut runner_lock = runner_ref.0.lock();
232-
if runner_lock.web_backend.run_mode() == RunMode::Continuous || runner_lock.needs_repaint {
232+
if runner_lock.needs_repaint {
233233
runner_lock.needs_repaint = false;
234234
let (output, paint_jobs) = runner_lock.logic()?;
235235
runner_lock.paint(paint_jobs)?;

example_glium/src/main.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#![warn(clippy::all)]
55

66
use egui::{Slider, Window};
7-
use egui_glium::{storage::FileStorage, RunMode};
7+
use egui_glium::storage::FileStorage;
88

99
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
1010
#[derive(Default, serde::Deserialize, serde::Serialize)]
@@ -39,7 +39,7 @@ fn main() {
3939
let title = "My Egui Window";
4040
let storage = FileStorage::from_path(".egui_example_glium.json".into()); // Where to persist app state
4141
let app: MyApp = egui::app::get_value(&storage, egui::app::APP_KEY).unwrap_or_default(); // Restore `MyApp` from file, or create new `MyApp`.
42-
egui_glium::run(title, RunMode::Reactive, storage, app);
42+
egui_glium::run(title, storage, app);
4343
}
4444

4545
fn my_save_function() {

0 commit comments

Comments
 (0)