Browse Source

[app] Refactor egui::app::App interface to be more data oriented

pull/30/head
Emil Ernerfeldt 4 years ago
parent
commit
251cde60f0
  1. 4
      CHANGELOG.md
  2. 2
      demo_web/src/lib.rs
  3. 33
      egui/src/app.rs
  4. 59
      egui/src/demos/app.rs
  5. 79
      egui_glium/src/backend.rs
  6. 45
      egui_web/src/backend.rs
  7. 9
      example_glium/src/main.rs

4
CHANGELOG.md

@ -2,6 +2,10 @@
## Unreleased
* `ui.horizontal(...)` etc returns `Response`
* Add ability to override text color with `visuals.override_text_color`
* Refactored the interface for `egui::app::App`
## 0.2.0 - 2020-10-10
* Color picker

2
demo_web/src/lib.rs

@ -9,6 +9,6 @@ pub fn start(canvas_id: &str) -> Result<(), wasm_bindgen::JsValue> {
let backend = egui_web::WebBackend::new(canvas_id)?;
let app = Box::new(egui::DemoApp::default());
let runner = egui_web::AppRunner::new(backend, app)?;
egui_web::run(runner)?;
egui_web::start(runner)?;
Ok(())
}

33
egui/src/app.rs

@ -13,7 +13,12 @@ use crate::Ui;
/// and deployed as a web site using the [`egui_web`](https://crates.io/crates/egui_web) crate.
pub trait App {
/// Called each time the UI needs repainting, which may be many times per second.
fn ui(&mut self, ui: &mut Ui, backend: &mut dyn Backend);
fn ui(
&mut self,
ui: &mut Ui,
info: &BackendInfo,
tex_allocator: Option<&mut dyn TextureAllocator>,
) -> AppOutput;
/// Called once on shutdown. Allows you to save state.
fn on_exit(&mut self, _storage: &mut dyn Storage) {}
@ -24,25 +29,29 @@ pub struct WebInfo {
pub web_location_hash: String,
}
pub trait Backend {
/// Information about the backend passed to the use app each frame.
pub struct BackendInfo {
/// If the app is running in a Web context, this returns information about the environment.
fn web_info(&self) -> Option<WebInfo> {
None
}
pub web_info: Option<WebInfo>,
/// Seconds of cpu usage (in seconds) of UI code on the previous frame.
/// Zero if this is the first frame.
fn cpu_usage(&self) -> Option<f32>;
/// `None` if this is the first frame.
pub cpu_usage: Option<f32>,
/// Local time. Used for the clock in the demo app.
fn seconds_since_midnight(&self) -> Option<f64> {
None
}
/// Set to `None` if you don't know.
pub seconds_since_midnight: Option<f64>,
}
/// Signal the backend that we'd like to exit the app now.
/// Action that can be taken by the user app.
#[derive(Clone, Copy, Debug, Default)]
pub struct AppOutput {
/// Set to `true` to stop the app.
/// This does nothing for web apps.
fn quit(&mut self) {}
pub quit: bool,
}
pub trait TextureAllocator {
/// Allocate a user texture (EXPERIMENTAL!)
fn new_texture_srgba_premultiplied(
&mut self,

59
egui/src/demos/app.rs

@ -314,11 +314,13 @@ impl DemoApp {
}
// TODO: give cpu_usage and web_info via `struct BackendInfo`
fn backend_ui(&mut self, ui: &mut Ui, backend: &mut dyn app::Backend) {
fn backend_ui(&mut self, ui: &mut Ui, info: &app::BackendInfo) -> app::AppOutput {
self.frame_history
.on_new_frame(ui.input().time, backend.cpu_usage());
.on_new_frame(ui.input().time, info.cpu_usage);
let is_web = backend.web_info().is_some();
let is_web = info.web_info.is_some();
let mut options = app::AppOutput::default();
if is_web {
ui.label("Egui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL.");
@ -332,10 +334,7 @@ impl DemoApp {
});
} else {
ui.heading("Egui");
if ui.button("Quit").clicked {
backend.quit();
return;
}
options.quit |= ui.button("Quit").clicked;
}
ui.separator();
@ -359,6 +358,8 @@ impl DemoApp {
&mut self.show_color_test,
"Show color blend test (debug backend painter)",
);
options
}
fn run_mode_ui(&mut self, ui: &mut Ui) {
@ -374,12 +375,19 @@ impl DemoApp {
}
impl app::App for DemoApp {
fn ui(&mut self, ui: &mut Ui, backend: &mut dyn app::Backend) {
fn ui(
&mut self,
ui: &mut Ui,
info: &app::BackendInfo,
tex_allocator: Option<&mut dyn app::TextureAllocator>,
) -> app::AppOutput {
let mut output = app::AppOutput::default();
Window::new("Backend")
.min_width(360.0)
.scroll(false)
.show(ui.ctx(), |ui| {
self.backend_ui(ui, backend);
output = self.backend_ui(ui, info);
});
let Self {
@ -388,28 +396,31 @@ impl app::App for DemoApp {
..
} = self;
if *show_color_test {
let mut tex_loader = |size: (usize, usize), pixels: &[Srgba]| {
backend.new_texture_srgba_premultiplied(size, pixels)
};
Window::new("Color Test")
.default_size(vec2(1024.0, 1024.0))
.scroll(true)
.open(show_color_test)
.show(ui.ctx(), |ui| {
color_test.ui(ui, &mut tex_loader);
});
// TODO: enable color test even without `tex_allocator`
if let Some(tex_allocator) = tex_allocator {
if *show_color_test {
let mut tex_loader = |size: (usize, usize), pixels: &[Srgba]| {
tex_allocator.new_texture_srgba_premultiplied(size, pixels)
};
Window::new("Color Test")
.default_size(vec2(1024.0, 1024.0))
.scroll(true)
.open(show_color_test)
.show(ui.ctx(), |ui| {
color_test.ui(ui, &mut tex_loader);
});
}
}
let web_info = backend.web_info();
let web_location_hash = web_info
let web_location_hash = info
.web_info
.as_ref()
.map(|info| info.web_location_hash.clone())
.unwrap_or_default();
let environment = DemoEnvironment {
web_location_hash,
seconds_since_midnight: backend.seconds_since_midnight(),
seconds_since_midnight: info.seconds_since_midnight,
};
self.ui(ui, &environment);
@ -418,6 +429,8 @@ impl app::App for DemoApp {
// Tell the backend to repaint as soon as possible
ui.ctx().request_repaint();
}
output
}
#[cfg(feature = "serde_json")]

79
egui_glium/src/backend.rs

@ -6,48 +6,20 @@ use crate::{
};
pub use egui::{
app::{App, Backend, Storage},
app::{self, App, Storage},
Srgba,
};
const EGUI_MEMORY_KEY: &str = "egui";
const WINDOW_KEY: &str = "window";
pub struct GliumBackend {
previous_frame_time: Option<f32>,
quit: bool,
painter: Painter,
}
impl GliumBackend {
pub fn new(painter: Painter) -> Self {
Self {
previous_frame_time: None,
quit: false,
painter,
}
}
}
impl Backend for GliumBackend {
fn cpu_usage(&self) -> Option<f32> {
self.previous_frame_time
}
fn seconds_since_midnight(&self) -> Option<f64> {
Some(seconds_since_midnight())
}
fn quit(&mut self) {
self.quit = true;
}
impl egui::app::TextureAllocator for Painter {
fn new_texture_srgba_premultiplied(
&mut self,
size: (usize, usize),
pixels: &[Srgba],
) -> egui::TextureId {
self.painter.new_user_texture(size, pixels)
self.new_user_texture(size, pixels)
}
}
@ -81,9 +53,9 @@ pub fn run(title: &str, mut storage: FileStorage, mut app: impl App + 'static) -
let mut raw_input = make_raw_input(&display);
// used to keep track of time for animations
let start_time = Instant::now();
let mut runner = GliumBackend::new(Painter::new(&display));
let mut previous_frame_time = None;
let mut painter = Painter::new(&display);
let mut clipboard = init_clipboard();
event_loop.run(move |event, _, control_flow| {
@ -91,27 +63,34 @@ pub fn run(title: &str, mut storage: FileStorage, mut app: impl App + 'static) -
let egui_start = Instant::now();
raw_input.time = start_time.elapsed().as_nanos() as f64 * 1e-9;
let backend_info = egui::app::BackendInfo {
web_info: None,
cpu_usage: previous_frame_time,
seconds_since_midnight: Some(seconds_since_midnight()),
};
let mut ui = ctx.begin_frame(raw_input.take());
app.ui(&mut ui, &mut runner);
let (output, paint_jobs) = ctx.end_frame();
let app_output = app.ui(&mut ui, &backend_info, Some(&mut painter));
let (egui_output, paint_jobs) = ctx.end_frame();
let frame_time = (Instant::now() - egui_start).as_secs_f64() as f32;
runner.previous_frame_time = Some(frame_time);
runner
.painter
.paint_jobs(&display, paint_jobs, &ctx.texture());
*control_flow = if runner.quit {
glutin::event_loop::ControlFlow::Exit
} else if output.needs_repaint {
display.gl_window().window().request_redraw();
glutin::event_loop::ControlFlow::Poll
} else {
glutin::event_loop::ControlFlow::Wait
};
previous_frame_time = Some(frame_time);
painter.paint_jobs(&display, paint_jobs, &ctx.texture());
{
let egui::app::AppOutput { quit } = app_output;
*control_flow = if quit {
glutin::event_loop::ControlFlow::Exit
} else if egui_output.needs_repaint {
display.gl_window().window().request_redraw();
glutin::event_loop::ControlFlow::Poll
} else {
glutin::event_loop::ControlFlow::Wait
};
}
handle_output(output, &display, clipboard.as_mut());
handle_output(egui_output, &display, clipboard.as_mut());
};
match event {

45
egui_web/src/backend.rs

@ -1,7 +1,7 @@
use crate::*;
pub use egui::{
app::{App, Backend, WebInfo},
app::{App, WebInfo},
Srgba,
};
@ -80,27 +80,13 @@ impl WebBackend {
}
}
impl Backend for WebBackend {
fn web_info(&self) -> Option<WebInfo> {
Some(WebInfo {
web_location_hash: location_hash().unwrap_or_default(),
})
}
fn cpu_usage(&self) -> Option<f32> {
self.previous_frame_time
}
fn seconds_since_midnight(&self) -> Option<f64> {
Some(seconds_since_midnight())
}
impl egui::app::TextureAllocator for webgl::Painter {
fn new_texture_srgba_premultiplied(
&mut self,
size: (usize, usize),
pixels: &[Srgba],
) -> egui::TextureId {
self.painter.new_user_texture(size, pixels)
self.new_user_texture(size, pixels)
}
}
@ -160,11 +146,26 @@ impl AppRunner {
let raw_input = self.web_input.new_frame();
let backend_info = egui::app::BackendInfo {
web_info: Some(WebInfo {
web_location_hash: location_hash().unwrap_or_default(),
}),
cpu_usage: self.web_backend.previous_frame_time,
seconds_since_midnight: Some(seconds_since_midnight()),
};
let mut ui = self.web_backend.begin_frame(raw_input);
self.app.ui(&mut ui, &mut self.web_backend);
let (output, paint_jobs) = self.web_backend.end_frame()?;
handle_output(&output);
Ok((output, paint_jobs))
let app_output = self
.app
.ui(&mut ui, &backend_info, Some(&mut self.web_backend.painter));
let (egui_output, paint_jobs) = self.web_backend.end_frame()?;
handle_output(&egui_output);
{
let egui::app::AppOutput { quit: _ } = app_output;
}
Ok((egui_output, paint_jobs))
}
pub fn paint(&mut self, paint_jobs: egui::PaintJobs) -> Result<(), JsValue> {
@ -174,7 +175,7 @@ impl AppRunner {
/// Install event listeners to register different input events
/// and starts running the given `AppRunner`.
pub fn run(app_runner: AppRunner) -> Result<AppRunnerRef, JsValue> {
pub fn start(app_runner: AppRunner) -> Result<AppRunnerRef, JsValue> {
let runner_ref = AppRunnerRef(Arc::new(Mutex::new(app_runner)));
install_canvas_events(&runner_ref)?;
install_document_events(&runner_ref)?;

9
example_glium/src/main.rs

@ -16,7 +16,12 @@ struct MyApp {
impl egui::app::App for MyApp {
/// This function will be called whenever the Ui needs to be shown,
/// which may be many times per second.
fn ui(&mut self, ui: &mut egui::Ui, _: &mut dyn egui::app::Backend) {
fn ui(
&mut self,
ui: &mut egui::Ui,
_info: &egui::app::BackendInfo,
_tex_allocator: Option<&mut dyn egui::app::TextureAllocator>,
) -> egui::app::AppOutput {
let MyApp { my_string, value } = self;
// Example used in `README.md`.
@ -28,6 +33,8 @@ impl egui::app::App for MyApp {
ui.text_edit(my_string);
ui.add(Slider::f32(value, 0.0..=1.0).text("float"));
});
Default::default()
}
fn on_exit(&mut self, storage: &mut dyn egui::app::Storage) {

Loading…
Cancel
Save