From e31235f465bad2213989e27d12fcc788c7ea6b67 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 3 Nov 2021 15:29:40 +0100 Subject: [PATCH 1/6] Replace Context begin_frame/end_frame with `fn run` taking a closure --- CHANGELOG.md | 1 + README.md | 8 +-- egui-winit/src/epi.rs | 8 +-- egui/src/context.rs | 86 ++++++++++++++++++------------ egui/src/lib.rs | 16 +++--- egui/src/response.rs | 2 +- egui_demo_lib/benches/benchmark.rs | 48 +++++++++-------- egui_demo_lib/src/lib.rs | 12 ++--- egui_glium/src/lib.rs | 10 ++-- egui_glow/src/lib.rs | 8 +-- egui_web/src/backend.rs | 9 ++-- 11 files changed, 111 insertions(+), 97 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb6b490ed22..a745e4e51a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w * Add context menus: See `Ui::menu_button` and `Response::context_menu` ([#543](https://github.com/emilk/egui/pull/543)). * You can now read and write the cursor of a `TextEdit` ([#848](https://github.com/emilk/egui/pull/848)). * Most widgets containing text (`Label`, `Button` etc) now supports rich text ([#855](https://github.com/emilk/egui/pull/855)). +* Add `CtxRef::run` as a convenicene for calling `begin_frame` and `end_frame`. ### Changed 🔧 * Unifiy the four `Memory` data buckets (`data`, `data_temp`, `id_data` and `id_data_temp`) into a single `Memory::data`, with a new interface ([#836](https://github.com/emilk/egui/pull/836)). diff --git a/README.md b/README.md index 1cbe9e999af..92cd8f501b6 100644 --- a/README.md +++ b/README.md @@ -198,14 +198,14 @@ Missing an integration for the thing you're working on? Create one, it is easy! You need to collect [`egui::RawInput`](https://docs.rs/egui/latest/egui/struct.RawInput.html), paint [`egui::ClippedMesh`](https://docs.rs/epaint/):es and handle [`egui::Output`](https://docs.rs/egui/latest/egui/struct.Output.html). The basic structure is this: ``` rust -let mut egui_ctx = egui::Context::new(); +let mut egui_ctx = egui::CtxRef::default(); // Game loop: loop { let raw_input: egui::RawInput = my_integration.gather_input(); - egui_ctx.begin_frame(raw_input); - my_app.ui(&mut egui_ctx); // add panels, windows and widgets to `egui_ctx` here - let (output, shapes) = egui_ctx.end_frame(); + let (output, shapes) = egui_ctx.run(raw_input, |egui_ctx| { + my_app.ui(egui_ctx); // add panels, windows and widgets to `egui_ctx` here + }); let clipped_meshes = egui_ctx.tessellate(shapes); // create triangles to paint my_integration.paint(clipped_meshes); my_integration.set_cursor_icon(output.cursor_icon); diff --git a/egui-winit/src/epi.rs b/egui-winit/src/epi.rs index b1fba0b4688..2cad172f4d1 100644 --- a/egui-winit/src/epi.rs +++ b/egui-winit/src/epi.rs @@ -282,8 +282,6 @@ impl EpiIntegration { let raw_input = self.egui_winit.take_egui_input(window); - self.egui_ctx.begin_frame(raw_input); - let mut app_output = epi::backend::AppOutput::default(); let mut frame = epi::backend::FrameBuilder { info: integration_info(self.integration_name, window, self.latest_frame_time), @@ -293,9 +291,11 @@ impl EpiIntegration { } .build(); - self.app.update(&self.egui_ctx, &mut frame); + let app = &mut self.app; // TODO: remove when we update MSVR to 1.56 + let (egui_output, shapes) = self.egui_ctx.run(raw_input, |egui_ctx| { + app.update(egui_ctx, &mut frame); + }); - let (egui_output, shapes) = self.egui_ctx.end_frame(); let needs_repaint = egui_output.needs_repaint; self.egui_winit .handle_output(window, &self.egui_ctx, egui_output); diff --git a/egui/src/context.rs b/egui/src/context.rs index 24b147c3848..85be7ee21ac 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -36,16 +36,14 @@ use epaint::{stats::*, text::Fonts, *}; /// // Game loop: /// loop { /// let raw_input = egui::RawInput::default(); -/// ctx.begin_frame(raw_input); -/// -/// egui::CentralPanel::default().show(&ctx, |ui| { -/// ui.label("Hello world!"); -/// if ui.button("Click me").clicked() { -/// /* take some action here */ -/// } +/// let (output, shapes) = ctx.run(raw_input, |ctx| { +/// egui::CentralPanel::default().show(&ctx, |ui| { +/// ui.label("Hello world!"); +/// if ui.button("Click me").clicked() { +/// // take some action here +/// } +/// }); /// }); -/// -/// let (output, shapes) = ctx.end_frame(); /// let clipped_meshes = ctx.tessellate(shapes); // create triangles to paint /// handle_output(output); /// paint(clipped_meshes); @@ -93,6 +91,27 @@ impl Default for CtxRef { } impl CtxRef { + /// Run the ui code for one frame. + /// + /// Put your widgets into a [`SidePanel`], [`TopBottomPanel`], [`CentralPanel`], [`Window`] or [`Area`]. + /// + /// This will modify the internal reference to point to a new generation of [`Context`]. + /// Any old clones of this [`CtxRef`] will refer to the old [`Context`], which will not get new input. + /// + /// This is a convenience for calling [`Self::begin_frame`] and [`Context::end_frame`] + #[must_use] + pub fn run( + &mut self, + new_input: RawInput, + run_ui: impl FnOnce(&CtxRef), + ) -> (Output, Vec) { + self.begin_frame(new_input); + run_ui(self); + self.end_frame() + } + + /// Alternative to [`Self::run`]. + /// /// Call at the start of every frame. Match with a call to [`Context::end_frame`]. /// /// This will modify the internal reference to point to a new generation of [`Context`]. @@ -577,30 +596,7 @@ impl Context { self.input = input.begin_frame(new_raw_input); self.frame_state.lock().begin_frame(&self.input); - { - // Load new fonts if required: - let new_font_definitions = self.memory().new_font_definitions.take(); - let pixels_per_point = self.input.pixels_per_point(); - - let pixels_per_point_changed = match &self.fonts { - None => true, - Some(current_fonts) => { - (current_fonts.pixels_per_point() - pixels_per_point).abs() > 1e-3 - } - }; - - if self.fonts.is_none() || new_font_definitions.is_some() || pixels_per_point_changed { - self.fonts = Some(Arc::new(Fonts::new( - pixels_per_point, - new_font_definitions.unwrap_or_else(|| { - self.fonts - .as_ref() - .map(|font| font.definitions().clone()) - .unwrap_or_default() - }), - ))); - } - } + self.update_fonts(self.input.pixels_per_point()); // Ensure we register the background area so panels and background ui can catch clicks: let screen_rect = self.input.screen_rect(); @@ -614,6 +610,30 @@ impl Context { ); } + /// Load fonts unless already loaded. + fn update_fonts(&mut self, pixels_per_point: f32) { + let new_font_definitions = self.memory().new_font_definitions.take(); + + let pixels_per_point_changed = match &self.fonts { + None => true, + Some(current_fonts) => { + (current_fonts.pixels_per_point() - pixels_per_point).abs() > 1e-3 + } + }; + + if self.fonts.is_none() || new_font_definitions.is_some() || pixels_per_point_changed { + self.fonts = Some(Arc::new(Fonts::new( + pixels_per_point, + new_font_definitions.unwrap_or_else(|| { + self.fonts + .as_ref() + .map(|font| font.definitions().clone()) + .unwrap_or_default() + }), + ))); + } + } + /// Call at the end of each frame. /// Returns what has happened this frame [`crate::Output`] as well as what you need to paint. /// You can transform the returned shapes into triangles with a call to [`Context::tessellate`]. diff --git a/egui/src/lib.rs b/egui/src/lib.rs index 87d05283bb2..0bf3f169a25 100644 --- a/egui/src/lib.rs +++ b/egui/src/lib.rs @@ -51,7 +51,7 @@ //! ui.add(egui::Label::new("Hello World!")); //! ui.label("A shorter and more convenient way to add a label."); //! if ui.button("Click me").clicked() { -//! /* take some action here */ +//! // take some action here //! } //! }); //! ``` @@ -117,16 +117,16 @@ //! // Game loop: //! loop { //! let raw_input: egui::RawInput = gather_input(); -//! ctx.begin_frame(raw_input); //! -//! egui::CentralPanel::default().show(&ctx, |ui| { -//! ui.label("Hello world!"); -//! if ui.button("Click me").clicked() { -//! /* take some action here */ -//! } +//! let (output, shapes) = ctx.run(raw_input, |ctx| { +//! egui::CentralPanel::default().show(&ctx, |ui| { +//! ui.label("Hello world!"); +//! if ui.button("Click me").clicked() { +//! // take some action here +//! } +//! }); //! }); //! -//! let (output, shapes) = ctx.end_frame(); //! let clipped_meshes = ctx.tessellate(shapes); // create triangles to paint //! handle_output(output); //! paint(clipped_meshes); diff --git a/egui/src/response.rs b/egui/src/response.rs index b00b163f712..ac36987dc14 100644 --- a/egui/src/response.rs +++ b/egui/src/response.rs @@ -480,7 +480,7 @@ impl Response { /// ``` rust /// # let mut ui = &mut egui::Ui::__test(); /// let response = ui.label("Right-click me!"); - /// response.context_menu(|ui|{ + /// response.context_menu(|ui| { /// if ui.button("Close the menu").clicked() { /// ui.close_menu(); /// } diff --git a/egui_demo_lib/benches/benchmark.rs b/egui_demo_lib/benches/benchmark.rs index 70e4ad31675..51088d6af22 100644 --- a/egui_demo_lib/benches/benchmark.rs +++ b/egui_demo_lib/benches/benchmark.rs @@ -13,24 +13,24 @@ pub fn criterion_benchmark(c: &mut Criterion) { // The most end-to-end benchmark. c.bench_function("demo_with_tessellate__realistic", |b| { b.iter(|| { - ctx.begin_frame(raw_input.clone()); - demo_windows.ui(&ctx); - let (_, shapes) = ctx.end_frame(); + let (_output, shapes) = ctx.run(raw_input.clone(), |ctx| { + demo_windows.ui(ctx); + }); ctx.tessellate(shapes) }) }); c.bench_function("demo_no_tessellate", |b| { b.iter(|| { - ctx.begin_frame(raw_input.clone()); - demo_windows.ui(&ctx); - ctx.end_frame() + ctx.run(raw_input.clone(), |ctx| { + demo_windows.ui(ctx); + }) }) }); - ctx.begin_frame(raw_input.clone()); - demo_windows.ui(&ctx); - let (_, shapes) = ctx.end_frame(); + let (_output, shapes) = ctx.run(raw_input.clone(), |ctx| { + demo_windows.ui(ctx); + }); c.bench_function("demo_only_tessellate", |b| { b.iter(|| ctx.tessellate(shapes.clone())) }); @@ -42,26 +42,28 @@ pub fn criterion_benchmark(c: &mut Criterion) { let mut demo_windows = egui_demo_lib::DemoWindows::default(); c.bench_function("demo_full_no_tessellate", |b| { b.iter(|| { - ctx.begin_frame(raw_input.clone()); - demo_windows.ui(&ctx); - ctx.end_frame() + ctx.run(raw_input.clone(), |ctx| { + demo_windows.ui(ctx); + }) }) }); } { let mut ctx = egui::CtxRef::default(); - ctx.begin_frame(raw_input); - let mut ui = egui::Ui::__test(); - c.bench_function("label &str", |b| { - b.iter(|| { - ui.label("the quick brown fox jumps over the lazy dog"); - }) - }); - c.bench_function("label format!", |b| { - b.iter(|| { - ui.label("the quick brown fox jumps over the lazy dog".to_owned()); - }) + let _ = ctx.run(raw_input.clone(), |ctx| { + egui::CentralPanel::default().show(ctx, |ui| { + c.bench_function("label &str", |b| { + b.iter(|| { + ui.label("the quick brown fox jumps over the lazy dog"); + }) + }); + c.bench_function("label format!", |b| { + b.iter(|| { + ui.label("the quick brown fox jumps over the lazy dog".to_owned()); + }) + }); + }); }); } diff --git a/egui_demo_lib/src/lib.rs b/egui_demo_lib/src/lib.rs index 974e4081795..0d7ffb06a7e 100644 --- a/egui_demo_lib/src/lib.rs +++ b/egui_demo_lib/src/lib.rs @@ -145,9 +145,9 @@ fn test_egui_e2e() { const NUM_FRAMES: usize = 5; for _ in 0..NUM_FRAMES { - ctx.begin_frame(raw_input.clone()); - demo_windows.ui(&ctx); - let (_output, shapes) = ctx.end_frame(); + let (_output, shapes) = ctx.run(raw_input.clone(), |ctx| { + demo_windows.ui(ctx); + }); let clipped_meshes = ctx.tessellate(shapes); assert!(!clipped_meshes.is_empty()); } @@ -164,9 +164,9 @@ fn test_egui_zero_window_size() { const NUM_FRAMES: usize = 5; for _ in 0..NUM_FRAMES { - ctx.begin_frame(raw_input.clone()); - demo_windows.ui(&ctx); - let (_output, shapes) = ctx.end_frame(); + let (_output, shapes) = ctx.run(raw_input.clone(), |ctx| { + demo_windows.ui(ctx); + }); let clipped_meshes = ctx.tessellate(shapes); assert!(clipped_meshes.is_empty(), "There should be nothing to show"); } diff --git a/egui_glium/src/lib.rs b/egui_glium/src/lib.rs index 66e04cd6087..750ac1ff85e 100644 --- a/egui_glium/src/lib.rs +++ b/egui_glium/src/lib.rs @@ -101,7 +101,7 @@ use glium::glutin; // ---------------------------------------------------------------------------- -/// Use [`egui`] from a [`glium`] app. +/// Convenience wrapper for using [`egui`] from a [`glium`] app. pub struct EguiGlium { pub egui_ctx: egui::CtxRef, pub egui_winit: egui_winit::State, @@ -136,16 +136,12 @@ impl EguiGlium { pub fn run( &mut self, display: &glium::Display, - mut run_ui: impl FnMut(&egui::CtxRef), + run_ui: impl FnMut(&egui::CtxRef), ) -> (bool, Vec) { let raw_input = self .egui_winit .take_egui_input(display.gl_window().window()); - self.egui_ctx.begin_frame(raw_input); - - run_ui(&self.egui_ctx); - - let (egui_output, shapes) = self.egui_ctx.end_frame(); + let (egui_output, shapes) = self.egui_ctx.run(raw_input, run_ui); let needs_repaint = egui_output.needs_repaint; self.egui_winit .handle_output(display.gl_window().window(), &self.egui_ctx, egui_output); diff --git a/egui_glow/src/lib.rs b/egui_glow/src/lib.rs index 9b759c96cbb..e60f811df9a 100644 --- a/egui_glow/src/lib.rs +++ b/egui_glow/src/lib.rs @@ -150,14 +150,10 @@ impl EguiGlow { pub fn run( &mut self, window: &glutin::window::Window, - mut run_ui: impl FnMut(&egui::CtxRef), + run_ui: impl FnMut(&egui::CtxRef), ) -> (bool, Vec) { let raw_input = self.egui_winit.take_egui_input(window); - self.egui_ctx.begin_frame(raw_input); - - run_ui(&self.egui_ctx); - - let (egui_output, shapes) = self.egui_ctx.end_frame(); + let (egui_output, shapes) = self.egui_ctx.run(raw_input, run_ui); let needs_repaint = egui_output.needs_repaint; self.egui_winit .handle_output(window, &self.egui_ctx, egui_output); diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index cc63f725b02..d1118f52489 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -189,8 +189,6 @@ impl AppRunner { let canvas_size = canvas_size_in_points(self.canvas_id()); let raw_input = self.input.new_frame(canvas_size); - 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(), @@ -200,9 +198,10 @@ impl AppRunner { } .build(); - self.app.update(&self.egui_ctx, &mut frame); - - let (egui_output, shapes) = self.egui_ctx.end_frame(); + let app = &mut self.app; // TODO: remove when we bump MSRV to 1.56 + let (egui_output, shapes) = self.egui_ctx.run(raw_input, |egui_ctx| { + app.update(egui_ctx, &mut frame); + }); let clipped_meshes = self.egui_ctx.tessellate(shapes); self.handle_egui_output(&egui_output); From 5ba41928c653555dd0f209faed16c36350852051 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 3 Nov 2021 15:52:58 +0100 Subject: [PATCH 2/6] Create `egui::__run_test_ui` to replace `Ui::__test` Uses a closure instead so we don't need Context::begin_frame --- CHANGELOG.md | 1 + egui/src/containers/collapsing_header.rs | 9 ++- egui/src/containers/combo_box.rs | 6 +- egui/src/containers/popup.rs | 12 ++-- egui/src/containers/scroll_area.rs | 8 ++- egui/src/grid.rs | 3 +- egui/src/input_state.rs | 3 +- egui/src/layout.rs | 3 +- egui/src/lib.rs | 48 +++++++++---- egui/src/response.rs | 18 +++-- egui/src/ui.rs | 88 ++++++++++++++---------- egui/src/widgets/button.rs | 9 ++- egui/src/widgets/drag_value.rs | 3 +- egui/src/widgets/hyperlink.rs | 3 +- egui/src/widgets/image.rs | 3 +- egui/src/widgets/label.rs | 6 +- egui/src/widgets/plot/mod.rs | 3 +- egui/src/widgets/selected_label.rs | 3 +- egui/src/widgets/separator.rs | 3 +- egui/src/widgets/slider.rs | 3 +- egui/src/widgets/text_edit/builder.rs | 9 ++- 21 files changed, 161 insertions(+), 83 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a745e4e51a2..7327992d70b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w ### Changed 🔧 * Unifiy the four `Memory` data buckets (`data`, `data_temp`, `id_data` and `id_data_temp`) into a single `Memory::data`, with a new interface ([#836](https://github.com/emilk/egui/pull/836)). * `ui.add(Button::new("…").text_color(…))` is now `ui.button(RichText::new("…").color(…))` (same for `Label` )([#855](https://github.com/emilk/egui/pull/855)). +* Replace `Ui::__test` with `egui::__run_test_ui`. ### Contributors 🙏 * [mankinskin](https://github.com/mankinskin) ([#543](https://github.com/emilk/egui/pull/543)) diff --git a/egui/src/containers/collapsing_header.rs b/egui/src/containers/collapsing_header.rs index 97e04e20088..9618b5b860c 100644 --- a/egui/src/containers/collapsing_header.rs +++ b/egui/src/containers/collapsing_header.rs @@ -130,7 +130,7 @@ pub(crate) fn paint_icon(ui: &mut Ui, openness: f32, response: &Response) { /// /// /// ``` -/// # let ui = &mut egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// egui::CollapsingHeader::new("Heading") /// .show(ui, |ui| { /// ui.label("Contents"); @@ -138,6 +138,7 @@ pub(crate) fn paint_icon(ui: &mut Ui, openness: f32, response: &Response) { /// /// // Short version: /// ui.collapsing("Heading", |ui| { ui.label("Contents"); }); +/// # }); /// ``` #[must_use = "You should call .show()"] pub struct CollapsingHeader { @@ -210,7 +211,7 @@ impl CollapsingHeader { /// /// Example: /// ``` - /// # let ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// let mut selected = false; /// let response = egui::CollapsingHeader::new("Select and open me") /// .selectable(true) @@ -219,6 +220,7 @@ impl CollapsingHeader { /// if response.header_response.clicked() { /// selected = true; /// } + /// # }); /// ``` pub fn selected(mut self, selected: bool) -> Self { self.selected = selected; @@ -229,8 +231,9 @@ impl CollapsingHeader { /// /// To show it behind all `CollapsingHeader` you can just use: /// ``` - /// # let ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// ui.visuals_mut().collapsing_header_frame = true; + /// # }); /// ``` pub fn show_background(mut self, show_background: bool) -> Self { self.show_background = show_background; diff --git a/egui/src/containers/combo_box.rs b/egui/src/containers/combo_box.rs index 3589fa12584..71c4f06358a 100644 --- a/egui/src/containers/combo_box.rs +++ b/egui/src/containers/combo_box.rs @@ -7,7 +7,7 @@ use epaint::Shape; /// # #[derive(Debug, PartialEq)] /// # enum Enum { First, Second, Third } /// # let mut selected = Enum::First; -/// # let mut ui = &mut egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// egui::ComboBox::from_label("Select one!") /// .selected_text(format!("{:?}", selected)) /// .show_ui(ui, |ui| { @@ -16,6 +16,7 @@ use epaint::Shape; /// ui.selectable_value(&mut selected, Enum::Third, "Third"); /// } /// ); +/// # }); /// ``` #[must_use = "You should call .show*"] pub struct ComboBox { @@ -109,7 +110,7 @@ impl ComboBox { /// # #[derive(Debug, PartialEq)] /// # enum Enum { First, Second, Third } /// # let mut selected = Enum::First; - /// # let mut ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// let alternatives = ["a", "b", "c", "d"]; /// let mut selected = 2; /// egui::ComboBox::from_label("Select one!").show_index( @@ -118,6 +119,7 @@ impl ComboBox { /// alternatives.len(), /// |i| alternatives[i].to_owned() /// ); + /// # }); /// ``` pub fn show_index( self, diff --git a/egui/src/containers/popup.rs b/egui/src/containers/popup.rs index 30f9266d1dc..5549fa10c52 100644 --- a/egui/src/containers/popup.rs +++ b/egui/src/containers/popup.rs @@ -58,12 +58,13 @@ impl MonoState { /// Returns `None` if the tooltip could not be placed. /// /// ``` -/// # let mut ui = egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// if ui.ui_contains_pointer() { /// egui::show_tooltip(ui.ctx(), egui::Id::new("my_tooltip"), |ui| { /// ui.label("Helpful text"); /// }); /// } +/// # }); /// ``` pub fn show_tooltip(ctx: &CtxRef, id: Id, add_contents: impl FnOnce(&mut Ui) -> R) -> Option { show_tooltip_at_pointer(ctx, id, add_contents) @@ -78,12 +79,13 @@ pub fn show_tooltip(ctx: &CtxRef, id: Id, add_contents: impl FnOnce(&mut Ui) /// Returns `None` if the tooltip could not be placed. /// /// ``` -/// # let mut ui = egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// if ui.ui_contains_pointer() { /// egui::show_tooltip_at_pointer(ui.ctx(), egui::Id::new("my_tooltip"), |ui| { /// ui.label("Helpful text"); /// }); /// } +/// # }); /// ``` pub fn show_tooltip_at_pointer( ctx: &CtxRef, @@ -221,10 +223,11 @@ fn show_tooltip_at_avoid_dyn<'c, R>( /// Returns `None` if the tooltip could not be placed. /// /// ``` -/// # let mut ui = egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// if ui.ui_contains_pointer() { /// egui::show_tooltip_text(ui.ctx(), egui::Id::new("my_tooltip"), "Helpful text"); /// } +/// # }); /// ``` pub fn show_tooltip_text(ctx: &CtxRef, id: Id, text: impl Into) -> Option<()> { show_tooltip(ctx, id, |ui| { @@ -264,7 +267,7 @@ fn show_tooltip_area_dyn<'c, R>( /// Returns `None` if the popup is not open. /// /// ``` -/// # let ui = &mut egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// let response = ui.button("Open popup"); /// let popup_id = ui.make_persistent_id("my_unique_id"); /// if response.clicked() { @@ -275,6 +278,7 @@ fn show_tooltip_area_dyn<'c, R>( /// ui.label("Some more info, or things you can select:"); /// ui.label("…"); /// }); +/// # }); /// ``` pub fn popup_below_widget( ui: &Ui, diff --git a/egui/src/containers/scroll_area.rs b/egui/src/containers/scroll_area.rs index cc1251d8ad9..e6a70530c4f 100644 --- a/egui/src/containers/scroll_area.rs +++ b/egui/src/containers/scroll_area.rs @@ -54,10 +54,12 @@ impl State { /// Add vertical and/or horizontal scrolling to a contained [`Ui`]. /// /// ``` -/// # let ui = &mut egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// egui::ScrollArea::vertical().show(ui, |ui| { /// // Add a lot of widgets here. /// }); +/// # }); +/// ``` #[derive(Clone, Debug)] #[must_use = "You should call .show()"] pub struct ScrollArea { @@ -370,7 +372,7 @@ impl ScrollArea { /// Efficiently show only the visible part of a large number of rows. /// /// ``` - /// # let ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// let text_style = egui::TextStyle::Body; /// let row_height = ui.fonts()[text_style].row_height(); /// // let row_height = ui.spacing().interact_size.y; // if you are adding buttons instead of labels. @@ -381,6 +383,8 @@ impl ScrollArea { /// ui.label(text); /// } /// }); + /// # }); + /// ``` pub fn show_rows( self, ui: &mut Ui, diff --git a/egui/src/grid.rs b/egui/src/grid.rs index f29d61414fa..2aba3ca63a6 100644 --- a/egui/src/grid.rs +++ b/egui/src/grid.rs @@ -238,7 +238,7 @@ impl GridLayout { /// [`Ui::horizontal`], [`Ui::vertical`] etc. /// /// ``` -/// # let ui = &mut egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// egui::Grid::new("some_unique_id").show(ui, |ui| { /// ui.label("First row, first column"); /// ui.label("First row, second column"); @@ -253,6 +253,7 @@ impl GridLayout { /// ui.label("Third row, second column"); /// ui.end_row(); /// }); +/// # }); /// ``` #[must_use = "You should call .show()"] pub struct Grid { diff --git a/egui/src/input_state.rs b/egui/src/input_state.rs index 80d658c6fe6..196e9a7e93a 100644 --- a/egui/src/input_state.rs +++ b/egui/src/input_state.rs @@ -237,7 +237,7 @@ impl InputState { /// /// ``` /// # use egui::emath::Rot2; - /// # let ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// let mut zoom = 1.0; // no zoom /// let mut rotation = 0.0; // no rotation /// if let Some(multi_touch) = ui.input().multi_touch() { @@ -245,6 +245,7 @@ impl InputState { /// rotation += multi_touch.rotation_delta; /// } /// let transform = zoom * Rot2::from_angle(rotation); + /// # }); /// ``` /// /// By far not all touch devices are supported, and the details depend on the `egui` diff --git a/egui/src/layout.rs b/egui/src/layout.rs index f0c9042db9f..433242cafb5 100644 --- a/egui/src/layout.rs +++ b/egui/src/layout.rs @@ -108,11 +108,12 @@ impl Direction { /// The layout of a [`Ui`][`crate::Ui`], e.g. "vertical & centered". /// /// ``` -/// # let ui = &mut egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// ui.with_layout(egui::Layout::right_to_left(), |ui| { /// ui.label("world!"); /// ui.label("Hello"); /// }); +/// # }); /// ``` #[derive(Clone, Copy, Debug, PartialEq)] // #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] diff --git a/egui/src/lib.rs b/egui/src/lib.rs index 0bf3f169a25..cc2f907f3fc 100644 --- a/egui/src/lib.rs +++ b/egui/src/lib.rs @@ -59,7 +59,7 @@ //! ### Quick start //! //! ``` rust -//! # let ui = &mut egui::Ui::__test(); +//! # egui::__run_test_ui(|ui| { //! # let mut my_string = String::new(); //! # let mut my_boolean = true; //! # let mut my_f32 = 42.0; @@ -89,6 +89,7 @@ //! ui.collapsing("Click to see what is hidden!", |ui| { //! ui.label("Not much, as it turns out"); //! }); +//! # }); //! ``` //! //! ## Conventions @@ -141,10 +142,11 @@ //! Here is an example to illustrate it: //! //! ``` -//! # let ui = &mut egui::Ui::__test(); +//! # egui::__run_test_ui(|ui| { //! if ui.button("click me").clicked() { //! take_action() //! } +//! # }); //! # fn take_action() {} //! ``` //! @@ -166,17 +168,19 @@ //! ## How widgets works //! //! ``` -//! # let ui = &mut egui::Ui::__test(); +//! # egui::__run_test_ui(|ui| { //! if ui.button("click me").clicked() { take_action() } +//! # }); //! # fn take_action() {} //! ``` //! //! is short for //! //! ``` -//! # let ui = &mut egui::Ui::__test(); +//! # egui::__run_test_ui(|ui| { //! let button = egui::Button::new("click me"); //! if ui.add(button).clicked() { take_action() } +//! # }); //! # fn take_action() {} //! ``` //! @@ -184,10 +188,11 @@ //! //! ``` //! # use egui::Widget; -//! # let ui = &mut egui::Ui::__test(); +//! # egui::__run_test_ui(|ui| { //! let button = egui::Button::new("click me"); //! let response = button.ui(ui); //! if response.clicked() { take_action() } +//! # }); //! # fn take_action() {} //! ``` //! @@ -214,32 +219,35 @@ //! 3. Use a justified layout: //! //! ``` rust -//! # let ui = &mut egui::Ui::__test(); +//! # egui::__run_test_ui(|ui| { //! ui.with_layout(egui::Layout::top_down_justified(egui::Align::Center), |ui| { //! ui.button("I am becoming wider as needed"); //! }); +//! # }); //! ``` //! //! 4. Fill in extra space with emptiness: //! //! ``` rust -//! # let ui = &mut egui::Ui::__test(); +//! # egui::__run_test_ui(|ui| { //! ui.allocate_space(ui.available_size()); // put this LAST in your panel/window code +//! # }); //! ``` //! //! ## Sizes //! You can control the size of widgets using [`Ui::add_sized`]. //! //! ``` -//! # let ui = &mut egui::Ui::__test(); +//! # egui::__run_test_ui(|ui| { //! # let mut my_value = 0.0_f32; //! ui.add_sized([40.0, 20.0], egui::DragValue::new(&mut my_value)); +//! # }); //! ``` //! //! ## Code snippets //! //! ``` -//! # let ui = &mut egui::Ui::__test(); +//! # egui::__run_test_ui(|ui| { //! # let mut some_bool = true; //! // Miscellaneous tips and tricks //! @@ -263,6 +271,7 @@ //! //! ui.label("This text will be red, monospace, and won't wrap to a new line"); //! }); // the temporary settings are reverted here +//! # }); //! ``` // Forbid warnings in release builds: @@ -432,8 +441,9 @@ pub fn warn_if_debug_build(ui: &mut crate::Ui) { /// Create a [`Hyperlink`](crate::Hyperlink) to the current [`file!()`] (and line) on Github /// /// ``` -/// # let ui = &mut egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// ui.add(egui::github_link_file_line!("https://github.com/YOUR/PROJECT/blob/master/", "(source code)")); +/// # }); /// ``` #[macro_export] macro_rules! github_link_file_line { @@ -446,8 +456,9 @@ macro_rules! github_link_file_line { /// Create a [`Hyperlink`](crate::Hyperlink) to the current [`file!()`] on github. /// /// ``` -/// # let ui = &mut egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// ui.add(egui::github_link_file!("https://github.com/YOUR/PROJECT/blob/master/", "(source code)")); +/// # }); /// ``` #[macro_export] macro_rules! github_link_file { @@ -462,7 +473,7 @@ macro_rules! github_link_file { /// Show debug info on hover when [`Context::set_debug_on_hover`] has been turned on. /// /// ``` -/// # let ui = &mut egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// // Turn on tracing of widgets /// ui.ctx().set_debug_on_hover(true); /// @@ -471,6 +482,7 @@ macro_rules! github_link_file { /// /// /// Show [`std::file`] and [`std::line`] on hover /// egui::trace!(ui); +/// # }); /// ``` #[macro_export] macro_rules! trace { @@ -561,3 +573,15 @@ pub enum WidgetType { /// If this is something you think should be added, file an issue. Other, } + +// ---------------------------------------------------------------------------- + +/// For use in tests; especially doctests. +pub fn __run_test_ui(mut add_contents: impl FnMut(&mut Ui)) { + let mut ctx = CtxRef::default(); + let _ = ctx.run(Default::default(), |ctx| { + crate::CentralPanel::default().show(ctx, |ui| { + add_contents(ui); + }); + }); +} diff --git a/egui/src/response.rs b/egui/src/response.rs index ac36987dc14..177f66cd1b1 100644 --- a/egui/src/response.rs +++ b/egui/src/response.rs @@ -200,13 +200,14 @@ impl Response { /// or (in case of a [`crate::TextEdit`]) because the user pressed enter. /// /// ``` - /// # let mut ui = egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// # let mut my_text = String::new(); /// # fn do_request(_: &str) {} /// let response = ui.text_edit_singleline(&mut my_text); /// if response.lost_focus() && ui.input().key_pressed(egui::Key::Enter) { /// do_request(&my_text); /// } + /// # }); /// ``` pub fn lost_focus(&self) -> bool { self.ctx.memory().lost_focus(self.id) @@ -415,11 +416,12 @@ impl Response { /// it is better to give the widget a `Sense` instead, e.g. using [`crate::Label::sense`]. /// /// ``` - /// # let mut ui = egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// let response = ui.label("hello"); /// assert!(!response.clicked()); // labels don't sense clicks by default /// let response = response.interact(egui::Sense::click()); /// if response.clicked() { /* … */ } + /// # }); /// ``` pub fn interact(&self, sense: Sense) -> Self { self.ctx.interact_with_hovered( @@ -436,7 +438,7 @@ impl Response { /// /// ``` /// # use egui::Align; - /// # let mut ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// egui::ScrollArea::vertical().show(ui, |ui| { /// for i in 0..1000 { /// let response = ui.button(format!("Button {}", i)); @@ -445,6 +447,7 @@ impl Response { /// } /// } /// }); + /// # }); /// ``` pub fn scroll_to_me(&self, align: Align) { let scroll_target = lerp(self.rect.x_range(), align.to_factor()); @@ -478,13 +481,14 @@ impl Response { /// Response to secondary clicks (right-clicks) by showing the given menu. /// /// ``` rust - /// # let mut ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// let response = ui.label("Right-click me!"); /// response.context_menu(|ui| { /// if ui.button("Close the menu").clicked() { /// ui.close_menu(); /// } /// }); + /// # }); /// ``` pub fn context_menu(self, add_contents: impl FnOnce(&mut Ui)) -> Self { self.ctx.show_context_menu(&self, add_contents); @@ -549,12 +553,13 @@ impl std::ops::BitOr for Response { /// To summarize the response from many widgets you can use this pattern: /// /// ``` -/// # let mut ui = egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// # let (widget_a, widget_b, widget_c) = (egui::Label::new("a"), egui::Label::new("b"), egui::Label::new("c")); /// let mut response = ui.add(widget_a); /// response |= ui.add(widget_b); /// response |= ui.add(widget_c); /// if response.hovered() { ui.label("You hovered at least one of the widgets"); } +/// # }); /// ``` impl std::ops::BitOrAssign for Response { fn bitor_assign(&mut self, rhs: Self) { @@ -568,13 +573,14 @@ impl std::ops::BitOrAssign for Response { /// the results of the inner function and the ui as a whole, e.g.: /// /// ``` -/// # let ui = &mut egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// let inner_resp = ui.horizontal(|ui| { /// ui.label("Blah blah"); /// 42 /// }); /// inner_resp.response.on_hover_text("You hovered the horizontal layout"); /// assert_eq!(inner_resp.inner, 42); +/// # }); /// ``` #[derive(Debug)] pub struct InnerResponse { diff --git a/egui/src/ui.rs b/egui/src/ui.rs index fe798d0a6fc..f95c04985e5 100644 --- a/egui/src/ui.rs +++ b/egui/src/ui.rs @@ -16,7 +16,7 @@ use crate::{ /// Represents a region of the screen with a type of layout (horizontal or vertical). /// /// ``` -/// # let mut ui = egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// ui.add(egui::Label::new("Hello World!")); /// ui.label("A shorter and more convenient way to add a label."); /// ui.horizontal(|ui| { @@ -25,6 +25,7 @@ use crate::{ /// /* … */ /// } /// }); +/// # }); /// ``` pub struct Ui { /// ID of this ui. @@ -109,16 +110,6 @@ impl Ui { } } - /// Empty `Ui` for use in tests. - pub fn __test() -> Self { - let mut ctx = CtxRef::default(); - ctx.begin_frame(Default::default()); - let id = Id::new("__test"); - let layer_id = LayerId::new(Order::Middle, id); - let rect = Rect::from_min_size(Pos2::new(0.0, 0.0), vec2(1000.0, 1000.0)); - Self::new(ctx, layer_id, id, rect, rect) - } - // ------------------------------------------------- /// A unique identity of this `Ui`. @@ -140,8 +131,9 @@ impl Ui { /// /// Example: /// ``` - /// # let ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// ui.style_mut().body_text_style = egui::TextStyle::Heading; + /// # }); /// ``` pub fn style_mut(&mut self) -> &mut Style { std::sync::Arc::make_mut(&mut self.style) // clone-on-write @@ -171,8 +163,9 @@ impl Ui { /// /// Example: /// ``` - /// # let ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// ui.spacing_mut().item_spacing = egui::vec2(10.0, 2.0); + /// # }); /// ``` pub fn spacing_mut(&mut self) -> &mut crate::style::Spacing { &mut self.style_mut().spacing @@ -192,8 +185,9 @@ impl Ui { /// /// Example: /// ``` - /// # let ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// ui.visuals_mut().override_text_color = Some(egui::Color32::RED); + /// # }); /// ``` pub fn visuals_mut(&mut self) -> &mut crate::Visuals { &mut self.style_mut().visuals @@ -227,7 +221,7 @@ impl Ui { /// /// ### Example /// ``` - /// # let ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// # let mut enabled = true; /// ui.group(|ui| { /// ui.checkbox(&mut enabled, "Enable subsection"); @@ -236,6 +230,7 @@ impl Ui { /// /* … */ /// } /// }); + /// # }); /// ``` pub fn set_enabled(&mut self, enabled: bool) { self.enabled &= enabled; @@ -260,7 +255,7 @@ impl Ui { /// /// ### Example /// ``` - /// # let ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// # let mut visible = true; /// ui.group(|ui| { /// ui.checkbox(&mut visible, "Show subsection"); @@ -269,6 +264,7 @@ impl Ui { /// /* … */ /// } /// }); + /// # }); /// ``` pub fn set_visible(&mut self, visible: bool) { self.set_enabled(visible); @@ -593,10 +589,11 @@ impl Ui { /// You will never get a rectangle that is smaller than the amount of space you asked for. /// /// ``` - /// # let mut ui = egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// let response = ui.allocate_response(egui::vec2(100.0, 200.0), egui::Sense::click()); /// if response.clicked() { /* … */ } /// ui.painter().rect_stroke(response.rect, 0.0, (1.0, egui::Color32::WHITE)); + /// # }); /// ``` pub fn allocate_response(&mut self, desired_size: Vec2, sense: Sense) -> Response { let (id, rect) = self.allocate_space(desired_size); @@ -640,9 +637,10 @@ impl Ui { /// Returns an automatic `Id` (which you can use for interaction) and the `Rect` of where to put your widget. /// /// ``` - /// # let mut ui = egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// let (id, rect) = ui.allocate_space(egui::vec2(100.0, 200.0)); /// let response = ui.interact(rect, id, egui::Sense::click()); + /// # }); /// ``` pub fn allocate_space(&mut self, desired_size: Vec2) -> (Id, Rect) { // For debug rendering @@ -833,8 +831,8 @@ impl Ui { /// /// ``` /// # use egui::*; - /// # let mut ui = &mut egui::Ui::__test(); /// # use std::f32::consts::TAU; + /// # egui::__run_test_ui(|ui| { /// let size = Vec2::splat(16.0); /// let (response, painter) = ui.allocate_painter(size, Sense::hover()); /// let rect = response.rect; @@ -846,6 +844,7 @@ impl Ui { /// painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke); /// painter.line_segment([c, c + r * Vec2::angled(TAU * 1.0 / 8.0)], stroke); /// painter.line_segment([c, c + r * Vec2::angled(TAU * 3.0 / 8.0)], stroke); + /// # }); /// ``` pub fn allocate_painter(&mut self, desired_size: Vec2, sense: Sense) -> (Response, Painter) { let response = self.allocate_response(desired_size, sense); @@ -858,7 +857,7 @@ impl Ui { /// /// ``` /// # use egui::Align; - /// # let mut ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// egui::ScrollArea::vertical().show(ui, |ui| { /// let scroll_bottom = ui.button("Scroll to bottom.").clicked(); /// for i in 0..1000 { @@ -869,6 +868,7 @@ impl Ui { /// ui.scroll_to_cursor(Align::BOTTOM); /// } /// }); + /// # }); /// ``` pub fn scroll_to_cursor(&mut self, align: Align) { let target = self.next_widget_position(); @@ -888,10 +888,11 @@ impl Ui { /// See also [`Self::add_sized`] and [`Self::put`]. /// /// ``` - /// # let mut ui = egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// # let mut my_value = 42; /// let response = ui.add(egui::Slider::new(&mut my_value, 0..=100)); /// response.on_hover_text("Drag me!"); + /// # }); /// ``` #[inline] pub fn add(&mut self, widget: impl Widget) -> Response { @@ -906,9 +907,10 @@ impl Ui { /// See also [`Self::add`] and [`Self::put`]. /// /// ``` - /// # let mut ui = egui::Ui::__test(); /// # let mut my_value = 42; + /// # egui::__run_test_ui(|ui| { /// ui.add_sized([40.0, 20.0], egui::DragValue::new(&mut my_value)); + /// # }); /// ``` pub fn add_sized(&mut self, max_size: impl Into, widget: impl Widget) -> Response { // TODO: configure to overflow to main_dir instead of centered overflow @@ -939,8 +941,9 @@ impl Ui { /// See also [`Self::add_enabled_ui`] and [`Self::is_enabled`]. /// /// ``` - /// # let ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// ui.add_enabled(false, egui::Button::new("Can't click this")); + /// # }); /// ``` pub fn add_enabled(&mut self, enabled: bool, widget: impl Widget) -> Response { if enabled || !self.is_enabled() { @@ -964,7 +967,7 @@ impl Ui { /// /// ### Example /// ``` - /// # let ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// # let mut enabled = true; /// ui.checkbox(&mut enabled, "Enable subsection"); /// ui.add_enabled_ui(enabled, |ui| { @@ -972,6 +975,7 @@ impl Ui { /// /* … */ /// } /// }); + /// # }); /// ``` pub fn add_enabled_ui( &mut self, @@ -1061,8 +1065,9 @@ impl Ui { /// Shortcut for `add(Hyperlink::new(url).text(label))` /// /// ``` - /// # let ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// ui.hyperlink_to("egui on GitHub", "https://www.github.com/emilk/egui/"); + /// # }); /// ``` /// /// See also [`Hyperlink`]. @@ -1106,7 +1111,7 @@ impl Ui { /// See also [`Button`]. /// /// ``` - /// # let ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// if ui.button("Click me!").clicked() { /// // … /// } @@ -1115,6 +1120,7 @@ impl Ui { /// if ui.button(RichText::new("delete").color(Color32::RED)).clicked() { /// // … /// } + /// # }); /// ``` #[must_use = "You should check if the user clicked this with `if ui.button(…).clicked() { … } "] #[inline] @@ -1150,7 +1156,7 @@ impl Ui { /// If clicked, `selected_value` is assigned to `*current_value`. /// /// ``` - /// # let ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// /// #[derive(PartialEq)] /// enum Enum { First, Second, Third } @@ -1163,6 +1169,7 @@ impl Ui { /// if ui.add(egui::RadioButton::new(my_enum == Enum::First, "First")).clicked() { /// my_enum = Enum::First /// } + /// # }); /// ``` pub fn radio_value( &mut self, @@ -1349,10 +1356,11 @@ impl Ui { /// Put into a [`Frame::group`], visually grouping the contents together /// /// ``` - /// # let ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// ui.group(|ui| { /// ui.label("Within a frame"); /// }); + /// # }); /// ``` /// /// Se also [`Self::scope`]. @@ -1365,11 +1373,12 @@ impl Ui { /// You can use this to temporarily change the [`Style`] of a sub-region, for instance: /// /// ``` - /// # let ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// ui.scope(|ui| { /// ui.spacing_mut().slider_width = 200.0; // Temporary change /// // … /// }); + /// # }); /// ``` pub fn scope(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse { self.scope_dyn(Box::new(add_contents)) @@ -1483,11 +1492,12 @@ impl Ui { /// It also contains the `Rect` used by the horizontal layout. /// /// ``` - /// # let ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// ui.horizontal(|ui| { /// ui.label("Same"); /// ui.label("row"); /// }); + /// # }); /// ``` /// /// See also [`Self::with_layout`] for more options. @@ -1557,11 +1567,12 @@ impl Ui { /// Widgets will be left-justified. /// /// ``` - /// # let ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// ui.vertical(|ui| { /// ui.label("over"); /// ui.label("under"); /// }); + /// # }); /// ``` /// /// See also [`Self::with_layout`] for more options. @@ -1574,11 +1585,12 @@ impl Ui { /// Widgets will be horizontally centered. /// /// ``` - /// # let ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// ui.vertical_centered(|ui| { /// ui.label("over"); /// ui.label("under"); /// }); + /// # }); /// ``` #[inline] pub fn vertical_centered( @@ -1592,11 +1604,12 @@ impl Ui { /// Widgets will be horizontally centered and justified (fill full width). /// /// ``` - /// # let ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// ui.vertical_centered_justified(|ui| { /// ui.label("over"); /// ui.label("under"); /// }); + /// # }); /// ``` pub fn vertical_centered_justified( &mut self, @@ -1611,11 +1624,12 @@ impl Ui { /// The new layout will take up all available space. /// /// ``` - /// # let ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// ui.with_layout(egui::Layout::right_to_left(), |ui| { /// ui.label("world!"); /// ui.label("Hello"); /// }); + /// # }); /// ``` /// /// See also [`Self::allocate_ui_with_layout`], @@ -1687,11 +1701,12 @@ impl Ui { /// Temporarily split split an Ui into several columns. /// /// ``` - /// # let mut ui = egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// ui.columns(2, |columns| { /// columns[0].label("First column"); /// columns[1].label("Second column"); /// }); + /// # }); /// ``` #[inline] pub fn columns( @@ -1764,7 +1779,7 @@ impl Ui { /// Create a menu button. Creates a button for a sub-menu when the `Ui` is inside a menu. /// /// ``` - /// # let mut ui = egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// ui.menu_button("My menu", |ui| { /// ui.menu_button("My sub-menu", |ui| { /// if ui.button("Close the menu").clicked() { @@ -1772,6 +1787,7 @@ impl Ui { /// } /// }); /// }); + /// # }); /// ``` pub fn menu_button( &mut self, diff --git a/egui/src/widgets/button.rs b/egui/src/widgets/button.rs index f760bc8c273..d9ff8e56158 100644 --- a/egui/src/widgets/button.rs +++ b/egui/src/widgets/button.rs @@ -5,7 +5,7 @@ use crate::*; /// See also [`Ui::button`]. /// /// ``` -/// # let ui = &mut egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// # fn do_stuff() {} /// /// if ui.add(egui::Button::new("Click me")).clicked() { @@ -16,6 +16,7 @@ use crate::*; /// if ui.add_enabled(false, egui::Button::new("Can't click this")).clicked() { /// unreachable!(); /// } +/// # }); /// ``` #[must_use = "You should put this widget in an ui with `ui.add(widget);`"] pub struct Button { @@ -177,11 +178,12 @@ impl Widget for Button { /// Usually you'd use [`Ui::checkbox`] instead. /// /// ``` -/// # let ui = &mut egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// # let mut my_bool = true; /// // These are equivalent: /// ui.checkbox(&mut my_bool, "Checked"); /// ui.add(egui::Checkbox::new(&mut my_bool, "Checked")); +/// # }); /// ``` #[must_use = "You should put this widget in an ui with `ui.add(widget);`"] pub struct Checkbox<'a> { @@ -275,7 +277,7 @@ impl<'a> Widget for Checkbox<'a> { /// Usually you'd use [`Ui::radio_value`] or [`Ui::radio`] instead. /// /// ``` -/// # let ui = &mut egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// #[derive(PartialEq)] /// enum Enum { First, Second, Third } /// let mut my_enum = Enum::First; @@ -287,6 +289,7 @@ impl<'a> Widget for Checkbox<'a> { /// if ui.add(egui::RadioButton::new(my_enum == Enum::First, "First")).clicked() { /// my_enum = Enum::First /// } +/// # }); /// ``` #[must_use = "You should put this widget in an ui with `ui.add(widget);`"] pub struct RadioButton { diff --git a/egui/src/widgets/drag_value.rs b/egui/src/widgets/drag_value.rs index 32a5d9deb57..a9afa87303c 100644 --- a/egui/src/widgets/drag_value.rs +++ b/egui/src/widgets/drag_value.rs @@ -42,9 +42,10 @@ fn set(get_set_value: &mut GetSetValue<'_>, value: f64) { /// A numeric value that you can change by dragging the number. More compact than a [`Slider`]. /// /// ``` -/// # let ui = &mut egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// # let mut my_f32: f32 = 0.0; /// ui.add(egui::DragValue::new(&mut my_f32).speed(0.1)); +/// # }); /// ``` #[must_use = "You should put this widget in an ui with `ui.add(widget);`"] pub struct DragValue<'a> { diff --git a/egui/src/widgets/hyperlink.rs b/egui/src/widgets/hyperlink.rs index dcc7b5056e4..f8c7b996b91 100644 --- a/egui/src/widgets/hyperlink.rs +++ b/egui/src/widgets/hyperlink.rs @@ -5,9 +5,10 @@ use crate::*; /// See also [`Ui::hyperlink`] and [`Ui::hyperlink_to`]. /// /// ``` -/// # let ui = &mut egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// ui.hyperlink("https://github.com/emilk/egui"); /// ui.add(egui::Hyperlink::new("https://github.com/emilk/egui").text("My favorite repo").small()); +/// # }); /// ``` #[must_use = "You should put this widget in an ui with `ui.add(widget);`"] pub struct Hyperlink { diff --git a/egui/src/widgets/image.rs b/egui/src/widgets/image.rs index 3f84d049eb5..ef1b88b6e9e 100644 --- a/egui/src/widgets/image.rs +++ b/egui/src/widgets/image.rs @@ -3,12 +3,13 @@ use crate::*; /// An widget to show an image of a given size. /// /// ``` -/// # let ui = &mut egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// # let my_texture_id = egui::TextureId::User(0); /// ui.add(egui::Image::new(my_texture_id, [640.0, 480.0])); /// /// // Shorter version: /// ui.image(my_texture_id, [640.0, 480.0]); +/// # }); /// ``` /// /// Se also [`crate::ImageButton`]. diff --git a/egui/src/widgets/label.rs b/egui/src/widgets/label.rs index d89cfcb76c5..704d394e144 100644 --- a/egui/src/widgets/label.rs +++ b/egui/src/widgets/label.rs @@ -3,11 +3,12 @@ use crate::{widget_text::WidgetTextGalley, *}; /// Static text. /// /// ``` -/// # let ui = &mut egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// ui.label("Equivalent"); /// ui.add(egui::Label::new("Equivalent")); /// ui.add(egui::Label::new("With Options").wrap(false)); /// ui.label(egui::RichText::new("With formatting").underline()); +/// # }); /// ``` #[must_use = "You should put this widget in an ui with `ui.add(widget);`"] pub struct Label { @@ -134,10 +135,11 @@ impl Label { /// /// ``` rust /// # use egui::{Label, Sense}; - /// # let ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// if ui.add(Label::new("click me").sense(Sense::click())).clicked() { /// /* … */ /// } + /// # }); /// ``` pub fn sense(mut self, sense: Sense) -> Self { self.sense = sense; diff --git a/egui/src/widgets/plot/mod.rs b/egui/src/widgets/plot/mod.rs index 98ad69a23c2..ae0e610c7aa 100644 --- a/egui/src/widgets/plot/mod.rs +++ b/egui/src/widgets/plot/mod.rs @@ -47,7 +47,7 @@ impl PlotMemory { /// `Plot` supports multiple lines and points. /// /// ``` -/// # let ui = &mut egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// use egui::plot::{Line, Plot, Value, Values}; /// let sin = (0..1000).map(|i| { /// let x = i as f64 * 0.01; @@ -57,6 +57,7 @@ impl PlotMemory { /// ui.add( /// Plot::new("my_plot").line(line).view_aspect(2.0) /// ); +/// # }); /// ``` pub struct Plot { id_source: Id, diff --git a/egui/src/widgets/selected_label.rs b/egui/src/widgets/selected_label.rs index ea6daef324b..e364883b739 100644 --- a/egui/src/widgets/selected_label.rs +++ b/egui/src/widgets/selected_label.rs @@ -7,7 +7,7 @@ use crate::*; /// Usually you'd use [`Ui::selectable_value`] or [`Ui::selectable_label`] instead. /// /// ``` -/// # let ui = &mut egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// #[derive(PartialEq)] /// enum Enum { First, Second, Third } /// let mut my_enum = Enum::First; @@ -19,6 +19,7 @@ use crate::*; /// if ui.add(egui::SelectableLabel::new(my_enum == Enum::First, "First")).clicked() { /// my_enum = Enum::First /// } +/// # }); /// ``` #[must_use = "You should put this widget in an ui with `ui.add(widget);`"] pub struct SelectableLabel { diff --git a/egui/src/widgets/separator.rs b/egui/src/widgets/separator.rs index b8271450be6..b3db250ee16 100644 --- a/egui/src/widgets/separator.rs +++ b/egui/src/widgets/separator.rs @@ -5,10 +5,11 @@ use crate::*; /// Usually you'd use the shorter version [`Ui::separator`]. /// /// ``` -/// # let ui = &mut egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// // These are equivalent: /// ui.separator(); /// ui.add(egui::Separator::default()); +/// # }); /// ``` #[must_use = "You should put this widget in an ui with `ui.add(widget);`"] pub struct Separator { diff --git a/egui/src/widgets/slider.rs b/egui/src/widgets/slider.rs index fc38fffbd3e..4d073ac4ca2 100644 --- a/egui/src/widgets/slider.rs +++ b/egui/src/widgets/slider.rs @@ -45,9 +45,10 @@ struct SliderSpec { /// The user can click the value display to edit its value. It can be turned off with `.show_value(false)`. /// /// ``` -/// # let ui = &mut egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// # let mut my_f32: f32 = 0.0; /// ui.add(egui::Slider::new(&mut my_f32, 0.0..=100.0).text("My value")); +/// # }); /// ``` /// /// The default `Slider` size is set by [`crate::style::Spacing::slider_width`]. diff --git a/egui/src/widgets/text_edit/builder.rs b/egui/src/widgets/text_edit/builder.rs index a2a20d5b885..8c6812e1059 100644 --- a/egui/src/widgets/text_edit/builder.rs +++ b/egui/src/widgets/text_edit/builder.rs @@ -13,7 +13,7 @@ use super::{CCursorRange, CursorRange, TextEditOutput, TextEditState}; /// Example: /// /// ``` -/// # let mut ui = egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// # let mut my_string = String::new(); /// let response = ui.add(egui::TextEdit::singleline(&mut my_string)); /// if response.changed() { @@ -22,14 +22,16 @@ use super::{CCursorRange, CursorRange, TextEditOutput, TextEditState}; /// if response.lost_focus() && ui.input().key_pressed(egui::Key::Enter) { /// // … /// } +/// # }); /// ``` /// /// To fill an [`Ui`] with a [`TextEdit`] use [`Ui::add_sized`]: /// /// ``` -/// # let mut ui = egui::Ui::__test(); +/// # egui::__run_test_ui(|ui| { /// # let mut my_string = String::new(); /// ui.add_sized(ui.available_size(), egui::TextEdit::multiline(&mut my_string)); +/// # }); /// ``` /// /// @@ -165,7 +167,7 @@ impl<'t> TextEdit<'t> { /// the text and the wrap width. /// /// ``` - /// # let ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// # let mut my_code = String::new(); /// # fn my_memoized_highlighter(s: &str) -> egui::text::LayoutJob { Default::default() } /// let mut layouter = |ui: &egui::Ui, string: &str, wrap_width: f32| { @@ -174,6 +176,7 @@ impl<'t> TextEdit<'t> { /// ui.fonts().layout_job(layout_job) /// }; /// ui.add(egui::TextEdit::multiline(&mut my_code).layouter(&mut layouter)); + /// # }); /// ``` pub fn layouter(mut self, layouter: &'t mut dyn FnMut(&Ui, &str, f32) -> Arc) -> Self { self.layouter = Some(layouter); From 65c5eb5083b036b18369f45e956d43db9065f2bf Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 3 Nov 2021 15:58:02 +0100 Subject: [PATCH 3/6] Add helper `egui::__run_test_ctx` for doctests --- egui/src/containers/area.rs | 5 ++--- egui/src/containers/panel.rs | 15 ++++++--------- egui/src/containers/window.rs | 5 ++--- egui/src/lib.rs | 12 ++++++++++-- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/egui/src/containers/area.rs b/egui/src/containers/area.rs index 9ba2359971d..77ae49550f0 100644 --- a/egui/src/containers/area.rs +++ b/egui/src/containers/area.rs @@ -33,14 +33,13 @@ impl State { /// This forms the base of the [`Window`] container. /// /// ``` -/// # let mut ctx = egui::CtxRef::default(); -/// # ctx.begin_frame(Default::default()); -/// # let ctx = &ctx; +/// # egui::__run_test_ctx(|ctx| { /// egui::Area::new("my_area") /// .fixed_pos(egui::pos2(32.0, 32.0)) /// .show(ctx, |ui| { /// ui.label("Floating text!"); /// }); +/// # }); #[must_use = "You should call .show()"] #[derive(Clone, Copy, Debug)] pub struct Area { diff --git a/egui/src/containers/panel.rs b/egui/src/containers/panel.rs index 5488667eb5b..d218d99b39c 100644 --- a/egui/src/containers/panel.rs +++ b/egui/src/containers/panel.rs @@ -73,12 +73,11 @@ impl Side { /// See the [module level docs](crate::containers::panel) for more details. /// /// ``` -/// # let mut ctx = egui::CtxRef::default(); -/// # ctx.begin_frame(Default::default()); -/// # let ctx = &ctx; +/// # egui::__run_test_ctx(|ctx| { /// egui::SidePanel::left("my_left_panel").show(ctx, |ui| { /// ui.label("Hello World!"); /// }); +/// # }); /// ``` /// /// See also [`TopBottomPanel`]. @@ -350,12 +349,11 @@ impl TopBottomSide { /// See the [module level docs](crate::containers::panel) for more details. /// /// ``` -/// # let mut ctx = egui::CtxRef::default(); -/// # ctx.begin_frame(Default::default()); -/// # let ctx = &ctx; +/// # egui::__run_test_ctx(|ctx| { /// egui::TopBottomPanel::top("my_panel").show(ctx, |ui| { /// ui.label("Hello World!"); /// }); +/// # }); /// ``` /// /// See also [`SidePanel`]. @@ -605,12 +603,11 @@ impl TopBottomPanel { /// See the [module level docs](crate::containers::panel) for more details. /// /// ``` -/// # let mut ctx = egui::CtxRef::default(); -/// # ctx.begin_frame(Default::default()); -/// # let ctx = &ctx; +/// # egui::__run_test_ctx(|ctx| { /// egui::CentralPanel::default().show(ctx, |ui| { /// ui.label("Hello World!"); /// }); +/// # }); /// ``` #[must_use = "You should call .show()"] #[derive(Default)] diff --git a/egui/src/containers/window.rs b/egui/src/containers/window.rs index fd18d140771..4eceac70dfc 100644 --- a/egui/src/containers/window.rs +++ b/egui/src/containers/window.rs @@ -15,12 +15,11 @@ use super::*; /// * if there should be a close button (none by default) /// /// ``` -/// # let mut ctx = egui::CtxRef::default(); -/// # ctx.begin_frame(Default::default()); -/// # let ctx = &ctx; +/// # egui::__run_test_ctx(|ctx| { /// egui::Window::new("My Window").show(ctx, |ui| { /// ui.label("Hello World!"); /// }); +/// # }); #[must_use = "You should call .show()"] pub struct Window<'open> { title: WidgetText, diff --git a/egui/src/lib.rs b/egui/src/lib.rs index cc2f907f3fc..6e3bd01ca53 100644 --- a/egui/src/lib.rs +++ b/egui/src/lib.rs @@ -45,8 +45,7 @@ //! get access to an [`Ui`] where you can put widgets. For example: //! //! ``` -//! # let mut ctx = egui::CtxRef::default(); -//! # ctx.begin_frame(Default::default()); +//! # egui::__run_test_ctx(|ctx| { //! egui::CentralPanel::default().show(&ctx, |ui| { //! ui.add(egui::Label::new("Hello World!")); //! ui.label("A shorter and more convenient way to add a label."); @@ -54,6 +53,7 @@ //! // take some action here //! } //! }); +//! # }); //! ``` //! //! ### Quick start @@ -576,6 +576,14 @@ pub enum WidgetType { // ---------------------------------------------------------------------------- +/// For use in tests; especially doctests. +pub fn __run_test_ctx(mut run_ui: impl FnMut(&CtxRef)) { + let mut ctx = CtxRef::default(); + let _ = ctx.run(Default::default(), |ctx| { + run_ui(ctx); + }); +} + /// For use in tests; especially doctests. pub fn __run_test_ui(mut add_contents: impl FnMut(&mut Ui)) { let mut ctx = CtxRef::default(); From 9c28c803c7d717703d0e8028e04b6c8d01dd0c7f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 3 Nov 2021 15:58:34 +0100 Subject: [PATCH 4/6] Make begin_frame/end_frame private --- CHANGELOG.md | 2 +- egui/src/context.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7327992d70b..2314402dd81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,11 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w * Add context menus: See `Ui::menu_button` and `Response::context_menu` ([#543](https://github.com/emilk/egui/pull/543)). * You can now read and write the cursor of a `TextEdit` ([#848](https://github.com/emilk/egui/pull/848)). * Most widgets containing text (`Label`, `Button` etc) now supports rich text ([#855](https://github.com/emilk/egui/pull/855)). -* Add `CtxRef::run` as a convenicene for calling `begin_frame` and `end_frame`. ### Changed 🔧 * Unifiy the four `Memory` data buckets (`data`, `data_temp`, `id_data` and `id_data_temp`) into a single `Memory::data`, with a new interface ([#836](https://github.com/emilk/egui/pull/836)). * `ui.add(Button::new("…").text_color(…))` is now `ui.button(RichText::new("…").color(…))` (same for `Label` )([#855](https://github.com/emilk/egui/pull/855)). +* Replace `CtxRef::begin_frame` and `end_frame` with `CtxRef::run`. * Replace `Ui::__test` with `egui::__run_test_ui`. ### Contributors 🙏 diff --git a/egui/src/context.rs b/egui/src/context.rs index 85be7ee21ac..ee74a9a0e7b 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -118,7 +118,7 @@ impl CtxRef { /// Any old clones of this [`CtxRef`] will refer to the old [`Context`], which will not get new input. /// /// Put your widgets into a [`SidePanel`], [`TopBottomPanel`], [`CentralPanel`], [`Window`] or [`Area`]. - pub fn begin_frame(&mut self, new_input: RawInput) { + fn begin_frame(&mut self, new_input: RawInput) { let mut self_: Context = (*self.0).clone(); self_.begin_frame_mut(new_input); *self = Self(Arc::new(self_)); @@ -638,7 +638,7 @@ impl Context { /// Returns what has happened this frame [`crate::Output`] as well as what you need to paint. /// You can transform the returned shapes into triangles with a call to [`Context::tessellate`]. #[must_use] - pub fn end_frame(&self) -> (Output, Vec) { + fn end_frame(&self) -> (Output, Vec) { if self.input.wants_repaint() { self.request_repaint(); } From 1f1621e51de7737d2513dbc599403d4a3dfeea11 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 3 Nov 2021 16:01:49 +0100 Subject: [PATCH 5/6] Add PR link to changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2314402dd81..1d16fcc55d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,8 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w ### Changed 🔧 * Unifiy the four `Memory` data buckets (`data`, `data_temp`, `id_data` and `id_data_temp`) into a single `Memory::data`, with a new interface ([#836](https://github.com/emilk/egui/pull/836)). * `ui.add(Button::new("…").text_color(…))` is now `ui.button(RichText::new("…").color(…))` (same for `Label` )([#855](https://github.com/emilk/egui/pull/855)). -* Replace `CtxRef::begin_frame` and `end_frame` with `CtxRef::run`. -* Replace `Ui::__test` with `egui::__run_test_ui`. +* Replace `CtxRef::begin_frame` and `end_frame` with `CtxRef::run` ([#872](https://github.com/emilk/egui/pull/872)). +* Replace `Ui::__test` with `egui::__run_test_ui` ([#872](https://github.com/emilk/egui/pull/872)). ### Contributors 🙏 * [mankinskin](https://github.com/mankinskin) ([#543](https://github.com/emilk/egui/pull/543)) From 4f42db3b9a73797f9429a5c3784a941e2c6d5fb8 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 3 Nov 2021 16:04:19 +0100 Subject: [PATCH 6/6] Clippy fix --- egui_demo_lib/benches/benchmark.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/egui_demo_lib/benches/benchmark.rs b/egui_demo_lib/benches/benchmark.rs index 51088d6af22..cf2a4bc9a26 100644 --- a/egui_demo_lib/benches/benchmark.rs +++ b/egui_demo_lib/benches/benchmark.rs @@ -51,7 +51,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { { let mut ctx = egui::CtxRef::default(); - let _ = ctx.run(raw_input.clone(), |ctx| { + let _ = ctx.run(raw_input, |ctx| { egui::CentralPanel::default().show(ctx, |ui| { c.bench_function("label &str", |b| { b.iter(|| {