diff --git a/docs/egui_demo_app.js b/docs/egui_demo_app.js index 240d9c36..23a7c65f 100644 --- a/docs/egui_demo_app.js +++ b/docs/egui_demo_app.js @@ -870,36 +870,36 @@ async function init(input) { var ret = wasm.memory; return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper2178 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 536, __wbg_adapter_26); + imports.wbg.__wbindgen_closure_wrapper2332 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 607, __wbg_adapter_26); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper2179 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 536, __wbg_adapter_29); + imports.wbg.__wbindgen_closure_wrapper2333 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 607, __wbg_adapter_29); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper2182 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 536, __wbg_adapter_32); + imports.wbg.__wbindgen_closure_wrapper2336 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 607, __wbg_adapter_32); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper2184 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 536, __wbg_adapter_35); + imports.wbg.__wbindgen_closure_wrapper2338 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 607, __wbg_adapter_35); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper2186 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 536, __wbg_adapter_38); + imports.wbg.__wbindgen_closure_wrapper2340 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 607, __wbg_adapter_38); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper2188 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 536, __wbg_adapter_41); + imports.wbg.__wbindgen_closure_wrapper2342 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 607, __wbg_adapter_41); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper2190 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 536, __wbg_adapter_44); + imports.wbg.__wbindgen_closure_wrapper2344 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 607, __wbg_adapter_44); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper3036 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 608, __wbg_adapter_47); + imports.wbg.__wbindgen_closure_wrapper3087 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 649, __wbg_adapter_47); return addHeapObject(ret); }; diff --git a/docs/egui_demo_app_bg.wasm b/docs/egui_demo_app_bg.wasm index 5c7a4fd5..b4dcfc82 100644 Binary files a/docs/egui_demo_app_bg.wasm and b/docs/egui_demo_app_bg.wasm differ diff --git a/egui/src/containers/collapsing_header.rs b/egui/src/containers/collapsing_header.rs index e8ccaf57..65004490 100644 --- a/egui/src/containers/collapsing_header.rs +++ b/egui/src/containers/collapsing_header.rs @@ -35,7 +35,7 @@ impl State { // Helper pub fn is_open(ctx: &Context, id: Id) -> Option { - if ctx.memory().all_collpasing_are_open { + if ctx.memory().everything_is_visible() { Some(true) } else { ctx.memory() @@ -52,7 +52,11 @@ impl State { /// 0 for closed, 1 for open, with tweening pub fn openness(&self, ctx: &Context, id: Id) -> f32 { - ctx.animate_bool(id, self.open || ctx.memory().all_collpasing_are_open) + if ctx.memory().everything_is_visible() { + 1.0 + } else { + ctx.animate_bool(id, self.open) + } } /// Show contents if we are open, with a nice animation between closed and open diff --git a/egui/src/containers/popup.rs b/egui/src/containers/popup.rs index 8b66a228..0a961b28 100644 --- a/egui/src/containers/popup.rs +++ b/egui/src/containers/popup.rs @@ -25,6 +25,8 @@ pub fn show_tooltip(ctx: &CtxRef, add_contents: impl FnOnce(&mut Ui)) { let position = position.min(ctx.input().screen_rect().right_bottom() - expected_size); let position = position.max(ctx.input().screen_rect().left_top()); position + } else if ctx.memory().everything_is_visible() { + Pos2::default() } else { return; // No good place for a tooltip :( }; diff --git a/egui/src/containers/window.rs b/egui/src/containers/window.rs index 82b8b730..41ea999a 100644 --- a/egui/src/containers/window.rs +++ b/egui/src/containers/window.rs @@ -209,7 +209,7 @@ impl<'open> Window<'open> { with_title_bar, } = self; - if matches!(open, Some(false)) && !ctx.memory().all_windows_are_open { + if matches!(open, Some(false)) && !ctx.memory().everything_is_visible() { return None; } diff --git a/egui/src/context.rs b/egui/src/context.rs index aa7aeee6..2e1f85d4 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -38,6 +38,7 @@ pub(crate) struct FrameState { /// How much space is used by panels. used_by_panels: Rect, + pub(crate) scroll_delta: Vec2, pub(crate) scroll_target: Option<(f32, Align)>, // TODO: move some things from `Memory` to here @@ -56,7 +57,7 @@ impl Default for FrameState { } impl FrameState { - pub fn begin_frame(&mut self, input: &InputState) { + fn begin_frame(&mut self, input: &InputState) { self.available_rect = input.screen_rect(); self.unused_rect = input.screen_rect(); self.used_by_panels = Rect::nothing(); @@ -717,6 +718,11 @@ impl Context { } animated_value } + + /// Clear memory of any animations. + pub fn clear_animations(&self) { + *self.animation_manager.lock() = Default::default(); + } } impl Context { diff --git a/egui/src/memory.rs b/egui/src/memory.rs index 39cb6c2e..0be9838b 100644 --- a/egui/src/memory.rs +++ b/egui/src/memory.rs @@ -60,12 +60,8 @@ pub struct Memory { #[cfg_attr(feature = "serde", serde(skip))] pub(crate) tooltip_rect: Option, - /// Useful for debugging, benchmarking etc. - pub all_collpasing_are_open: bool, - /// Useful for debugging, benchmarking etc. - pub all_menus_are_open: bool, - /// Useful for debugging, benchmarking etc. - pub all_windows_are_open: bool, + #[cfg_attr(feature = "serde", serde(skip))] + everything_is_visible: bool, } /// Say there is a button in a scroll area. @@ -258,7 +254,7 @@ impl Memory { /// Only one can be be open at a time. impl Memory { pub fn is_popup_open(&mut self, popup_id: Id) -> bool { - self.popup == Some(popup_id) + self.popup == Some(popup_id) || self.everything_is_visible() } pub fn open_popup(&mut self, popup_id: Id) { @@ -276,6 +272,24 @@ impl Memory { self.open_popup(popup_id); } } + + /// If true, all windows, menus, tooltips etc are to be visible at once. + /// + /// This is useful for testing, benchmarking, pre-caching, etc. + /// + /// Experimental feature! + pub fn everything_is_visible(&self) -> bool { + self.everything_is_visible + } + + /// If true, all windows, menus, tooltips etc are to be visible at once. + /// + /// This is useful for testing, benchmarking, pre-caching, etc. + /// + /// Experimental feature! + pub fn set_everything_is_visible(&mut self, value: bool) { + self.everything_is_visible = value; + } } // ---------------------------------------------------------------------------- diff --git a/egui/src/menu.rs b/egui/src/menu.rs index 2a149aac..8b6629ff 100644 --- a/egui/src/menu.rs +++ b/egui/src/menu.rs @@ -94,7 +94,7 @@ fn menu_impl<'c>( bar_state.open_menu = Some(menu_id); } - if bar_state.open_menu == Some(menu_id) || ui.memory().all_menus_are_open { + if bar_state.open_menu == Some(menu_id) || ui.ctx().memory().everything_is_visible() { let area = Area::new(menu_id) .order(Order::Foreground) .fixed_pos(button_response.rect.left_bottom()); diff --git a/egui/src/types.rs b/egui/src/types.rs index e68ae13e..abe0ac0d 100644 --- a/egui/src/types.rs +++ b/egui/src/types.rs @@ -139,7 +139,7 @@ impl Response { /// Show this UI if the item was hovered (i.e. a tooltip). /// If you call this multiple times the tooltips will stack underneath the previous ones. pub fn on_hover_ui(self, add_contents: impl FnOnce(&mut Ui)) -> Self { - if self.hovered { + if self.hovered || self.ctx.memory().everything_is_visible() { crate::containers::show_tooltip(&self.ctx, add_contents); } self diff --git a/egui_demo_lib/benches/benchmark.rs b/egui_demo_lib/benches/benchmark.rs index 6f961ece..749d3db0 100644 --- a/egui_demo_lib/benches/benchmark.rs +++ b/egui_demo_lib/benches/benchmark.rs @@ -18,7 +18,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { { let mut ctx = egui::CtxRef::default(); - ctx.memory().all_collpasing_are_open = true; // expand the demo window with everything + ctx.memory().set_everything_is_visible(true); // give us everything let mut demo_windows = egui_demo_lib::DemoWindows::default(); c.bench_function("demo_windows_full", |b| { @@ -32,7 +32,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { { let mut ctx = egui::CtxRef::default(); - ctx.memory().all_collpasing_are_open = true; // expand the demo window with everything + ctx.memory().set_everything_is_visible(true); // give us everything let mut demo_windows = egui_demo_lib::DemoWindows::default(); ctx.begin_frame(raw_input.clone()); demo_windows.ui(&ctx); diff --git a/egui_demo_lib/src/wrap_app.rs b/egui_demo_lib/src/wrap_app.rs index 8eb9a943..ead2a44c 100644 --- a/egui_demo_lib/src/wrap_app.rs +++ b/egui_demo_lib/src/wrap_app.rs @@ -42,6 +42,10 @@ impl epi::App for WrapApp { epi::set_value(storage, epi::APP_KEY, self); } + fn warm_up_enabled(&self) -> bool { + true // The example windows use a lot of emojis. Pre-cache them by running one frame where everything is open. + } + fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) { if let Some(web_info) = frame.info().web_info.as_ref() { if let Some(anchor) = web_info.web_location_hash.strip_prefix("#") { @@ -91,14 +95,14 @@ impl epi::App for WrapApp { }); self.backend_panel.update(ctx, frame); - if self.backend_panel.open { + if self.backend_panel.open || ctx.memory().everything_is_visible() { egui::SidePanel::left("backend_panel", 150.0).show(ctx, |ui| { self.backend_panel.ui(ui, frame); }); } for (anchor, app) in self.apps.iter_mut() { - if anchor == self.selected_anchor { + if anchor == self.selected_anchor || ctx.memory().everything_is_visible() { app.update(ctx, frame); } } @@ -192,19 +196,6 @@ impl BackendPanel { fn ui(&mut self, ui: &mut egui::Ui, frame: &mut epi::Frame<'_>) { ui.heading("💻 Backend"); - if frame.is_web() { - ui.label("Egui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL."); - ui.label( - "Everything you see is rendered as textured triangles. There is no DOM. There are no HTML elements. \ - This is not JavaScript. This is Rust, running at 60 FPS. This is the web page, reinvented with game tech."); - ui.label("This is also work in progress, and not ready for production... yet :)"); - ui.horizontal(|ui| { - ui.label("Project home page:"); - ui.hyperlink("https://github.com/emilk/egui"); - }); - ui.separator(); - } - self.run_mode_ui(ui); ui.separator(); @@ -219,8 +210,19 @@ impl BackendPanel { } } - if !frame.is_web() { - ui.separator(); + ui.separator(); + + if frame.is_web() { + ui.label("Egui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL."); + ui.label( + "Everything you see is rendered as textured triangles. There is no DOM. There are no HTML elements. \ + This is not JavaScript. This is Rust, running at 60 FPS. This is the web page, reinvented with game tech."); + ui.label("This is also work in progress, and not ready for production... yet :)"); + ui.horizontal_wrapped_for_text(egui::TextStyle::Body, |ui| { + ui.label("Project home page:"); + ui.hyperlink("https://github.com/emilk/egui"); + }); + } else { if ui .button("📱 Phone Size") .on_hover_text("Resize the window to be small like a phone.") diff --git a/egui_glium/src/backend.rs b/egui_glium/src/backend.rs index a6bd2dd1..a4438a1c 100644 --- a/egui_glium/src/backend.rs +++ b/egui_glium/src/backend.rs @@ -90,6 +90,18 @@ fn create_storage(app_name: &str) -> Option> { } } +fn integration_info( + display: &glium::Display, + previous_frame_time: Option, +) -> epi::IntegrationInfo { + epi::IntegrationInfo { + web_info: None, + cpu_usage: previous_frame_time, + seconds_since_midnight: Some(seconds_since_midnight()), + native_pixels_per_point: Some(native_pixels_per_point(&display)), + } +} + /// Run an egui app pub fn run(mut app: Box) -> ! { let mut storage = create_storage(app.name()); @@ -113,6 +125,7 @@ pub fn run(mut app: Box) -> ! { .as_mut() .and_then(|storage| epi::get_value(storage.as_ref(), EGUI_MEMORY_KEY)) .unwrap_or_default(); + app.setup(&ctx); let mut input_state = GliumInputState::from_pixels_per_point(native_pixels_per_point(&display)); @@ -126,6 +139,36 @@ pub fn run(mut app: Box) -> ! { let http = std::sync::Arc::new(crate::http::GliumHttp {}); + if app.warm_up_enabled() { + // let warm_up_start = Instant::now(); + input_state.raw.time = Some(0.0); + input_state.raw.screen_rect = Some(Rect::from_min_size( + Default::default(), + screen_size_in_pixels(&display) / input_state.raw.pixels_per_point.unwrap(), + )); + ctx.begin_frame(input_state.raw.take()); + let mut app_output = epi::backend::AppOutput::default(); + let mut frame = epi::backend::FrameBuilder { + info: integration_info(&display, None), + tex_allocator: Some(&mut painter), + http: http.clone(), + output: &mut app_output, + repaint_signal: repaint_signal.clone(), + } + .build(); + + let saved_memory = ctx.memory().clone(); + ctx.memory().set_everything_is_visible(true); + app.update(&ctx, &mut frame); + *ctx.memory() = saved_memory; // We don't want to remember that windows were huge. + ctx.clear_animations(); + + let (egui_output, _paint_commands) = ctx.end_frame(); + handle_output(egui_output, &display, clipboard.as_mut()); + // TODO: handle app_output + // eprintln!("Warmed up in {} ms", warm_up_start.elapsed().as_millis()) + } + event_loop.run(move |event, _, control_flow| { let mut redraw = || { let frame_start = Instant::now(); @@ -138,12 +181,7 @@ pub fn run(mut app: Box) -> ! { ctx.begin_frame(input_state.raw.take()); let mut app_output = epi::backend::AppOutput::default(); let mut frame = epi::backend::FrameBuilder { - info: epi::IntegrationInfo { - web_info: None, - cpu_usage: previous_frame_time, - seconds_since_midnight: Some(seconds_since_midnight()), - native_pixels_per_point: Some(native_pixels_per_point(&display)), - }, + info: integration_info(&display, previous_frame_time), tex_allocator: Some(&mut painter), http: http.clone(), output: &mut app_output, diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index e2b67bf3..2d22d5a2 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -179,6 +179,20 @@ impl AppRunner { self.web_backend.canvas_id() } + pub fn warm_up(&mut self) -> Result<(), JsValue> { + if self.app.warm_up_enabled() { + let saved_memory = self.web_backend.ctx.memory().clone(); + self.web_backend + .ctx + .memory() + .set_everything_is_visible(true); + self.logic()?; + *self.web_backend.ctx.memory() = saved_memory; // We don't want to remember that windows were huge. + self.web_backend.ctx.clear_animations(); + } + Ok(()) + } + pub fn logic(&mut self) -> Result<(egui::Output, egui::PaintJobs), JsValue> { resize_canvas_to_screen_size(self.web_backend.canvas_id()); let canvas_size = canvas_size_in_points(self.web_backend.canvas_id()); @@ -227,7 +241,8 @@ impl AppRunner { /// and start running the given app. pub fn start(canvas_id: &str, app: Box) -> Result { let backend = WebBackend::new(canvas_id)?; - let runner = AppRunner::new(backend, app)?; + let mut runner = AppRunner::new(backend, app)?; + runner.warm_up()?; start_runner(runner) } diff --git a/epi/src/lib.rs b/epi/src/lib.rs index f3bbe4f6..80137a29 100644 --- a/epi/src/lib.rs +++ b/epi/src/lib.rs @@ -64,6 +64,14 @@ pub trait App { /// Optional. fn setup(&mut self, _ctx: &egui::CtxRef) {} + /// If `true` a warm-up call to [`Self::update`] will be issued where + /// `ctx.memory().everything_is_visible()` will be set to `true`. + /// + /// In this warm-up call, all paint commands will be ignored. + fn warm_up_enabled(&self) -> bool { + false + } + /// Called once on start. Allows you to restore state. fn load(&mut self, _storage: &dyn Storage) {}