mirror of
https://github.com/emilk/egui.git
synced 2026-08-30 05:10:03 -04:00
Refactor integrations (#871)
* Unify code in egui_glium and egui_glow into egui_winit::EpiIntegration * Simplify `EguiGlium` interface * Simplify `EguiGlow` interface * egui_web refactor: merge `WebBackend` into `AppRunner`
This commit is contained in:
@@ -4,72 +4,14 @@ pub use egui::{pos2, Color32};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub struct WebBackend {
|
||||
egui_ctx: egui::CtxRef,
|
||||
painter: Box<dyn Painter>,
|
||||
previous_frame_time: Option<f32>,
|
||||
frame_start: Option<f64>,
|
||||
}
|
||||
|
||||
impl WebBackend {
|
||||
pub fn new(canvas_id: &str) -> Result<Self, JsValue> {
|
||||
let ctx = egui::CtxRef::default();
|
||||
|
||||
let painter: Box<dyn Painter> =
|
||||
if let Ok(webgl2_painter) = webgl2::WebGl2Painter::new(canvas_id) {
|
||||
console_log("Using WebGL2 backend");
|
||||
Box::new(webgl2_painter)
|
||||
} else {
|
||||
console_log("Falling back to WebGL1 backend");
|
||||
Box::new(webgl1::WebGlPainter::new(canvas_id)?)
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
egui_ctx: ctx,
|
||||
painter,
|
||||
previous_frame_time: None,
|
||||
frame_start: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// id of the canvas html element containing the rendering
|
||||
pub fn canvas_id(&self) -> &str {
|
||||
self.painter.canvas_id()
|
||||
}
|
||||
|
||||
pub fn begin_frame(&mut self, raw_input: egui::RawInput) {
|
||||
self.frame_start = Some(now_sec());
|
||||
self.egui_ctx.begin_frame(raw_input)
|
||||
}
|
||||
|
||||
pub fn end_frame(&mut self) -> Result<(egui::Output, Vec<egui::ClippedMesh>), JsValue> {
|
||||
let frame_start = self
|
||||
.frame_start
|
||||
.take()
|
||||
.expect("unmatched calls to begin_frame/end_frame");
|
||||
|
||||
let (output, shapes) = self.egui_ctx.end_frame();
|
||||
let clipped_meshes = self.egui_ctx.tessellate(shapes);
|
||||
|
||||
let now = now_sec();
|
||||
self.previous_frame_time = Some((now - frame_start) as f32);
|
||||
|
||||
Ok((output, clipped_meshes))
|
||||
}
|
||||
|
||||
pub fn paint(
|
||||
&mut self,
|
||||
clear_color: egui::Rgba,
|
||||
clipped_meshes: Vec<egui::ClippedMesh>,
|
||||
) -> Result<(), JsValue> {
|
||||
self.painter.upload_egui_texture(&self.egui_ctx.texture());
|
||||
self.painter.clear(clear_color);
|
||||
self.painter
|
||||
.paint_meshes(clipped_meshes, self.egui_ctx.pixels_per_point())
|
||||
}
|
||||
|
||||
pub fn painter_debug_info(&self) -> String {
|
||||
self.painter.debug_info()
|
||||
fn create_painter(canvas_id: &str) -> Result<Box<dyn Painter>, JsValue> {
|
||||
if let Ok(webgl2_painter) = webgl2::WebGl2Painter::new(canvas_id) {
|
||||
console_log("Using WebGL2 backend");
|
||||
Ok(Box::new(webgl2_painter))
|
||||
} else {
|
||||
console_log("Falling back to WebGL1 backend");
|
||||
let webgl1_painter = webgl1::WebGlPainter::new(canvas_id)?;
|
||||
Ok(Box::new(webgl1_painter))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +71,9 @@ impl epi::RepaintSignal for NeedRepaint {
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub struct AppRunner {
|
||||
web_backend: WebBackend,
|
||||
egui_ctx: egui::CtxRef,
|
||||
painter: Box<dyn Painter>,
|
||||
previous_frame_time: Option<f32>,
|
||||
pub(crate) input: WebInput,
|
||||
app: Box<dyn epi::App>,
|
||||
pub(crate) needs_repaint: std::sync::Arc<NeedRepaint>,
|
||||
@@ -142,21 +86,25 @@ pub struct AppRunner {
|
||||
}
|
||||
|
||||
impl AppRunner {
|
||||
pub fn new(web_backend: WebBackend, app: Box<dyn epi::App>) -> Result<Self, JsValue> {
|
||||
load_memory(&web_backend.egui_ctx);
|
||||
pub fn new(canvas_id: &str, app: Box<dyn epi::App>) -> Result<Self, JsValue> {
|
||||
let egui_ctx = egui::CtxRef::default();
|
||||
|
||||
load_memory(&egui_ctx);
|
||||
|
||||
let prefer_dark_mode = crate::prefer_dark_mode();
|
||||
|
||||
if prefer_dark_mode == Some(true) {
|
||||
web_backend.egui_ctx.set_visuals(egui::Visuals::dark());
|
||||
egui_ctx.set_visuals(egui::Visuals::dark());
|
||||
} else {
|
||||
web_backend.egui_ctx.set_visuals(egui::Visuals::light());
|
||||
egui_ctx.set_visuals(egui::Visuals::light());
|
||||
}
|
||||
|
||||
let storage = LocalStorage::default();
|
||||
|
||||
let mut runner = Self {
|
||||
web_backend,
|
||||
egui_ctx,
|
||||
painter: create_painter(canvas_id)?,
|
||||
previous_frame_time: None,
|
||||
input: Default::default(),
|
||||
app,
|
||||
needs_repaint: Default::default(),
|
||||
@@ -172,23 +120,21 @@ impl AppRunner {
|
||||
let mut app_output = epi::backend::AppOutput::default();
|
||||
let mut frame = epi::backend::FrameBuilder {
|
||||
info: runner.integration_info(),
|
||||
tex_allocator: runner.web_backend.painter.as_tex_allocator(),
|
||||
tex_allocator: runner.painter.as_tex_allocator(),
|
||||
output: &mut app_output,
|
||||
repaint_signal: runner.needs_repaint.clone(),
|
||||
}
|
||||
.build();
|
||||
runner.app.setup(
|
||||
&runner.web_backend.egui_ctx,
|
||||
&mut frame,
|
||||
Some(&runner.storage),
|
||||
);
|
||||
runner
|
||||
.app
|
||||
.setup(&runner.egui_ctx, &mut frame, Some(&runner.storage));
|
||||
}
|
||||
|
||||
Ok(runner)
|
||||
}
|
||||
|
||||
pub fn egui_ctx(&self) -> &egui::CtxRef {
|
||||
&self.web_backend.egui_ctx
|
||||
&self.egui_ctx
|
||||
}
|
||||
|
||||
pub fn auto_save(&mut self) {
|
||||
@@ -197,7 +143,7 @@ impl AppRunner {
|
||||
|
||||
if time_since_last_save > self.app.auto_save_interval().as_secs_f64() {
|
||||
if self.app.persist_egui_memory() {
|
||||
save_memory(&self.web_backend.egui_ctx);
|
||||
save_memory(&self.egui_ctx);
|
||||
}
|
||||
self.app.save(&mut self.storage);
|
||||
self.last_save_time = now;
|
||||
@@ -205,19 +151,16 @@ impl AppRunner {
|
||||
}
|
||||
|
||||
pub fn canvas_id(&self) -> &str {
|
||||
self.web_backend.canvas_id()
|
||||
self.painter.canvas_id()
|
||||
}
|
||||
|
||||
pub fn warm_up(&mut self) -> Result<(), JsValue> {
|
||||
if self.app.warm_up_enabled() {
|
||||
let saved_memory = self.web_backend.egui_ctx.memory().clone();
|
||||
self.web_backend
|
||||
.egui_ctx
|
||||
.memory()
|
||||
.set_everything_is_visible(true);
|
||||
let saved_memory = self.egui_ctx.memory().clone();
|
||||
self.egui_ctx.memory().set_everything_is_visible(true);
|
||||
self.logic()?;
|
||||
*self.web_backend.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge.
|
||||
self.web_backend.egui_ctx.clear_animations();
|
||||
*self.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge.
|
||||
self.egui_ctx.clear_animations();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -229,59 +172,98 @@ impl AppRunner {
|
||||
web_location_hash: location_hash().unwrap_or_default(),
|
||||
}),
|
||||
prefer_dark_mode: self.prefer_dark_mode,
|
||||
cpu_usage: self.web_backend.previous_frame_time,
|
||||
cpu_usage: self.previous_frame_time,
|
||||
native_pixels_per_point: Some(native_pixels_per_point()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn logic(&mut self) -> Result<(egui::Output, Vec<egui::ClippedMesh>), JsValue> {
|
||||
resize_canvas_to_screen_size(self.web_backend.canvas_id(), self.app.max_size_points());
|
||||
let canvas_size = canvas_size_in_points(self.web_backend.canvas_id());
|
||||
let frame_start = now_sec();
|
||||
|
||||
resize_canvas_to_screen_size(self.canvas_id(), self.app.max_size_points());
|
||||
let canvas_size = canvas_size_in_points(self.canvas_id());
|
||||
let raw_input = self.input.new_frame(canvas_size);
|
||||
|
||||
self.web_backend.begin_frame(raw_input);
|
||||
self.egui_ctx.begin_frame(raw_input);
|
||||
|
||||
let mut app_output = epi::backend::AppOutput::default();
|
||||
let mut frame = epi::backend::FrameBuilder {
|
||||
info: self.integration_info(),
|
||||
tex_allocator: self.web_backend.painter.as_tex_allocator(),
|
||||
tex_allocator: self.painter.as_tex_allocator(),
|
||||
output: &mut app_output,
|
||||
repaint_signal: self.needs_repaint.clone(),
|
||||
}
|
||||
.build();
|
||||
|
||||
self.app.update(&self.web_backend.egui_ctx, &mut frame);
|
||||
let (egui_output, clipped_meshes) = self.web_backend.end_frame()?;
|
||||
self.app.update(&self.egui_ctx, &mut frame);
|
||||
|
||||
if self.web_backend.egui_ctx.memory().options.screen_reader {
|
||||
self.screen_reader.speak(&egui_output.events_description());
|
||||
}
|
||||
handle_output(&egui_output, self);
|
||||
let (egui_output, shapes) = self.egui_ctx.end_frame();
|
||||
let clipped_meshes = self.egui_ctx.tessellate(shapes);
|
||||
|
||||
self.handle_egui_output(&egui_output);
|
||||
|
||||
{
|
||||
let epi::backend::AppOutput {
|
||||
quit: _, // Can't quit a web page
|
||||
window_size: _, // Can't resize a web page
|
||||
window_title: _,
|
||||
decorated: _, // Can't show decorations
|
||||
drag_window: _, // Can't be dragged
|
||||
quit: _, // Can't quit a web page
|
||||
window_size: _, // Can't resize a web page
|
||||
window_title: _, // TODO: change title of window
|
||||
decorated: _, // Can't toggle decorations
|
||||
drag_window: _, // Can't be dragged
|
||||
} = app_output;
|
||||
}
|
||||
|
||||
self.previous_frame_time = Some((now_sec() - frame_start) as f32);
|
||||
Ok((egui_output, clipped_meshes))
|
||||
}
|
||||
|
||||
pub fn paint(&mut self, clipped_meshes: Vec<egui::ClippedMesh>) -> Result<(), JsValue> {
|
||||
self.web_backend
|
||||
.paint(self.app.clear_color(), clipped_meshes)
|
||||
self.painter.upload_egui_texture(&self.egui_ctx.texture());
|
||||
self.painter.clear(self.app.clear_color());
|
||||
self.painter
|
||||
.paint_meshes(clipped_meshes, self.egui_ctx.pixels_per_point())
|
||||
}
|
||||
|
||||
fn handle_egui_output(&mut self, output: &egui::Output) {
|
||||
if self.egui_ctx.memory().options.screen_reader {
|
||||
self.screen_reader.speak(&output.events_description());
|
||||
}
|
||||
|
||||
let egui::Output {
|
||||
cursor_icon,
|
||||
open_url,
|
||||
copied_text,
|
||||
needs_repaint: _, // handled elsewhere
|
||||
events: _, // already handled
|
||||
mutable_text_under_cursor,
|
||||
text_cursor_pos,
|
||||
} = output;
|
||||
|
||||
set_cursor_icon(*cursor_icon);
|
||||
if let Some(open) = open_url {
|
||||
crate::open_url(&open.url, open.new_tab);
|
||||
}
|
||||
|
||||
#[cfg(web_sys_unstable_apis)]
|
||||
if !copied_text.is_empty() {
|
||||
set_clipboard_text(copied_text);
|
||||
}
|
||||
|
||||
#[cfg(not(web_sys_unstable_apis))]
|
||||
let _ = copied_text;
|
||||
|
||||
self.mutable_text_under_cursor = *mutable_text_under_cursor;
|
||||
|
||||
if &self.text_cursor_pos != text_cursor_pos {
|
||||
move_text_cursor(text_cursor_pos, self.canvas_id());
|
||||
self.text_cursor_pos = *text_cursor_pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Install event listeners to register different input events
|
||||
/// and start running the given app.
|
||||
pub fn start(canvas_id: &str, app: Box<dyn epi::App>) -> Result<AppRunnerRef, JsValue> {
|
||||
let backend = WebBackend::new(canvas_id)?;
|
||||
let mut runner = AppRunner::new(backend, app)?;
|
||||
let mut runner = AppRunner::new(canvas_id, app)?;
|
||||
runner.warm_up()?;
|
||||
start_runner(runner)
|
||||
}
|
||||
|
||||
@@ -293,38 +293,6 @@ impl epi::Storage for LocalStorage {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub fn handle_output(output: &egui::Output, runner: &mut AppRunner) {
|
||||
let egui::Output {
|
||||
cursor_icon,
|
||||
open_url,
|
||||
copied_text,
|
||||
needs_repaint: _, // handled elsewhere
|
||||
events: _, // we ignore these (TODO: accessibility screen reader)
|
||||
mutable_text_under_cursor,
|
||||
text_cursor_pos,
|
||||
} = output;
|
||||
|
||||
set_cursor_icon(*cursor_icon);
|
||||
if let Some(open) = open_url {
|
||||
crate::open_url(&open.url, open.new_tab);
|
||||
}
|
||||
|
||||
#[cfg(web_sys_unstable_apis)]
|
||||
if !copied_text.is_empty() {
|
||||
set_clipboard_text(copied_text);
|
||||
}
|
||||
|
||||
#[cfg(not(web_sys_unstable_apis))]
|
||||
let _ = copied_text;
|
||||
|
||||
runner.mutable_text_under_cursor = *mutable_text_under_cursor;
|
||||
|
||||
if &runner.text_cursor_pos != text_cursor_pos {
|
||||
move_text_cursor(text_cursor_pos, runner.canvas_id());
|
||||
runner.text_cursor_pos = *text_cursor_pos;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cursor_icon(cursor: egui::CursorIcon) -> Option<()> {
|
||||
let document = web_sys::window()?.document()?;
|
||||
document
|
||||
|
||||
Reference in New Issue
Block a user