[app] Refactor egui::app::App interface to be more data oriented
This commit is contained in:
parent
e56924dc4f
commit
251cde60f0
7 changed files with 124 additions and 111 deletions
|
@ -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
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,9 +396,11 @@ impl app::App for DemoApp {
|
|||
..
|
||||
} = self;
|
||||
|
||||
// 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]| {
|
||||
backend.new_texture_srgba_premultiplied(size, pixels)
|
||||
tex_allocator.new_texture_srgba_premultiplied(size, pixels)
|
||||
};
|
||||
Window::new("Color Test")
|
||||
.default_size(vec2(1024.0, 1024.0))
|
||||
|
@ -400,16 +410,17 @@ impl app::App for DemoApp {
|
|||
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")]
|
||||
|
|
|
@ -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);
|
||||
previous_frame_time = Some(frame_time);
|
||||
painter.paint_jobs(&display, paint_jobs, &ctx.texture());
|
||||
|
||||
runner
|
||||
.painter
|
||||
.paint_jobs(&display, paint_jobs, &ctx.texture());
|
||||
{
|
||||
let egui::app::AppOutput { quit } = app_output;
|
||||
|
||||
*control_flow = if runner.quit {
|
||||
*control_flow = if quit {
|
||||
glutin::event_loop::ControlFlow::Exit
|
||||
} else if output.needs_repaint {
|
||||
} 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 {
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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…
Reference in a new issue