diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index 9169638f1..8cc0dd914 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -1,4 +1,4 @@ -use std::time::Instant; +use std::{sync::Arc, time::Instant}; use winit::event_loop::EventLoopWindowTarget; @@ -9,7 +9,7 @@ use raw_window_handle::{HasRawDisplayHandle as _, HasRawWindowHandle as _}; #[cfg(feature = "accesskit")] use egui::accesskit; -use egui::{epaint::ahash::HashMap, window::ViewportBuilder, NumExt as _}; +use egui::{epaint::ahash::HashMap, window::ViewportBuilder, Context, NumExt as _}; #[cfg(feature = "accesskit")] use egui_winit::accesskit_winit; use egui_winit::{native_pixels_per_point, EventResponse, WindowSettings}; @@ -432,7 +432,7 @@ impl EpiIntegration { let saved_memory: egui::Memory = self.egui_ctx.memory(|mem| mem.clone()); self.egui_ctx .memory_mut(|mem| mem.set_everything_is_visible(true)); - let full_output = self.update(app, window, egui_winit); + let full_output = self.update(app, window, egui_winit, None); self.pending_full_output.append(full_output); // Handle it next frame self.egui_ctx.memory_mut(|mem| *mem = saved_memory); // We don't want to remember that windows were huge. self.egui_ctx.clear_animations(); @@ -496,6 +496,7 @@ impl EpiIntegration { app: &mut dyn epi::App, window: &winit::window::Window, egui_winit: &mut egui_winit::State, + render: Option>>, ) -> egui::FullOutput { let frame_start = std::time::Instant::now(); @@ -509,7 +510,11 @@ impl EpiIntegration { // Run user code: let full_output = self.egui_ctx.run(raw_input, |egui_ctx| { crate::profile_scope!("App::update"); - app.update(egui_ctx, &mut self.frame); + if let Some(render) = render { + (render.as_ref())(egui_ctx) + } else { + app.update(egui_ctx, &mut self.frame); + } }); self.pending_full_output.append(full_output); diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 7a1bd0039..347cee60e 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -411,7 +411,7 @@ fn run_and_exit(event_loop: EventLoop, mut winit_app: impl WinitApp + mod glow_integration { use std::sync::Arc; - use egui::{epaint::ahash::HashMap, window::ViewportBuilder, NumExt as _}; + use egui::{epaint::ahash::HashMap, window::ViewportBuilder, Context, NumExt as _}; use egui_winit::EventResponse; use glow::HasContext; use glutin::{ @@ -454,6 +454,7 @@ mod glow_integration { gl_surface: Option>, window: Option, window_id: u64, + render: Option>>, pub egui_winit: Option, } /// This struct will contain both persistent and temporary glutin state. @@ -601,6 +602,7 @@ mod glow_integration { window, window_id: 0, egui_winit: None, + render: None, }], window_maps, }) @@ -1049,6 +1051,7 @@ mod glow_integration { app.as_mut(), win.window.as_ref().unwrap(), win.egui_winit.as_mut().unwrap(), + win.render.clone(), ); integration.handle_platform_output( @@ -1153,7 +1156,7 @@ mod glow_integration { // 0 is the main viewport/window that will not be known by the egui_ctx let mut active_viewports_ids = vec![0]; - viewports.retain_mut(|(id, builder)| { + viewports.retain_mut(|(id, builder, render)| { for w in gl_window.windows.iter_mut() { if w.window_id == *id { if w.builder != *builder { @@ -1164,7 +1167,7 @@ mod glow_integration { } w.window = None; w.gl_surface = None; - + w.render = Some(render.clone()); w.builder = builder.clone(); } active_viewports_ids.push(*id); @@ -1174,13 +1177,14 @@ mod glow_integration { true }); - for (id, builder) in viewports { + for (id, builder, render) in viewports { gl_window.windows.push(Window { builder, gl_surface: None, window: None, window_id: id, egui_winit: None, + render: Some(render.clone()), }); active_viewports_ids.push(id); } @@ -1234,7 +1238,7 @@ mod glow_integration { winit::event::Event::MainEventsCleared => { if let Some(running) = self.running.as_mut() { - running.gl_window.on_resume(event_loop); + let _ = running.gl_window.on_resume(event_loop); } EventResult::Wait } diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 4e277d2dc..8aa0b2899 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -191,7 +191,7 @@ impl<'open> Window<'open> { collapsible: true, default_open: true, with_title_bar: true, - embedded: true, + embedded: false, } } @@ -417,19 +417,11 @@ impl<'open> Window<'open> { /// Returns `None` if the window is not open (if [`Window::open`] was called with `&mut false`). /// Returns `Some(InnerResponse { inner: None })` if the window is collapsed. #[inline] - pub fn show( - self, - ctx: &Context, - add_contents: impl FnOnce(&mut Ui) -> R, - ) -> Option>> { + pub fn show(self, ctx: &Context, add_contents: impl Fn(&mut Ui) + Send + Sync + 'static) { self.show_dyn(ctx, Box::new(add_contents)) } - fn show_dyn<'c, R>( - self, - ctx: &Context, - add_contents: Box R + 'c>, - ) -> Option>> { + fn show_dyn(self, ctx: &Context, add_contents: Box) { let Window { title, open, @@ -444,31 +436,34 @@ impl<'open> Window<'open> { mut window_builder, } = self; + let is_explicitly_closed = matches!(open, Some(false)); + let is_open = !is_explicitly_closed || ctx.memory(|mem| mem.everything_is_visible()); if !embedded { + if !is_open { + return; + } if let Some(size) = ctx.data(|data| data.get_temp::(area.id.with("size"))) { let size = size.round() + ctx.style().spacing.window_margin.sum(); window_builder = window_builder.with_inner_size((size.x as u32 + 1, size.y as u32 + 1)); } - ctx.create_viewport(window_builder, move |_window_id| { + ctx.create_viewport(window_builder, move |ctx| { let mut frame = frame.unwrap_or(Frame::window(&ctx.style())).rounding(0.0); CentralPanel::default() .frame(frame) - .show(ctx, |ui| Some(add_contents(ui))) + .show(ctx, |ui| Some(add_contents(ui))); }) } else { if ctx.current_viewport() != ctx.current_rendering_viewport() { - return None; + return; } let frame = frame.unwrap_or_else(|| Frame::window(&ctx.style())); - let is_explicitly_closed = matches!(open, Some(false)); - let is_open = !is_explicitly_closed || ctx.memory(|mem| mem.everything_is_visible()); area.show_open_close_animation(ctx, &frame, is_open); if !is_open { - return None; + return; } let area_id = area.id; @@ -623,7 +618,7 @@ impl<'open> Window<'open> { inner: content_inner, response: full_response, }; - Some(inner_response) + // Some(inner_response) } } } diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 226fcf4e3..20c950e60 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -160,7 +160,16 @@ struct ContextImpl { repaint: Repaint, - viewports: HashMap, + viewports: HashMap< + String, + ( + ViewportBuilder, + u64, + u64, + bool, + Arc>, + ), + >, viewport_counter: u64, current_rendering_viewport: u64, viewport_stack: Vec, @@ -1274,19 +1283,30 @@ impl Context { let repaint_after = self.write(|ctx| ctx.repaint.end_frame()); let shapes = self.drain_paint_lists(); + // This is used for, + // If there are no viewport that contains the current viewpor that viewport needs to be destroyed! + let avalibile_viewports = self.read(|ctx| { + let mut avalibile_viewports = vec![0]; + for (_, (_, id, _, _, _)) in ctx.viewports.iter() { + avalibile_viewports.push(*id); + } + avalibile_viewports + }); + let mut viewports = Vec::new(); self.write(|ctx| { - ctx.viewports.retain(|_, (builder, id, parent, used)| { - let out = *used; - if ctx.current_rendering_viewport == *parent - || ctx.current_rendering_viewport == *id - { - *used = false; - } else { - } - viewports.push((*id, builder.clone())); - out - }) + ctx.viewports + .retain(|_, (builder, id, parent, used, render)| { + let out = *used; + + if ctx.current_rendering_viewport == *parent { + *used = false; + } + + viewports.push((*id, builder.clone(), render.clone())); + (out || ctx.current_rendering_viewport != *parent) + && avalibile_viewports.contains(parent) + }) }); FullOutput { @@ -1921,16 +1941,17 @@ impl Context { self.write(|ctx| ctx.is_desktop = value) } - pub fn create_viewport( + pub fn create_viewport( &self, window_builder: ViewportBuilder, - func: impl FnOnce(u64) -> T, - ) -> Option { + func: impl Fn(&Context) + Send + Sync + 'static, + ) { let id = self.write(|ctx| { if ctx.is_desktop { if let Some(window) = ctx.viewports.get_mut(&window_builder.title) { window.2 = *ctx.viewport_stack.last().unwrap_or(&0); window.3 = true; + window.4 = Arc::new(Box::new(func)); window.1 } else { let id = ctx.viewport_counter + 1; @@ -1942,6 +1963,7 @@ impl Context { id, *ctx.viewport_stack.last().unwrap_or(&0), true, + Arc::new(Box::new(func)), ), ); id @@ -1954,11 +1976,9 @@ impl Context { ctx.viewport_stack.push(id); ctx.viewport_stack.last().cloned().unwrap_or(0) == ctx.current_rendering_viewport }); - let out = if should_render { Some(func(id)) } else { None }; self.write(|ctx| { ctx.viewport_stack.pop(); }); - out } } diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index 7966df473..9714cfcd6 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -1,11 +1,14 @@ //! All the data egui returns to the backend at the end of each frame. +use std::sync::Arc; + +use crate::Context; use crate::{window::ViewportBuilder, WidgetType}; /// What egui emits each frame from [`crate::Context::run`]. /// /// The backend should use this. -#[derive(Clone, Default, PartialEq)] +#[derive(Clone, Default)] pub struct FullOutput { /// Non-rendering related output. pub platform_output: PlatformOutput, @@ -31,7 +34,11 @@ pub struct FullOutput { /// You can use [`crate::Context::tessellate`] to turn this into triangles. pub shapes: Vec, - pub viewports: Vec<(u64, ViewportBuilder)>, + pub viewports: Vec<( + u64, + ViewportBuilder, + Arc>, + )>, } impl FullOutput { diff --git a/crates/egui_demo_app/src/backend_panel.rs b/crates/egui_demo_app/src/backend_panel.rs index 9f87834f0..984b15501 100644 --- a/crates/egui_demo_app/src/backend_panel.rs +++ b/crates/egui_demo_app/src/backend_panel.rs @@ -1,3 +1,5 @@ +use std::sync::{Arc, RwLock}; + /// How often we repaint the demo app by default #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum RunMode { @@ -353,7 +355,7 @@ struct EguiWindows { output_events: bool, #[cfg_attr(feature = "serde", serde(skip))] - output_event_history: std::collections::VecDeque, + output_event_history: Arc>>, } impl Default for EguiWindows { @@ -397,41 +399,48 @@ impl EguiWindows { output_event_history, } = self; - ctx.output(|o| { - for event in &o.events { - output_event_history.push_back(event.clone()); + { + let mut output_event_history = output_event_history.write().unwrap(); + ctx.output(|o| { + for event in &o.events { + output_event_history.push_back(event.clone()); + } + }); + while output_event_history.len() > 1000 { + output_event_history.pop_front(); } - }); - while output_event_history.len() > 1000 { - output_event_history.pop_front(); } + let tmp_ctx = ctx.clone(); egui::Window::new("🔧 Settings") .open(settings) .vscroll(true) - .show(ctx, |ui| { - ctx.settings_ui(ui); + .show(ctx, move |ui| { + tmp_ctx.settings_ui(ui); }); + let tmp_ctx = ctx.clone(); egui::Window::new("🔍 Inspection") .open(inspection) .vscroll(true) - .show(ctx, |ui| { - ctx.inspection_ui(ui); + .show(ctx, move |ui| { + tmp_ctx.inspection_ui(ui); }); + let tmp_ctx = ctx.clone(); egui::Window::new("📝 Memory") .open(memory) .resizable(false) - .show(ctx, |ui| { - ctx.memory_ui(ui); + .show(ctx, move |ui| { + tmp_ctx.memory_ui(ui); }); + let tmp_output_event_history = output_event_history.clone(); egui::Window::new("📤 Output Events") .open(output_events) .resizable(true) .default_width(520.0) - .show(ctx, |ui| { + .show(ctx, move |ui| { ui.label( "Recent output events from egui. \ These are emitted when you interact with widgets, or move focus between them with TAB. \ @@ -443,7 +452,7 @@ impl EguiWindows { egui::ScrollArea::vertical() .stick_to_bottom(true) .show(ui, |ui| { - for event in output_event_history { + for event in &*tmp_output_event_history.read().unwrap() { ui.label(format!("{:?}", event)); } }); diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index 3b2c09a79..b47508da5 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -5,6 +5,7 @@ use eframe::glow; #[cfg(target_arch = "wasm32")] use core::any::Any; +use std::sync::{Arc, RwLock}; #[derive(Default)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -147,7 +148,7 @@ pub struct WrapApp { #[cfg(any(feature = "glow", feature = "wgpu"))] custom3d: Option, - dropped_files: Vec, + dropped_files: Arc>>, } impl WrapApp { @@ -412,17 +413,20 @@ impl WrapApp { // Collect dropped files: ctx.input(|i| { if !i.raw.dropped_files.is_empty() { - self.dropped_files = i.raw.dropped_files.clone(); + *self.dropped_files.write().unwrap() = i.raw.dropped_files.clone(); } }); // Show dropped files (if any): - if !self.dropped_files.is_empty() { + let is_empty = self.dropped_files.read().unwrap().is_empty(); + if !is_empty { let mut open = true; + let dropped_files = self.dropped_files.clone(); egui::Window::new("Dropped files") .open(&mut open) - .show(ctx, |ui| { - for file in &self.dropped_files { + .show(ctx, move |ui| { + let dropped_files = &*dropped_files.read().unwrap(); + for file in dropped_files { let mut info = if let Some(path) = &file.path { path.display().to_string() } else if !file.name.is_empty() { @@ -437,7 +441,7 @@ impl WrapApp { } }); if !open { - self.dropped_files.clear(); + self.dropped_files.write().unwrap().clear(); } } } diff --git a/crates/egui_demo_lib/src/demo/about.rs b/crates/egui_demo_lib/src/demo/about.rs index 0fbb97642..b5ccc027a 100644 --- a/crates/egui_demo_lib/src/demo/about.rs +++ b/crates/egui_demo_lib/src/demo/about.rs @@ -14,7 +14,7 @@ impl super::Demo for About { .open(open) .show(ctx, |ui| { use super::View as _; - self.ui(ui); + Self::default().ui(ui); }); } } diff --git a/crates/egui_demo_lib/src/demo/code_editor.rs b/crates/egui_demo_lib/src/demo/code_editor.rs index e9632a208..10934727a 100644 --- a/crates/egui_demo_lib/src/demo/code_editor.rs +++ b/crates/egui_demo_lib/src/demo/code_editor.rs @@ -1,13 +1,21 @@ -// ---------------------------------------------------------------------------- +use std::sync::{Arc, RwLock}; +// ---------------------------------------------------------------------------- #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] -pub struct CodeEditor { +pub struct CodeEditorData { language: String, code: String, } -impl Default for CodeEditor { +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(default))] +#[derive(Default, Clone)] +pub struct CodeEditor { + data: Arc>, +} + +impl Default for CodeEditorData { fn default() -> Self { Self { language: "rs".into(), @@ -27,17 +35,18 @@ impl super::Demo for CodeEditor { } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + let clone = self.clone(); use super::View as _; egui::Window::new(self.name()) .open(open) .default_height(500.0) - .show(ctx, |ui| self.ui(ui)); + .show(ctx, move |ui| clone.clone().ui(ui)); } } impl super::View for CodeEditor { fn ui(&mut self, ui: &mut egui::Ui) { - let Self { language, code } = self; + let CodeEditorData { language, code } = &mut *self.data.write().unwrap(); ui.horizontal(|ui| { ui.set_height(0.0); diff --git a/crates/egui_demo_lib/src/demo/code_example.rs b/crates/egui_demo_lib/src/demo/code_example.rs index c72fde221..86841f5e1 100644 --- a/crates/egui_demo_lib/src/demo/code_example.rs +++ b/crates/egui_demo_lib/src/demo/code_example.rs @@ -1,10 +1,17 @@ +use std::sync::{Arc, RwLock}; + #[derive(Debug)] -pub struct CodeExample { +pub struct CodeExampleData { name: String, age: u32, } -impl Default for CodeExample { +#[derive(Debug, Default, Clone)] +pub struct CodeExample { + data: Arc>, +} + +impl Default for CodeExampleData { fn default() -> Self { Self { name: "Arthur".to_owned(), @@ -29,28 +36,29 @@ impl CodeExample { });"#, ); // Putting things on the same line using ui.horizontal: + let mut data = self.data.write().unwrap(); ui.horizontal(|ui| { ui.label("Your name: "); - ui.text_edit_singleline(&mut self.name); + ui.text_edit_singleline(&mut data.name); }); ui.end_row(); show_code( ui, - r#"egui::Slider::new(&mut self.age, 0..=120).text("age")"#, + r#"egui::Slider::new(&mut data.age, 0..=120).text("age")"#, ); - ui.add(egui::Slider::new(&mut self.age, 0..=120).text("age")); + ui.add(egui::Slider::new(&mut data.age, 0..=120).text("age")); ui.end_row(); show_code( ui, r#" if ui.button("Click each year").clicked() { - self.age += 1; + data.age += 1; }"#, ); if ui.button("Click each year").clicked() { - self.age += 1; + data.age += 1; } ui.end_row(); @@ -58,7 +66,7 @@ impl CodeExample { ui, r#"ui.label(format!("Hello '{}', age {}", self.name, self.age));"#, ); - ui.label(format!("Hello '{}', age {}", self.name, self.age)); + ui.label(format!("Hello '{}', age {}", data.name, data.age)); ui.end_row(); } } @@ -69,13 +77,14 @@ impl super::Demo for CodeExample { } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + let clone = self.clone(); use super::View; egui::Window::new(self.name()) .open(open) .default_size([800.0, 400.0]) .vscroll(false) .hscroll(true) - .show(ctx, |ui| self.ui(ui)); + .show(ctx, move |ui| clone.clone().ui(ui)); } } diff --git a/crates/egui_demo_lib/src/demo/context_menu.rs b/crates/egui_demo_lib/src/demo/context_menu.rs index f8b6bc58a..f73a27f7d 100644 --- a/crates/egui_demo_lib/src/demo/context_menu.rs +++ b/crates/egui_demo_lib/src/demo/context_menu.rs @@ -1,3 +1,5 @@ +use std::sync::{Arc, RwLock}; + #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] enum Plot { @@ -14,10 +16,9 @@ fn gaussian(x: f64) -> f64 { fn sigmoid(x: f64) -> f64 { -1.0 + 2.0 / (1.0 + f64::exp(-x)) } - #[derive(Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct ContextMenus { +pub struct ContextMenusData { plot: Plot, show_axes: [bool; 2], allow_drag: bool, @@ -29,7 +30,19 @@ pub struct ContextMenus { height: f32, } -impl Default for ContextMenus { +#[derive(Clone, Default)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct ContextMenus { + data: Arc>, +} + +impl PartialEq for ContextMenus { + fn eq(&self, other: &Self) -> bool { + *self.data.read().unwrap() == *other.data.read().unwrap() + } +} + +impl Default for ContextMenusData { fn default() -> Self { Self { plot: Plot::Sin, @@ -51,12 +64,13 @@ impl super::Demo for ContextMenus { } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + let clone = self.clone(); use super::View; egui::Window::new(self.name()) .vscroll(false) .resizable(false) .open(open) - .show(ctx, |ui| self.ui(ui)); + .show(ctx, move |ui| clone.clone().ui(ui)); } } @@ -73,13 +87,14 @@ impl super::View for ContextMenus { ui.label("Right-click plot to edit it!"); ui.horizontal(|ui| { self.example_plot(ui).context_menu(|ui| { + let data = &mut *self.data.write().unwrap(); ui.menu_button("Plot", |ui| { - if ui.radio_value(&mut self.plot, Plot::Sin, "Sin").clicked() + if ui.radio_value(&mut data.plot, Plot::Sin, "Sin").clicked() || ui - .radio_value(&mut self.plot, Plot::Bell, "Gaussian") + .radio_value(&mut data.plot, Plot::Bell, "Gaussian") .clicked() || ui - .radio_value(&mut self.plot, Plot::Sigmoid, "Sigmoid") + .radio_value(&mut data.plot, Plot::Sigmoid, "Sigmoid") .clicked() { ui.close_menu(); @@ -87,22 +102,22 @@ impl super::View for ContextMenus { }); egui::Grid::new("button_grid").show(ui, |ui| { ui.add( - egui::DragValue::new(&mut self.width) + egui::DragValue::new(&mut data.width) .speed(1.0) .prefix("Width:"), ); ui.add( - egui::DragValue::new(&mut self.height) + egui::DragValue::new(&mut data.height) .speed(1.0) .prefix("Height:"), ); ui.end_row(); - ui.checkbox(&mut self.show_axes[0], "x-Axis"); - ui.checkbox(&mut self.show_axes[1], "y-Axis"); + ui.checkbox(&mut data.show_axes[0], "x-Axis"); + ui.checkbox(&mut data.show_axes[1], "y-Axis"); ui.end_row(); - if ui.checkbox(&mut self.allow_drag, "Drag").changed() - || ui.checkbox(&mut self.allow_zoom, "Zoom").changed() - || ui.checkbox(&mut self.allow_scroll, "Scroll").changed() + if ui.checkbox(&mut data.allow_drag, "Drag").changed() + || ui.checkbox(&mut data.allow_zoom, "Zoom").changed() + || ui.checkbox(&mut data.allow_scroll, "Scroll").changed() { ui.close_menu(); } @@ -117,6 +132,7 @@ impl super::View for ContextMenus { impl ContextMenus { fn example_plot(&self, ui: &mut egui::Ui) -> egui::Response { + let data = &mut *self.data.write().unwrap(); use egui::plot::{Line, PlotPoints}; let n = 128; let line = Line::new( @@ -124,7 +140,7 @@ impl ContextMenus { .map(|i| { use std::f64::consts::TAU; let x = egui::remap(i as f64, 0.0..=n as f64, -TAU..=TAU); - match self.plot { + match data.plot { Plot::Sin => [x, x.sin()], Plot::Bell => [x, 10.0 * gaussian(x)], Plot::Sigmoid => [x, sigmoid(x)], @@ -133,14 +149,14 @@ impl ContextMenus { .collect::(), ); egui::plot::Plot::new("example_plot") - .show_axes(self.show_axes) - .allow_drag(self.allow_drag) - .allow_zoom(self.allow_zoom) - .allow_scroll(self.allow_scroll) - .center_x_axis(self.center_x_axis) - .center_x_axis(self.center_y_axis) - .width(self.width) - .height(self.height) + .show_axes(data.show_axes) + .allow_drag(data.allow_drag) + .allow_zoom(data.allow_zoom) + .allow_scroll(data.allow_scroll) + .center_x_axis(data.center_x_axis) + .center_x_axis(data.center_y_axis) + .width(data.width) + .height(data.height) .data_aspect(1.0) .show(ui, |plot_ui| plot_ui.line(line)) .response diff --git a/crates/egui_demo_lib/src/demo/dancing_strings.rs b/crates/egui_demo_lib/src/demo/dancing_strings.rs index 3beb323e7..a7b14c507 100644 --- a/crates/egui_demo_lib/src/demo/dancing_strings.rs +++ b/crates/egui_demo_lib/src/demo/dancing_strings.rs @@ -16,7 +16,7 @@ impl super::Demo for DancingStrings { .open(open) .default_size(vec2(512.0, 256.0)) .vscroll(false) - .show(ctx, |ui| self.ui(ui)); + .show(ctx, |ui| Self::default().ui(ui)); } } diff --git a/crates/egui_demo_lib/src/demo/demo_app_windows.rs b/crates/egui_demo_lib/src/demo/demo_app_windows.rs index 4ad6c2d95..5890645f8 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -1,5 +1,7 @@ use egui::{Context, Modifiers, ScrollArea, Ui}; use std::collections::BTreeSet; +use std::sync::Arc; +use std::sync::RwLock; use super::About; use super::Demo; @@ -12,7 +14,7 @@ use crate::is_mobile; #[cfg_attr(feature = "serde", serde(default))] struct Demos { #[cfg_attr(feature = "serde", serde(skip))] - demos: Vec>, + demos: Vec>, open: BTreeSet, } @@ -45,7 +47,7 @@ impl Default for Demos { } impl Demos { - pub fn from_demos(demos: Vec>) -> Self { + pub fn from_demos(demos: Vec>) -> Self { let mut open = BTreeSet::new(); open.insert( super::widget_gallery::WidgetGallery::default() @@ -81,7 +83,7 @@ impl Demos { #[cfg_attr(feature = "serde", serde(default))] struct Tests { #[cfg_attr(feature = "serde", serde(skip))] - demos: Vec>, + demos: Vec>, open: BTreeSet, } @@ -101,7 +103,7 @@ impl Default for Tests { } impl Tests { - pub fn from_demos(demos: Vec>) -> Self { + pub fn from_demos(demos: Vec>) -> Self { let mut open = BTreeSet::new(); open.insert( super::widget_gallery::WidgetGallery::default() @@ -146,16 +148,23 @@ fn set_open(open: &mut BTreeSet, key: &'static str, is_open: bool) { // ---------------------------------------------------------------------------- /// A menu bar in which you can select different demo windows to show. + #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] -pub struct DemoWindows { +pub struct DemoWindowsData { about_is_open: bool, about: About, demos: Demos, tests: Tests, } +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(default))] +#[derive(Clone, Default)] +pub struct DemoWindows { + data: Arc>, +} -impl Default for DemoWindows { +impl Default for DemoWindowsData { fn default() -> Self { Self { about_is_open: true, @@ -177,32 +186,36 @@ impl DemoWindows { } fn mobile_ui(&mut self, ctx: &Context) { - if self.about_is_open { + let mut about_is_open = self.data.read().unwrap().about_is_open; + if about_is_open { let screen_size = ctx.input(|i| i.screen_rect.size()); let default_width = (screen_size.x - 20.0).min(400.0); - let mut close = false; - egui::Window::new(self.about.name()) + let close = Arc::new(RwLock::new(false)); + let close_ = close.clone(); + let clone = self.clone(); + egui::Window::new(self.data.read().unwrap().about.name()) .anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0]) .default_width(default_width) .default_height(ctx.available_rect().height() - 46.0) .vscroll(true) - .open(&mut self.about_is_open) + .open(&mut about_is_open) .resizable(false) .collapsible(false) - .show(ctx, |ui| { - self.about.ui(ui); + .show(ctx, move |ui| { + let close = close.clone(); + clone.data.write().unwrap().about.ui(ui); ui.add_space(12.0); ui.vertical_centered_justified(|ui| { if ui .button(egui::RichText::new("Continue to the demo!").size(20.0)) .clicked() { - close = true; + *close.write().unwrap() = true; } }); }); - self.about_is_open &= !close; + self.data.write().unwrap().about_is_open &= !*close_.read().unwrap(); } else { self.mobile_top_bar(ctx); self.show_windows(ctx); @@ -275,20 +288,22 @@ impl DemoWindows { /// Show the open windows. fn show_windows(&mut self, ctx: &Context) { - self.about.show(ctx, &mut self.about_is_open); - self.demos.windows(ctx); - self.tests.windows(ctx); + let data = &mut *self.data.write().unwrap(); + data.about.show(ctx, &mut data.about_is_open); + data.demos.windows(ctx); + data.tests.windows(ctx); } fn demo_list_ui(&mut self, ui: &mut egui::Ui) { ScrollArea::vertical().show(ui, |ui| { ui.with_layout(egui::Layout::top_down_justified(egui::Align::LEFT), |ui| { - ui.toggle_value(&mut self.about_is_open, self.about.name()); + let data = &mut *self.data.write().unwrap(); + ui.toggle_value(&mut data.about_is_open, data.about.name()); ui.separator(); - self.demos.checkboxes(ui); + data.demos.checkboxes(ui); ui.separator(); - self.tests.checkboxes(ui); + data.tests.checkboxes(ui); ui.separator(); if ui.button("Organize windows").clicked() { diff --git a/crates/egui_demo_lib/src/demo/drag_and_drop.rs b/crates/egui_demo_lib/src/demo/drag_and_drop.rs index 23d3daafc..618f2ee7c 100644 --- a/crates/egui_demo_lib/src/demo/drag_and_drop.rs +++ b/crates/egui_demo_lib/src/demo/drag_and_drop.rs @@ -1,3 +1,5 @@ +use std::sync::{Arc, RwLock}; + use egui::*; pub fn drag_source(ui: &mut Ui, id: Id, body: impl FnOnce(&mut Ui)) { @@ -75,24 +77,32 @@ pub fn drop_target( InnerResponse::new(ret, response) } -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct DragAndDropDemo { /// columns with items - columns: Vec>, + columns: Arc>>>, +} + +impl PartialEq for DragAndDropDemo { + fn eq(&self, other: &Self) -> bool { + *self.columns.read().unwrap() == *other.columns.read().unwrap() + } } impl Default for DragAndDropDemo { fn default() -> Self { Self { - columns: vec![ - vec!["Item A", "Item B", "Item C"], - vec!["Item D", "Item E"], - vec!["Item F", "Item G", "Item H"], - ] - .into_iter() - .map(|v| v.into_iter().map(ToString::to_string).collect()) - .collect(), + columns: Arc::new(RwLock::new( + vec![ + vec!["Item A", "Item B", "Item C"], + vec!["Item D", "Item E"], + vec!["Item F", "Item G", "Item H"], + ] + .into_iter() + .map(|v| v.into_iter().map(ToString::to_string).collect()) + .collect(), + )), } } } @@ -103,13 +113,14 @@ impl super::Demo for DragAndDropDemo { } fn show(&mut self, ctx: &Context, open: &mut bool) { + let clone = self.clone(); use super::View as _; Window::new(self.name()) .open(open) .default_size(vec2(256.0, 256.0)) .vscroll(false) .resizable(false) - .show(ctx, |ui| self.ui(ui)); + .show(ctx, move |ui| clone.clone().ui(ui)); } } @@ -121,8 +132,9 @@ impl super::View for DragAndDropDemo { let id_source = "my_drag_and_drop_demo"; let mut source_col_row = None; let mut drop_col = None; - ui.columns(self.columns.len(), |uis| { - for (col_idx, column) in self.columns.clone().into_iter().enumerate() { + let mut columns = self.columns.write().unwrap(); + ui.columns(columns.len(), |uis| { + for (col_idx, column) in columns.clone().into_iter().enumerate() { let ui = &mut uis[col_idx]; let can_accept_what_is_being_dragged = true; // We accept anything being dragged (for now) ¯\_(ツ)_/¯ let response = drop_target(ui, can_accept_what_is_being_dragged, |ui| { @@ -133,7 +145,7 @@ impl super::View for DragAndDropDemo { let response = ui.add(Label::new(item).sense(Sense::click())); response.context_menu(|ui| { if ui.button("Remove").clicked() { - self.columns[col_idx].remove(row_idx); + columns[col_idx].remove(row_idx); ui.close_menu(); } }); @@ -148,7 +160,7 @@ impl super::View for DragAndDropDemo { let response = response.context_menu(|ui| { if ui.button("New Item").clicked() { - self.columns[col_idx].push("New Item".to_owned()); + columns[col_idx].push("New Item".to_owned()); ui.close_menu(); } }); @@ -164,8 +176,8 @@ impl super::View for DragAndDropDemo { if let Some(drop_col) = drop_col { if ui.input(|i| i.pointer.any_released()) { // do the drop: - let item = self.columns[source_col].remove(source_row); - self.columns[drop_col].push(item); + let item = columns[source_col].remove(source_row); + columns[drop_col].push(item); } } } diff --git a/crates/egui_demo_lib/src/demo/font_book.rs b/crates/egui_demo_lib/src/demo/font_book.rs index 2b4eebae0..acbdf94fb 100644 --- a/crates/egui_demo_lib/src/demo/font_book.rs +++ b/crates/egui_demo_lib/src/demo/font_book.rs @@ -1,12 +1,19 @@ -use std::collections::BTreeMap; - -pub struct FontBook { +use std::{ + collections::BTreeMap, + sync::{Arc, RwLock}, +}; +pub struct FontBookData { filter: String, font_id: egui::FontId, named_chars: BTreeMap>, } -impl Default for FontBook { +#[derive(Default, Clone)] +pub struct FontBook { + data: Arc>, +} + +impl Default for FontBookData { fn default() -> Self { Self { filter: Default::default(), @@ -22,10 +29,13 @@ impl super::Demo for FontBook { } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { - egui::Window::new(self.name()).open(open).show(ctx, |ui| { - use super::View as _; - self.ui(ui); - }); + let clone = self.clone(); + egui::Window::new(self.name()) + .open(open) + .show(ctx, move |ui| { + use super::View as _; + clone.clone().ui(ui); + }); } } @@ -35,10 +45,12 @@ impl super::View for FontBook { ui.add(crate::egui_github_link_file!()); }); + let data = &mut *self.data.write().unwrap(); + ui.label(format!( "The selected font supports {} characters.", - self.named_chars - .get(&self.font_id.family) + data.named_chars + .get(&data.font_id.family) .map(|map| map.len()) .unwrap_or_default() )); @@ -55,22 +67,22 @@ impl super::View for FontBook { ui.separator(); - egui::introspection::font_id_ui(ui, &mut self.font_id); + egui::introspection::font_id_ui(ui, &mut data.font_id); ui.horizontal(|ui| { ui.label("Filter:"); - ui.add(egui::TextEdit::singleline(&mut self.filter).desired_width(120.0)); - self.filter = self.filter.to_lowercase(); + ui.add(egui::TextEdit::singleline(&mut data.filter).desired_width(120.0)); + data.filter = data.filter.to_lowercase(); if ui.button("x").clicked() { - self.filter.clear(); + data.filter.clear(); } }); - let filter = &self.filter; - let named_chars = self + let filter = &data.filter; + let named_chars = data .named_chars - .entry(self.font_id.family.clone()) - .or_insert_with(|| available_characters(ui, self.font_id.family.clone())); + .entry(data.font_id.family.clone()) + .or_insert_with(|| available_characters(ui, data.font_id.family.clone())); ui.separator(); @@ -81,13 +93,13 @@ impl super::View for FontBook { for (&chr, name) in named_chars { if filter.is_empty() || name.contains(filter) || *filter == chr.to_string() { let button = egui::Button::new( - egui::RichText::new(chr.to_string()).font(self.font_id.clone()), + egui::RichText::new(chr.to_string()).font(data.font_id.clone()), ) .frame(false); let tooltip_ui = |ui: &mut egui::Ui| { ui.label( - egui::RichText::new(chr.to_string()).font(self.font_id.clone()), + egui::RichText::new(chr.to_string()).font(data.font_id.clone()), ); ui.label(format!("{}\nU+{:X}\n\nClick to copy", name, chr as u32)); }; diff --git a/crates/egui_demo_lib/src/demo/highlighting.rs b/crates/egui_demo_lib/src/demo/highlighting.rs index 24cf21c5a..7263fadcc 100644 --- a/crates/egui_demo_lib/src/demo/highlighting.rs +++ b/crates/egui_demo_lib/src/demo/highlighting.rs @@ -14,7 +14,7 @@ impl super::Demo for Highlighting { .open(open) .show(ctx, |ui| { use super::View as _; - self.ui(ui); + Self::default().ui(ui); }); } } diff --git a/crates/egui_demo_lib/src/demo/layout_test.rs b/crates/egui_demo_lib/src/demo/layout_test.rs index 2d4fa8a82..3e28cba23 100644 --- a/crates/egui_demo_lib/src/demo/layout_test.rs +++ b/crates/egui_demo_lib/src/demo/layout_test.rs @@ -1,8 +1,10 @@ +use std::sync::{Arc, RwLock}; + use egui::*; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] -pub struct LayoutTest { +pub struct LayoutTestData { // Identical to contents of `egui::Layout` layout: LayoutSettings, @@ -11,7 +13,14 @@ pub struct LayoutTest { wrap_row_height: f32, } -impl Default for LayoutTest { +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(default))] +#[derive(Default, Clone)] +pub struct LayoutTest { + data: Arc>, +} + +impl Default for LayoutTestData { fn default() -> Self { Self { layout: LayoutSettings::top_down(), @@ -79,12 +88,13 @@ impl super::Demo for LayoutTest { } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + let clone = self.clone(); egui::Window::new(self.name()) .open(open) .resizable(false) - .show(ctx, |ui| { + .show(ctx, move |ui| { use super::View as _; - self.ui(ui); + clone.clone().ui(ui); }); } } @@ -93,23 +103,24 @@ impl super::View for LayoutTest { fn ui(&mut self, ui: &mut Ui) { ui.label("Tests and demonstrates the limits of the egui layouts"); self.content_ui(ui); + let data = self.data.write().unwrap(); Resize::default() .default_size([150.0, 200.0]) .show(ui, |ui| { - if self.layout.main_wrap { - if self.layout.main_dir.is_horizontal() { + if data.layout.main_wrap { + if data.layout.main_dir.is_horizontal() { ui.allocate_ui( - vec2(ui.available_size_before_wrap().x, self.wrap_row_height), - |ui| ui.with_layout(self.layout.layout(), demo_ui), + vec2(ui.available_size_before_wrap().x, data.wrap_row_height), + |ui| ui.with_layout(data.layout.layout(), demo_ui), ); } else { ui.allocate_ui( - vec2(self.wrap_column_width, ui.available_size_before_wrap().y), - |ui| ui.with_layout(self.layout.layout(), demo_ui), + vec2(data.wrap_column_width, ui.available_size_before_wrap().y), + |ui| ui.with_layout(data.layout.layout(), demo_ui), ); } } else { - ui.with_layout(self.layout.layout(), demo_ui); + ui.with_layout(data.layout.layout(), demo_ui); } }); ui.label("Resize to see effect"); @@ -118,15 +129,16 @@ impl super::View for LayoutTest { impl LayoutTest { pub fn content_ui(&mut self, ui: &mut Ui) { + let mut data = self.data.write().unwrap(); ui.horizontal(|ui| { - ui.selectable_value(&mut self.layout, LayoutSettings::top_down(), "Top-down"); + ui.selectable_value(&mut data.layout, LayoutSettings::top_down(), "Top-down"); ui.selectable_value( - &mut self.layout, + &mut data.layout, LayoutSettings::top_down_justified_centered(), "Top-down, centered and justified", ); ui.selectable_value( - &mut self.layout, + &mut data.layout, LayoutSettings::horizontal_wrapped(), "Horizontal wrapped", ); @@ -140,20 +152,20 @@ impl LayoutTest { Direction::TopDown, Direction::BottomUp, ] { - ui.radio_value(&mut self.layout.main_dir, dir, format!("{:?}", dir)); + ui.radio_value(&mut data.layout.main_dir, dir, format!("{:?}", dir)); } }); ui.horizontal(|ui| { - ui.checkbox(&mut self.layout.main_wrap, "Main wrap") + ui.checkbox(&mut data.layout.main_wrap, "Main wrap") .on_hover_text("Wrap when next widget doesn't fit the current row/column"); - if self.layout.main_wrap { - if self.layout.main_dir.is_horizontal() { - ui.add(Slider::new(&mut self.wrap_row_height, 0.0..=200.0).text("Row height")); + if data.layout.main_wrap { + if data.layout.main_dir.is_horizontal() { + ui.add(Slider::new(&mut data.wrap_row_height, 0.0..=200.0).text("Row height")); } else { ui.add( - Slider::new(&mut self.wrap_column_width, 0.0..=200.0).text("Column width"), + Slider::new(&mut data.wrap_column_width, 0.0..=200.0).text("Column width"), ); } } @@ -162,11 +174,11 @@ impl LayoutTest { ui.horizontal(|ui| { ui.label("Cross Align:"); for &align in &[Align::Min, Align::Center, Align::Max] { - ui.radio_value(&mut self.layout.cross_align, align, format!("{:?}", align)); + ui.radio_value(&mut data.layout.cross_align, align, format!("{:?}", align)); } }); - ui.checkbox(&mut self.layout.cross_justify, "Cross Justified") + ui.checkbox(&mut data.layout.cross_justify, "Cross Justified") .on_hover_text("Try to fill full width/height (e.g. buttons)"); } } diff --git a/crates/egui_demo_lib/src/demo/misc_demo_window.rs b/crates/egui_demo_lib/src/demo/misc_demo_window.rs index 61a6091e9..fbcdb089e 100644 --- a/crates/egui_demo_lib/src/demo/misc_demo_window.rs +++ b/crates/egui_demo_lib/src/demo/misc_demo_window.rs @@ -1,11 +1,12 @@ +use std::sync::{Arc, RwLock}; + use super::*; use crate::LOREM_IPSUM; use egui::{epaint::text::TextWrapping, *}; -/// Showcase some ui code #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] -pub struct MiscDemoWindow { +pub struct MiscDemoWindowData { num_columns: usize, break_anywhere: bool, @@ -21,10 +22,17 @@ pub struct MiscDemoWindow { dummy_bool: bool, dummy_usize: usize, } +/// Showcase some ui code +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(default))] +#[derive(Default, Clone)] +pub struct MiscDemoWindow { + data: Arc>, +} -impl Default for MiscDemoWindow { - fn default() -> MiscDemoWindow { - MiscDemoWindow { +impl Default for MiscDemoWindowData { + fn default() -> MiscDemoWindowData { + MiscDemoWindowData { num_columns: 2, max_rows: 2, @@ -49,11 +57,12 @@ impl Demo for MiscDemoWindow { } fn show(&mut self, ctx: &Context, open: &mut bool) { + let clone = self.clone(); Window::new(self.name()) .open(open) .vscroll(true) .hscroll(true) - .show(ctx, |ui| self.ui(ui)); + .show(ctx, move |ui| clone.clone().ui(ui)); } } @@ -61,10 +70,13 @@ impl View for MiscDemoWindow { fn ui(&mut self, ui: &mut Ui) { ui.set_min_width(250.0); + let mut data = self.data.write().unwrap(); + let data = &mut *data; + CollapsingHeader::new("Widgets") .default_open(true) .show(ui, |ui| { - self.widgets.ui(ui); + data.widgets.ui(ui); }); CollapsingHeader::new("Text layout") @@ -72,25 +84,25 @@ impl View for MiscDemoWindow { .show(ui, |ui| { text_layout_ui( ui, - &mut self.max_rows, - &mut self.break_anywhere, - &mut self.overflow_character, + &mut data.max_rows, + &mut data.break_anywhere, + &mut data.overflow_character, ); }); CollapsingHeader::new("Colors") .default_open(false) .show(ui, |ui| { - self.colors.ui(ui); + data.colors.ui(ui); }); CollapsingHeader::new("Custom Collapsing Header") .default_open(false) - .show(ui, |ui| self.custom_collapsing_header.ui(ui)); + .show(ui, |ui| data.custom_collapsing_header.ui(ui)); CollapsingHeader::new("Tree") .default_open(false) - .show(ui, |ui| self.tree.ui(ui)); + .show(ui, |ui| data.tree.ui(ui)); CollapsingHeader::new("Checkboxes") .default_open(false) @@ -99,28 +111,28 @@ impl View for MiscDemoWindow { ui.spacing_mut().item_spacing = Vec2::ZERO; ui.horizontal_wrapped(|ui| { for _ in 0..64 { - ui.checkbox(&mut self.dummy_bool, ""); + ui.checkbox(&mut data.dummy_bool, ""); } }); - ui.checkbox(&mut self.dummy_bool, "checkbox"); + ui.checkbox(&mut data.dummy_bool, "checkbox"); ui.label("Radiobuttons are similar:"); ui.spacing_mut().item_spacing = Vec2::ZERO; ui.horizontal_wrapped(|ui| { for i in 0..64 { - ui.radio_value(&mut self.dummy_usize, i, ""); + ui.radio_value(&mut data.dummy_usize, i, ""); } }); - ui.radio_value(&mut self.dummy_usize, 64, "radio_value"); + ui.radio_value(&mut data.dummy_usize, 64, "radio_value"); }); ui.collapsing("Columns", |ui| { - ui.add(Slider::new(&mut self.num_columns, 1..=10).text("Columns")); - ui.columns(self.num_columns, |cols| { + ui.add(Slider::new(&mut data.num_columns, 1..=10).text("Columns")); + ui.columns(data.num_columns, |cols| { for (i, col) in cols.iter_mut().enumerate() { - col.label(format!("Column {} out of {}", i + 1, self.num_columns)); - if i + 1 == self.num_columns && col.button("Delete this").clicked() { - self.num_columns -= 1; + col.label(format!("Column {} out of {}", i + 1, data.num_columns)); + if i + 1 == data.num_columns && col.button("Delete this").clicked() { + data.num_columns -= 1; } } }); @@ -128,7 +140,7 @@ impl View for MiscDemoWindow { CollapsingHeader::new("Test box rendering") .default_open(false) - .show(ui, |ui| self.box_painting.ui(ui)); + .show(ui, |ui| data.box_painting.ui(ui)); CollapsingHeader::new("Resize") .default_open(false) diff --git a/crates/egui_demo_lib/src/demo/multi_touch.rs b/crates/egui_demo_lib/src/demo/multi_touch.rs index c358f6b21..20d33c89f 100644 --- a/crates/egui_demo_lib/src/demo/multi_touch.rs +++ b/crates/egui_demo_lib/src/demo/multi_touch.rs @@ -1,16 +1,23 @@ +use std::sync::{Arc, RwLock}; + use egui::{ emath::{RectTransform, Rot2}, vec2, Color32, Frame, Pos2, Rect, Sense, Stroke, Vec2, }; -pub struct MultiTouch { +pub struct MultiTouchData { rotation: f32, translation: Vec2, zoom: f32, last_touch_time: f64, } -impl Default for MultiTouch { +#[derive(Clone, Default)] +pub struct MultiTouch { + data: Arc>, +} + +impl Default for MultiTouchData { fn default() -> Self { Self { rotation: 0., @@ -27,13 +34,14 @@ impl super::Demo for MultiTouch { } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + let clone = self.clone(); egui::Window::new(self.name()) .open(open) .default_size(vec2(512.0, 512.0)) .resizable(true) - .show(ctx, |ui| { + .show(ctx, move |ui| { use super::View as _; - self.ui(ui); + clone.clone().ui(ui); }); } } @@ -83,22 +91,24 @@ impl super::View for MultiTouch { // color and width: let mut stroke_width = 1.; if let Some(multi_touch) = ui.ctx().multi_touch() { + let mut data = self.data.write().unwrap(); // This adjusts the current zoom factor and rotation angle according to the dynamic // change (for the current frame) of the touch gesture: - self.zoom *= multi_touch.zoom_delta; - self.rotation += multi_touch.rotation_delta; + data.zoom *= multi_touch.zoom_delta; + data.rotation += multi_touch.rotation_delta; // the translation we get from `multi_touch` needs to be scaled down to the // normalized coordinates we use as the basis for painting: - self.translation += to_screen.inverse().scale() * multi_touch.translation_delta; + data.translation += to_screen.inverse().scale() * multi_touch.translation_delta; // touch pressure will make the arrow thicker (not all touch devices support this): stroke_width += 10. * multi_touch.force; - self.last_touch_time = ui.input(|i| i.time); + data.last_touch_time = ui.input(|i| i.time); } else { self.slowly_reset(ui); } - let zoom_and_rotate = self.zoom * Rot2::from_angle(self.rotation); - let arrow_start_offset = self.translation + zoom_and_rotate * vec2(-0.5, 0.5); + let data = self.data.read().unwrap(); + let zoom_and_rotate = data.zoom * Rot2::from_angle(data.rotation); + let arrow_start_offset = data.translation + zoom_and_rotate * vec2(-0.5, 0.5); // Paints an arrow pointing from bottom-left (-0.5, 0.5) to top-right (0.5, -0.5), but // scaled, rotated, and translated according to the current touch gesture: @@ -118,7 +128,8 @@ impl MultiTouch { // This has nothing to do with the touch gesture. It just smoothly brings the // painted arrow back into its original position, for a nice visual effect: - let time_since_last_touch = (ui.input(|i| i.time) - self.last_touch_time) as f32; + let mut data = self.data.write().unwrap(); + let time_since_last_touch = (ui.input(|i| i.time) - data.last_touch_time) as f32; let delay = 0.5; if time_since_last_touch < delay { @@ -129,15 +140,15 @@ impl MultiTouch { egui::remap_clamp(time_since_last_touch, delay..=1.0, 1.0..=0.0).powf(4.0); if half_life <= 1e-3 { - self.zoom = 1.0; - self.rotation = 0.0; - self.translation = Vec2::ZERO; + data.zoom = 1.0; + data.rotation = 0.0; + data.translation = Vec2::ZERO; } else { let dt = ui.input(|i| i.unstable_dt); let half_life_factor = (-(2_f32.ln()) / half_life * dt).exp(); - self.zoom = 1. + ((self.zoom - 1.) * half_life_factor); - self.rotation *= half_life_factor; - self.translation *= half_life_factor; + data.zoom = 1. + ((data.zoom - 1.) * half_life_factor); + data.rotation *= half_life_factor; + data.translation *= half_life_factor; ui.ctx().request_repaint(); } } diff --git a/crates/egui_demo_lib/src/demo/paint_bezier.rs b/crates/egui_demo_lib/src/demo/paint_bezier.rs index ad507de50..a6601aeb1 100644 --- a/crates/egui_demo_lib/src/demo/paint_bezier.rs +++ b/crates/egui_demo_lib/src/demo/paint_bezier.rs @@ -1,9 +1,11 @@ +use std::sync::{Arc, RwLock}; + use egui::epaint::{CubicBezierShape, PathShape, QuadraticBezierShape}; use egui::*; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] -pub struct PaintBezier { +pub struct PaintBezierData { /// Bézier curve degree, it can be 3, 4. degree: usize, @@ -22,7 +24,14 @@ pub struct PaintBezier { bounding_box_stroke: Stroke, } -impl Default for PaintBezier { +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(default))] +#[derive(Default, Clone)] +pub struct PaintBezier { + data: Arc>, +} + +impl Default for PaintBezierData { fn default() -> Self { Self { degree: 4, @@ -42,14 +51,15 @@ impl Default for PaintBezier { impl PaintBezier { pub fn ui_control(&mut self, ui: &mut egui::Ui) { + let mut data = self.data.write().unwrap(); ui.collapsing("Colors", |ui| { ui.horizontal(|ui| { ui.label("Fill color:"); - ui.color_edit_button_srgba(&mut self.fill); + ui.color_edit_button_srgba(&mut data.fill); }); - egui::stroke_ui(ui, &mut self.stroke, "Curve Stroke"); - egui::stroke_ui(ui, &mut self.aux_stroke, "Auxiliary Stroke"); - egui::stroke_ui(ui, &mut self.bounding_box_stroke, "Bounding Box Stroke"); + egui::stroke_ui(ui, &mut data.stroke, "Curve Stroke"); + egui::stroke_ui(ui, &mut data.aux_stroke, "Auxiliary Stroke"); + egui::stroke_ui(ui, &mut data.bounding_box_stroke, "Bounding Box Stroke"); }); ui.collapsing("Global tessellation options", |ui| { @@ -59,8 +69,8 @@ impl PaintBezier { .tessellation_options_mut(|to| *to = tessellation_options); }); - ui.radio_value(&mut self.degree, 3, "Quadratic Bézier"); - ui.radio_value(&mut self.degree, 4, "Cubic Bézier"); + ui.radio_value(&mut data.degree, 3, "Quadratic Bézier"); + ui.radio_value(&mut data.degree, 4, "Cubic Bézier"); ui.label("Move the points by dragging them."); ui.small("Only convex curves can be accurately filled."); } @@ -75,12 +85,14 @@ impl PaintBezier { ); let control_point_radius = 8.0; + let mut data = self.data.write().unwrap(); + let data = &mut *data; - let control_point_shapes: Vec = self + let control_point_shapes: Vec = data .control_points .iter_mut() .enumerate() - .take(self.degree) + .take(data.degree) .map(|(i, point)| { let size = Vec2::splat(2.0 * control_point_radius); @@ -99,33 +111,33 @@ impl PaintBezier { }) .collect(); - let points_in_screen: Vec = self + let points_in_screen: Vec = data .control_points .iter() - .take(self.degree) + .take(data.degree) .map(|p| to_screen * *p) .collect(); - match self.degree { + match data.degree { 3 => { let points = points_in_screen.clone().try_into().unwrap(); let shape = - QuadraticBezierShape::from_points_stroke(points, true, self.fill, self.stroke); + QuadraticBezierShape::from_points_stroke(points, true, data.fill, data.stroke); painter.add(epaint::RectShape::stroke( shape.visual_bounding_rect(), 0.0, - self.bounding_box_stroke, + data.bounding_box_stroke, )); painter.add(shape); } 4 => { let points = points_in_screen.clone().try_into().unwrap(); let shape = - CubicBezierShape::from_points_stroke(points, true, self.fill, self.stroke); + CubicBezierShape::from_points_stroke(points, true, data.fill, data.stroke); painter.add(epaint::RectShape::stroke( shape.visual_bounding_rect(), 0.0, - self.bounding_box_stroke, + data.bounding_box_stroke, )); painter.add(shape); } @@ -134,7 +146,7 @@ impl PaintBezier { } }; - painter.add(PathShape::line(points_in_screen, self.aux_stroke)); + painter.add(PathShape::line(points_in_screen, data.aux_stroke)); painter.extend(control_point_shapes); response @@ -147,13 +159,14 @@ impl super::Demo for PaintBezier { } fn show(&mut self, ctx: &Context, open: &mut bool) { + let clone = self.clone(); use super::View as _; Window::new(self.name()) .open(open) .vscroll(false) .resizable(false) .default_size([300.0, 350.0]) - .show(ctx, |ui| self.ui(ui)); + .show(ctx, move |ui| clone.clone().ui(ui)); } } diff --git a/crates/egui_demo_lib/src/demo/painting.rs b/crates/egui_demo_lib/src/demo/painting.rs index 57e5f114d..d3a03224f 100644 --- a/crates/egui_demo_lib/src/demo/painting.rs +++ b/crates/egui_demo_lib/src/demo/painting.rs @@ -1,14 +1,22 @@ -use egui::*; +use std::sync::{Arc, RwLock}; +use egui::*; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] -pub struct Painting { +pub struct PaintingData { /// in 0-1 normalized coordinates lines: Vec>, stroke: Stroke, } -impl Default for Painting { +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(default))] +#[derive(Default, Clone)] +pub struct Painting { + data: Arc>, +} + +impl Default for PaintingData { fn default() -> Self { Self { lines: Default::default(), @@ -19,11 +27,12 @@ impl Default for Painting { impl Painting { pub fn ui_control(&mut self, ui: &mut egui::Ui) -> egui::Response { + let mut data = self.data.write().unwrap(); ui.horizontal(|ui| { - egui::stroke_ui(ui, &mut self.stroke, "Stroke"); + egui::stroke_ui(ui, &mut data.stroke, "Stroke"); ui.separator(); if ui.button("Clear Painting").clicked() { - self.lines.clear(); + data.lines.clear(); } }) .response @@ -39,11 +48,13 @@ impl Painting { ); let from_screen = to_screen.inverse(); - if self.lines.is_empty() { - self.lines.push(vec![]); + let mut data = self.data.write().unwrap(); + + if data.lines.is_empty() { + data.lines.push(vec![]); } - let current_line = self.lines.last_mut().unwrap(); + let current_line = data.lines.last_mut().unwrap(); if let Some(pointer_pos) = response.interact_pointer_pos() { let canvas_pos = from_screen * pointer_pos; @@ -52,17 +63,17 @@ impl Painting { response.mark_changed(); } } else if !current_line.is_empty() { - self.lines.push(vec![]); + data.lines.push(vec![]); response.mark_changed(); } - let shapes = self + let shapes = data .lines .iter() .filter(|line| line.len() >= 2) .map(|line| { let points: Vec = line.iter().map(|p| to_screen * *p).collect(); - egui::Shape::line(points, self.stroke) + egui::Shape::line(points, data.stroke) }); painter.extend(shapes); @@ -77,12 +88,13 @@ impl super::Demo for Painting { } fn show(&mut self, ctx: &Context, open: &mut bool) { + let clone = self.clone(); use super::View as _; Window::new(self.name()) .open(open) .default_size(vec2(512.0, 512.0)) .vscroll(false) - .show(ctx, |ui| self.ui(ui)); + .show(ctx, move |ui| clone.clone().ui(ui)); } } diff --git a/crates/egui_demo_lib/src/demo/plot_demo.rs b/crates/egui_demo_lib/src/demo/plot_demo.rs index 12d79ce0e..2af182b05 100644 --- a/crates/egui_demo_lib/src/demo/plot_demo.rs +++ b/crates/egui_demo_lib/src/demo/plot_demo.rs @@ -1,5 +1,6 @@ use std::f64::consts::TAU; use std::ops::RangeInclusive; +use std::sync::{Arc, RwLock}; use egui::plot::{AxisBools, GridInput, GridMark, PlotResponse}; use egui::*; @@ -32,7 +33,7 @@ impl Default for Panel { // ---------------------------------------------------------------------------- #[derive(PartialEq, Default)] -pub struct PlotDemo { +pub struct PlotDemoData { line_demo: LineDemo, marker_demo: MarkerDemo, legend_demo: LegendDemo, @@ -44,18 +45,30 @@ pub struct PlotDemo { open_panel: Panel, } +#[derive(Default, Clone)] +pub struct PlotDemo { + data: Arc>, +} + +impl PartialEq for PlotDemo { + fn eq(&self, other: &Self) -> bool { + *self.data.read().unwrap() == *other.data.read().unwrap() + } +} + impl super::Demo for PlotDemo { fn name(&self) -> &'static str { "🗠 Plot" } fn show(&mut self, ctx: &Context, open: &mut bool) { + let clone = self.clone(); use super::View as _; Window::new(self.name()) .open(open) .default_size(vec2(400.0, 400.0)) .vscroll(false) - .show(ctx, |ui| self.ui(ui)); + .show(ctx, move |ui| clone.clone().ui(ui)); } } @@ -77,43 +90,44 @@ impl super::View for PlotDemo { ui.add(crate::egui_github_link_file!()); }); }); + let mut data = self.data.write().unwrap(); ui.separator(); ui.horizontal(|ui| { - ui.selectable_value(&mut self.open_panel, Panel::Lines, "Lines"); - ui.selectable_value(&mut self.open_panel, Panel::Markers, "Markers"); - ui.selectable_value(&mut self.open_panel, Panel::Legend, "Legend"); - ui.selectable_value(&mut self.open_panel, Panel::Charts, "Charts"); - ui.selectable_value(&mut self.open_panel, Panel::Items, "Items"); - ui.selectable_value(&mut self.open_panel, Panel::Interaction, "Interaction"); - ui.selectable_value(&mut self.open_panel, Panel::CustomAxes, "Custom Axes"); - ui.selectable_value(&mut self.open_panel, Panel::LinkedAxes, "Linked Axes"); + ui.selectable_value(&mut data.open_panel, Panel::Lines, "Lines"); + ui.selectable_value(&mut data.open_panel, Panel::Markers, "Markers"); + ui.selectable_value(&mut data.open_panel, Panel::Legend, "Legend"); + ui.selectable_value(&mut data.open_panel, Panel::Charts, "Charts"); + ui.selectable_value(&mut data.open_panel, Panel::Items, "Items"); + ui.selectable_value(&mut data.open_panel, Panel::Interaction, "Interaction"); + ui.selectable_value(&mut data.open_panel, Panel::CustomAxes, "Custom Axes"); + ui.selectable_value(&mut data.open_panel, Panel::LinkedAxes, "Linked Axes"); }); ui.separator(); - match self.open_panel { + match data.open_panel { Panel::Lines => { - self.line_demo.ui(ui); + data.line_demo.ui(ui); } Panel::Markers => { - self.marker_demo.ui(ui); + data.marker_demo.ui(ui); } Panel::Legend => { - self.legend_demo.ui(ui); + data.legend_demo.ui(ui); } Panel::Charts => { - self.charts_demo.ui(ui); + data.charts_demo.ui(ui); } Panel::Items => { - self.items_demo.ui(ui); + data.items_demo.ui(ui); } Panel::Interaction => { - self.interaction_demo.ui(ui); + data.interaction_demo.ui(ui); } Panel::CustomAxes => { - self.custom_axes_demo.ui(ui); + data.custom_axes_demo.ui(ui); } Panel::LinkedAxes => { - self.linked_axes_demo.ui(ui); + data.linked_axes_demo.ui(ui); } } } diff --git a/crates/egui_demo_lib/src/demo/scrolling.rs b/crates/egui_demo_lib/src/demo/scrolling.rs index b970b3264..1f9b65d6f 100644 --- a/crates/egui_demo_lib/src/demo/scrolling.rs +++ b/crates/egui_demo_lib/src/demo/scrolling.rs @@ -1,3 +1,5 @@ +use std::sync::{Arc, RwLock}; + use egui::*; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -19,49 +21,64 @@ impl Default for ScrollDemo { #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] #[derive(Default, PartialEq)] -pub struct Scrolling { +pub struct ScrollingData { demo: ScrollDemo, scroll_to: ScrollTo, scroll_stick_to: ScrollStickTo, } +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(default))] +#[derive(Default, Clone)] +pub struct Scrolling { + data: Arc>, +} + +impl PartialEq for Scrolling { + fn eq(&self, other: &Self) -> bool { + *self.data.read().unwrap() == *other.data.read().unwrap() + } +} + impl super::Demo for Scrolling { fn name(&self) -> &'static str { "↕ Scrolling" } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + let clone = self.clone(); egui::Window::new(self.name()) .open(open) .resizable(false) - .show(ctx, |ui| { + .show(ctx, move |ui| { use super::View as _; - self.ui(ui); + clone.clone().ui(ui); }); } } impl super::View for Scrolling { fn ui(&mut self, ui: &mut Ui) { + let mut data = self.data.write().unwrap(); ui.horizontal(|ui| { - ui.selectable_value(&mut self.demo, ScrollDemo::ScrollTo, "Scroll to"); + ui.selectable_value(&mut data.demo, ScrollDemo::ScrollTo, "Scroll to"); ui.selectable_value( - &mut self.demo, + &mut data.demo, ScrollDemo::ManyLines, "Scroll a lot of lines", ); ui.selectable_value( - &mut self.demo, + &mut data.demo, ScrollDemo::LargeCanvas, "Scroll a large canvas", ); - ui.selectable_value(&mut self.demo, ScrollDemo::StickToEnd, "Stick to end"); - ui.selectable_value(&mut self.demo, ScrollDemo::Bidirectional, "Bidirectional"); + ui.selectable_value(&mut data.demo, ScrollDemo::StickToEnd, "Stick to end"); + ui.selectable_value(&mut data.demo, ScrollDemo::Bidirectional, "Bidirectional"); }); ui.separator(); - match self.demo { + match data.demo { ScrollDemo::ScrollTo => { - self.scroll_to.ui(ui); + data.scroll_to.ui(ui); } ScrollDemo::ManyLines => { huge_content_lines(ui); @@ -70,7 +87,7 @@ impl super::View for Scrolling { huge_content_painter(ui); } ScrollDemo::StickToEnd => { - self.scroll_stick_to.ui(ui); + data.scroll_stick_to.ui(ui); } ScrollDemo::Bidirectional => { egui::ScrollArea::both().show(ui, |ui| { diff --git a/crates/egui_demo_lib/src/demo/sliders.rs b/crates/egui_demo_lib/src/demo/sliders.rs index 1f565a544..2dce3b12c 100644 --- a/crates/egui_demo_lib/src/demo/sliders.rs +++ b/crates/egui_demo_lib/src/demo/sliders.rs @@ -1,11 +1,14 @@ use egui::*; -use std::f64::INFINITY; +use std::{ + f64::INFINITY, + sync::{Arc, RwLock}, +}; /// Showcase sliders #[derive(PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] -pub struct Sliders { +pub struct SlidersData { pub min: f64, pub max: f64, pub logarithmic: bool, @@ -19,7 +22,20 @@ pub struct Sliders { pub trailing_fill: bool, } -impl Default for Sliders { +#[derive(Clone, Default)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(default))] +pub struct Sliders { + pub data: Arc>, +} + +impl PartialEq for Sliders { + fn eq(&self, other: &Self) -> bool { + *self.data.read().unwrap() == *other.data.read().unwrap() + } +} + +impl Default for SlidersData { fn default() -> Self { Self { min: 0.0, @@ -43,149 +59,154 @@ impl super::Demo for Sliders { } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + let clone = self.clone(); egui::Window::new(self.name()) .open(open) .resizable(false) - .show(ctx, |ui| { + .show(ctx, move |ui| { use super::View as _; - self.ui(ui); + clone.clone().ui(ui); }); } } impl super::View for Sliders { fn ui(&mut self, ui: &mut Ui) { - let Self { - min, - max, - logarithmic, - clamp_to_range, - smart_aim, - step, - use_steps, - integer, - vertical, - value, - trailing_fill, - } = self; + { + let SlidersData { + min, + max, + logarithmic, + clamp_to_range, + smart_aim, + step, + use_steps, + integer, + vertical, + value, + trailing_fill, + } = &mut *self.data.write().unwrap(); - ui.label("You can click a slider value to edit it with the keyboard."); + ui.label("You can click a slider value to edit it with the keyboard."); - let (type_min, type_max) = if *integer { - ((i32::MIN as f64), (i32::MAX as f64)) - } else if *logarithmic { - (-INFINITY, INFINITY) - } else { - (-1e5, 1e5) // linear sliders make little sense with huge numbers - }; + let (type_min, type_max) = if *integer { + ((i32::MIN as f64), (i32::MAX as f64)) + } else if *logarithmic { + (-INFINITY, INFINITY) + } else { + (-1e5, 1e5) // linear sliders make little sense with huge numbers + }; - *min = min.clamp(type_min, type_max); - *max = max.clamp(type_min, type_max); + *min = min.clamp(type_min, type_max); + *max = max.clamp(type_min, type_max); - let orientation = if *vertical { - SliderOrientation::Vertical - } else { - SliderOrientation::Horizontal - }; + let orientation = if *vertical { + SliderOrientation::Vertical + } else { + SliderOrientation::Horizontal + }; - let istep = if *use_steps { *step } else { 0.0 }; - if *integer { - let mut value_i32 = *value as i32; - ui.add( - Slider::new(&mut value_i32, (*min as i32)..=(*max as i32)) - .logarithmic(*logarithmic) - .clamp_to_range(*clamp_to_range) - .smart_aim(*smart_aim) - .orientation(orientation) - .text("i32 demo slider") - .step_by(istep) - .trailing_fill(*trailing_fill), - ); - *value = value_i32 as f64; - } else { - ui.add( - Slider::new(value, (*min)..=(*max)) - .logarithmic(*logarithmic) - .clamp_to_range(*clamp_to_range) - .smart_aim(*smart_aim) - .orientation(orientation) - .text("f64 demo slider") - .step_by(istep) - .trailing_fill(*trailing_fill), - ); + let istep = if *use_steps { *step } else { 0.0 }; + if *integer { + let mut value_i32 = *value as i32; + ui.add( + Slider::new(&mut value_i32, (*min as i32)..=(*max as i32)) + .logarithmic(*logarithmic) + .clamp_to_range(*clamp_to_range) + .smart_aim(*smart_aim) + .orientation(orientation) + .text("i32 demo slider") + .step_by(istep) + .trailing_fill(*trailing_fill), + ); + *value = value_i32 as f64; + } else { + ui.add( + Slider::new(value, (*min)..=(*max)) + .logarithmic(*logarithmic) + .clamp_to_range(*clamp_to_range) + .smart_aim(*smart_aim) + .orientation(orientation) + .text("f64 demo slider") + .step_by(istep) + .trailing_fill(*trailing_fill), + ); - ui.label( - "Sliders will intelligently pick how many decimals to show. \ + ui.label( + "Sliders will intelligently pick how many decimals to show. \ You can always see the full precision value by hovering the value.", + ); + + if ui.button("Assign PI").clicked() { + self.data.write().unwrap().value = std::f64::consts::PI; + } + } + + ui.separator(); + + ui.label("Slider range:"); + ui.add( + Slider::new(min, type_min..=type_max) + .logarithmic(true) + .smart_aim(*smart_aim) + .text("left") + .trailing_fill(*trailing_fill), + ); + ui.add( + Slider::new(max, type_min..=type_max) + .logarithmic(true) + .smart_aim(*smart_aim) + .text("right") + .trailing_fill(*trailing_fill), ); - if ui.button("Assign PI").clicked() { - self.value = std::f64::consts::PI; + ui.separator(); + + ui.checkbox(trailing_fill, "Toggle trailing color"); + ui.label("When enabled, trailing color will be painted up until the circle."); + + ui.separator(); + + ui.checkbox(use_steps, "Use steps"); + ui.label("When enabled, the minimal value change would be restricted to a given step."); + if *use_steps { + ui.add(egui::DragValue::new(step).speed(1.0)); } + + ui.separator(); + + ui.horizontal(|ui| { + ui.label("Slider type:"); + ui.radio_value(integer, true, "i32"); + ui.radio_value(integer, false, "f64"); + }) + .response + .on_hover_text("All numeric types (f32, usize, …) are supported."); + + ui.horizontal(|ui| { + ui.label("Slider orientation:"); + ui.radio_value(vertical, false, "Horizontal"); + ui.radio_value(vertical, true, "Vertical"); + }); + ui.add_space(8.0); + + ui.checkbox(logarithmic, "Logarithmic"); + ui.label("Logarithmic sliders are great for when you want to span a huge range, i.e. from zero to a million."); + ui.label("Logarithmic sliders can include infinity and zero."); + ui.add_space(8.0); + + ui.checkbox(clamp_to_range, "Clamp to range"); + ui.label( + "If true, the slider will clamp incoming and outgoing values to the given range.", + ); + ui.label("If false, the slider can shows values outside its range, and you can manually enter values outside the range."); + ui.add_space(8.0); + + ui.checkbox(smart_aim, "Smart Aim"); + ui.label("Smart Aim will guide you towards round values when you drag the slider so you you are more likely to hit 250 than 247.23"); + ui.add_space(8.0); } - ui.separator(); - - ui.label("Slider range:"); - ui.add( - Slider::new(min, type_min..=type_max) - .logarithmic(true) - .smart_aim(*smart_aim) - .text("left") - .trailing_fill(*trailing_fill), - ); - ui.add( - Slider::new(max, type_min..=type_max) - .logarithmic(true) - .smart_aim(*smart_aim) - .text("right") - .trailing_fill(*trailing_fill), - ); - - ui.separator(); - - ui.checkbox(trailing_fill, "Toggle trailing color"); - ui.label("When enabled, trailing color will be painted up until the circle."); - - ui.separator(); - - ui.checkbox(use_steps, "Use steps"); - ui.label("When enabled, the minimal value change would be restricted to a given step."); - if *use_steps { - ui.add(egui::DragValue::new(step).speed(1.0)); - } - - ui.separator(); - - ui.horizontal(|ui| { - ui.label("Slider type:"); - ui.radio_value(integer, true, "i32"); - ui.radio_value(integer, false, "f64"); - }) - .response - .on_hover_text("All numeric types (f32, usize, …) are supported."); - - ui.horizontal(|ui| { - ui.label("Slider orientation:"); - ui.radio_value(vertical, false, "Horizontal"); - ui.radio_value(vertical, true, "Vertical"); - }); - ui.add_space(8.0); - - ui.checkbox(logarithmic, "Logarithmic"); - ui.label("Logarithmic sliders are great for when you want to span a huge range, i.e. from zero to a million."); - ui.label("Logarithmic sliders can include infinity and zero."); - ui.add_space(8.0); - - ui.checkbox(clamp_to_range, "Clamp to range"); - ui.label("If true, the slider will clamp incoming and outgoing values to the given range."); - ui.label("If false, the slider can shows values outside its range, and you can manually enter values outside the range."); - ui.add_space(8.0); - - ui.checkbox(smart_aim, "Smart Aim"); - ui.label("Smart Aim will guide you towards round values when you drag the slider so you you are more likely to hit 250 than 247.23"); - ui.add_space(8.0); - ui.vertical_centered(|ui| { egui::reset_button(ui, self); ui.add(crate::egui_github_link_file!()); diff --git a/crates/egui_demo_lib/src/demo/strip_demo.rs b/crates/egui_demo_lib/src/demo/strip_demo.rs index 2e9038b3a..1106997d0 100644 --- a/crates/egui_demo_lib/src/demo/strip_demo.rs +++ b/crates/egui_demo_lib/src/demo/strip_demo.rs @@ -18,7 +18,7 @@ impl super::Demo for StripDemo { .default_width(400.0) .show(ctx, |ui| { use super::View as _; - self.ui(ui); + Self::default().ui(ui); }); } } diff --git a/crates/egui_demo_lib/src/demo/table_demo.rs b/crates/egui_demo_lib/src/demo/table_demo.rs index 8fdea4202..384bf1242 100644 --- a/crates/egui_demo_lib/src/demo/table_demo.rs +++ b/crates/egui_demo_lib/src/demo/table_demo.rs @@ -1,3 +1,5 @@ +use std::sync::{Arc, RwLock}; + #[derive(PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] enum DemoType { @@ -6,9 +8,8 @@ enum DemoType { ManyHeterogenous, } -/// Shows off a table with dynamic layout #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct TableDemo { +pub struct TableDemoData { demo: DemoType, striped: bool, resizable: bool, @@ -17,7 +18,14 @@ pub struct TableDemo { scroll_to_row: Option, } -impl Default for TableDemo { +/// Shows off a table with dynamic layout +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[derive(Clone, Default)] +pub struct TableDemo { + data: Arc>, +} + +impl Default for TableDemoData { fn default() -> Self { Self { demo: DemoType::Manual, @@ -36,13 +44,14 @@ impl super::Demo for TableDemo { } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + let clone = self.clone(); egui::Window::new(self.name()) .open(open) .resizable(true) .default_width(400.0) - .show(ctx, |ui| { + .show(ctx, move |ui| { use super::View as _; - self.ui(ui); + clone.clone().ui(ui); }); } } @@ -51,52 +60,55 @@ const NUM_MANUAL_ROWS: usize = 20; impl super::View for TableDemo { fn ui(&mut self, ui: &mut egui::Ui) { - ui.vertical(|ui| { - ui.horizontal(|ui| { - ui.checkbox(&mut self.striped, "Striped"); - ui.checkbox(&mut self.resizable, "Resizable columns"); + { + let mut data = self.data.write().unwrap(); + ui.vertical(|ui| { + ui.horizontal(|ui| { + ui.checkbox(&mut data.striped, "Striped"); + ui.checkbox(&mut data.resizable, "Resizable columns"); + }); + + ui.label("Table type:"); + ui.radio_value(&mut data.demo, DemoType::Manual, "Few, manual rows"); + ui.radio_value( + &mut data.demo, + DemoType::ManyHomogeneous, + "Thousands of rows of same height", + ); + ui.radio_value( + &mut data.demo, + DemoType::ManyHeterogenous, + "Thousands of rows of differing heights", + ); + + if data.demo != DemoType::Manual { + ui.add( + egui::Slider::new(&mut data.num_rows, 0..=100_000) + .logarithmic(true) + .text("Num rows"), + ); + } + + { + let max_rows = if data.demo == DemoType::Manual { + NUM_MANUAL_ROWS + } else { + data.num_rows + }; + + let slider_response = ui.add( + egui::Slider::new(&mut data.scroll_to_row_slider, 0..=max_rows) + .logarithmic(true) + .text("Row to scroll to"), + ); + if slider_response.changed() { + data.scroll_to_row = Some(data.scroll_to_row_slider); + } + } }); - ui.label("Table type:"); - ui.radio_value(&mut self.demo, DemoType::Manual, "Few, manual rows"); - ui.radio_value( - &mut self.demo, - DemoType::ManyHomogeneous, - "Thousands of rows of same height", - ); - ui.radio_value( - &mut self.demo, - DemoType::ManyHeterogenous, - "Thousands of rows of differing heights", - ); - - if self.demo != DemoType::Manual { - ui.add( - egui::Slider::new(&mut self.num_rows, 0..=100_000) - .logarithmic(true) - .text("Num rows"), - ); - } - - { - let max_rows = if self.demo == DemoType::Manual { - NUM_MANUAL_ROWS - } else { - self.num_rows - }; - - let slider_response = ui.add( - egui::Slider::new(&mut self.scroll_to_row_slider, 0..=max_rows) - .logarithmic(true) - .text("Row to scroll to"), - ); - if slider_response.changed() { - self.scroll_to_row = Some(self.scroll_to_row_slider); - } - } - }); - - ui.separator(); + ui.separator(); + } // Leave room for the source code link after the table demo: use egui_extras::{Size, StripBuilder}; @@ -123,10 +135,11 @@ impl TableDemo { use egui_extras::{Column, TableBuilder}; let text_height = egui::TextStyle::Body.resolve(ui.style()).size; + let mut data = self.data.write().unwrap(); let mut table = TableBuilder::new(ui) - .striped(self.striped) - .resizable(self.resizable) + .striped(data.striped) + .resizable(data.resizable) .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) .column(Column::auto()) .column(Column::initial(100.0).range(40.0..=300.0)) @@ -134,7 +147,7 @@ impl TableDemo { .column(Column::remainder()) .min_scrolled_height(0.0); - if let Some(row_nr) = self.scroll_to_row.take() { + if let Some(row_nr) = data.scroll_to_row.take() { table = table.scroll_to_row(row_nr, None); } @@ -153,7 +166,7 @@ impl TableDemo { ui.strong("Content"); }); }) - .body(|mut body| match self.demo { + .body(|mut body| match data.demo { DemoType::Manual => { for row_index in 0..NUM_MANUAL_ROWS { let is_thick = thick_row(row_index); @@ -180,7 +193,7 @@ impl TableDemo { } } DemoType::ManyHomogeneous => { - body.rows(text_height, self.num_rows, |row_index, mut row| { + body.rows(text_height, data.num_rows, |row_index, mut row| { row.col(|ui| { ui.label(row_index.to_string()); }); @@ -206,7 +219,7 @@ impl TableDemo { } } body.heterogeneous_rows( - (0..self.num_rows).map(row_thickness), + (0..data.num_rows).map(row_thickness), |row_index, mut row| { row.col(|ui| { ui.label(row_index.to_string()); diff --git a/crates/egui_demo_lib/src/demo/tests.rs b/crates/egui_demo_lib/src/demo/tests.rs index 83e798769..375e1a07d 100644 --- a/crates/egui_demo_lib/src/demo/tests.rs +++ b/crates/egui_demo_lib/src/demo/tests.rs @@ -1,3 +1,5 @@ +use std::sync::{Arc, RwLock}; + #[derive(Default)] pub struct CursorTest {} @@ -9,7 +11,7 @@ impl super::Demo for CursorTest { fn show(&mut self, ctx: &egui::Context, open: &mut bool) { egui::Window::new(self.name()).open(open).show(ctx, |ui| { use super::View as _; - self.ui(ui); + Self::default().ui(ui); }); } } @@ -41,7 +43,7 @@ impl super::Demo for IdTest { fn show(&mut self, ctx: &egui::Context, open: &mut bool) { egui::Window::new(self.name()).open(open).show(ctx, |ui| { use super::View as _; - self.ui(ui); + Self::default().ui(ui); }); } } @@ -94,15 +96,14 @@ enum WidgetType { TextEdit, } -#[derive(Clone, Debug, PartialEq)] -pub struct ManualLayoutTest { +#[derive(Debug, PartialEq)] +pub struct ManualLayoutTestData { widget_offset: egui::Vec2, widget_size: egui::Vec2, widget_type: WidgetType, text_edit_contents: String, } - -impl Default for ManualLayoutTest { +impl Default for ManualLayoutTestData { fn default() -> Self { Self { widget_offset: egui::Vec2::splat(150.0), @@ -113,18 +114,30 @@ impl Default for ManualLayoutTest { } } +#[derive(Clone, Debug, Default)] +pub struct ManualLayoutTest { + data: Arc>, +} + +impl PartialEq for ManualLayoutTest { + fn eq(&self, other: &Self) -> bool { + *self.data.read().unwrap() == *other.data.read().unwrap() + } +} + impl super::Demo for ManualLayoutTest { fn name(&self) -> &'static str { "Manual Layout Test" } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + let clone = self.clone(); egui::Window::new(self.name()) .resizable(false) .open(open) - .show(ctx, |ui| { + .show(ctx, move |ui| { use super::View as _; - self.ui(ui); + clone.clone().ui(ui); }); } } @@ -133,12 +146,12 @@ impl super::View for ManualLayoutTest { fn ui(&mut self, ui: &mut egui::Ui) { egui::reset_button(ui, self); - let Self { + let ManualLayoutTestData { widget_offset, widget_size, widget_type, text_edit_contents, - } = self; + } = &mut *self.data.write().unwrap(); ui.horizontal(|ui| { ui.label("Test widget:"); ui.radio_value(widget_type, WidgetType::Button, "Button"); @@ -178,9 +191,8 @@ impl super::View for ManualLayoutTest { } // ---------------------------------------------------------------------------- - #[derive(PartialEq)] -pub struct TableTest { +pub struct TableTestData { num_cols: usize, num_rows: usize, min_col_width: f32, @@ -188,7 +200,18 @@ pub struct TableTest { text_length: usize, } -impl Default for TableTest { +#[derive(Default, Clone)] +pub struct TableTest { + data: Arc>, +} + +impl PartialEq for TableTest { + fn eq(&self, other: &Self) -> bool { + *self.data.read().unwrap() == *other.data.read().unwrap() + } +} + +impl Default for TableTestData { fn default() -> Self { Self { num_cols: 4, @@ -206,96 +229,104 @@ impl super::Demo for TableTest { } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { - egui::Window::new(self.name()).open(open).show(ctx, |ui| { - use super::View as _; - self.ui(ui); - }); + let clone = self.clone(); + egui::Window::new(self.name()) + .open(open) + .show(ctx, move |ui| { + use super::View as _; + clone.clone().ui(ui); + }); } } impl super::View for TableTest { fn ui(&mut self, ui: &mut egui::Ui) { - ui.add( - egui::Slider::new(&mut self.min_col_width, 0.0..=400.0).text("Minimum column width"), - ); - ui.add( - egui::Slider::new(&mut self.max_col_width, 0.0..=400.0).text("Maximum column width"), - ); - ui.add(egui::Slider::new(&mut self.num_cols, 0..=5).text("Columns")); - ui.add(egui::Slider::new(&mut self.num_rows, 0..=20).text("Rows")); + { + let mut data = self.data.write().unwrap(); + ui.add( + egui::Slider::new(&mut data.min_col_width, 0.0..=400.0) + .text("Minimum column width"), + ); + ui.add( + egui::Slider::new(&mut data.max_col_width, 0.0..=400.0) + .text("Maximum column width"), + ); + ui.add(egui::Slider::new(&mut data.num_cols, 0..=5).text("Columns")); + ui.add(egui::Slider::new(&mut data.num_rows, 0..=20).text("Rows")); - ui.separator(); + ui.separator(); - let words = [ - "random", "words", "in", "a", "random", "order", "that", "just", "keeps", "going", - "with", "some", "more", - ]; + let words = [ + "random", "words", "in", "a", "random", "order", "that", "just", "keeps", "going", + "with", "some", "more", + ]; - egui::Grid::new("my_grid") - .striped(true) - .min_col_width(self.min_col_width) - .max_col_width(self.max_col_width) - .show(ui, |ui| { - for row in 0..self.num_rows { - for col in 0..self.num_cols { - if col == 0 { - ui.label(format!("row {}", row)); - } else { - let word_idx = row * 3 + col * 5; - let word_count = (row * 5 + col * 75) % 13; - let mut string = String::new(); - for word in words.iter().cycle().skip(word_idx).take(word_count) { - string += word; - string += " "; + egui::Grid::new("my_grid") + .striped(true) + .min_col_width(data.min_col_width) + .max_col_width(data.max_col_width) + .show(ui, |ui| { + for row in 0..data.num_rows { + for col in 0..data.num_cols { + if col == 0 { + ui.label(format!("row {}", row)); + } else { + let word_idx = row * 3 + col * 5; + let word_count = (row * 5 + col * 75) % 13; + let mut string = String::new(); + for word in words.iter().cycle().skip(word_idx).take(word_count) { + string += word; + string += " "; + } + ui.label(string); } - ui.label(string); } + ui.end_row(); } + }); + + ui.separator(); + ui.add(egui::Slider::new(&mut data.text_length, 1..=40).text("Text length")); + egui::Grid::new("parent grid").striped(true).show(ui, |ui| { + ui.vertical(|ui| { + ui.label("Vertical nest1"); + ui.label("Vertical nest2"); + }); + ui.label("First row, second column"); + ui.end_row(); + + ui.horizontal(|ui| { + ui.label("Horizontal nest1"); + ui.label("Horizontal nest2"); + }); + ui.label("Second row, second column"); + ui.end_row(); + + ui.scope(|ui| { + ui.label("Scope nest 1"); + ui.label("Scope nest 2"); + }); + ui.label("Third row, second column"); + ui.end_row(); + + egui::Grid::new("nested grid").show(ui, |ui| { + ui.label("Grid nest11"); + ui.label("Grid nest12"); ui.end_row(); - } - }); - - ui.separator(); - ui.add(egui::Slider::new(&mut self.text_length, 1..=40).text("Text length")); - egui::Grid::new("parent grid").striped(true).show(ui, |ui| { - ui.vertical(|ui| { - ui.label("Vertical nest1"); - ui.label("Vertical nest2"); - }); - ui.label("First row, second column"); - ui.end_row(); - - ui.horizontal(|ui| { - ui.label("Horizontal nest1"); - ui.label("Horizontal nest2"); - }); - ui.label("Second row, second column"); - ui.end_row(); - - ui.scope(|ui| { - ui.label("Scope nest 1"); - ui.label("Scope nest 2"); - }); - ui.label("Third row, second column"); - ui.end_row(); - - egui::Grid::new("nested grid").show(ui, |ui| { - ui.label("Grid nest11"); - ui.label("Grid nest12"); + ui.label("Grid nest21"); + ui.label("Grid nest22"); + ui.end_row(); + }); + ui.label("Fourth row, second column"); ui.end_row(); - ui.label("Grid nest21"); - ui.label("Grid nest22"); + + let mut dyn_text = String::from("O"); + dyn_text.extend(std::iter::repeat('h').take(data.text_length)); + ui.label(dyn_text); + ui.label("Fifth row, second column"); ui.end_row(); }); - ui.label("Fourth row, second column"); - ui.end_row(); - - let mut dyn_text = String::from("O"); - dyn_text.extend(std::iter::repeat('h').take(self.text_length)); - ui.label(dyn_text); - ui.label("Fifth row, second column"); - ui.end_row(); - }); + } ui.vertical_centered(|ui| { egui::reset_button(ui, self); @@ -307,9 +338,9 @@ impl super::View for TableTest { // ---------------------------------------------------------------------------- #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[derive(Default)] +#[derive(Default, Clone)] pub struct InputTest { - info: String, + info: Arc>, } impl super::Demo for InputTest { @@ -318,12 +349,13 @@ impl super::Demo for InputTest { } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + let clone = self.clone(); egui::Window::new(self.name()) .open(open) .resizable(false) - .show(ctx, |ui| { + .show(ctx, move |ui| { use super::View as _; - self.ui(ui); + clone.clone().ui(ui); }); } } @@ -369,23 +401,24 @@ impl super::View for InputTest { } } if !new_info.is_empty() { - self.info = new_info; + *self.info.write().unwrap() = new_info; } - ui.label(&self.info); + ui.label(&*self.info.write().unwrap()); } } // ---------------------------------------------------------------------------- +#[derive(Clone)] pub struct WindowResizeTest { - text: String, + text: Arc>, } impl Default for WindowResizeTest { fn default() -> Self { Self { - text: crate::LOREM_IPSUM_LONG.to_owned(), + text: Arc::new(RwLock::new(crate::LOREM_IPSUM_LONG.to_owned())), } } } @@ -451,14 +484,17 @@ impl super::Demo for WindowResizeTest { lorem_ipsum(ui, crate::LOREM_IPSUM); }); + let clone = self.clone(); + Window::new("↔ resizable with TextEdit") .open(open) .vscroll(false) .resizable(true) .default_height(300.0) - .show(ctx, |ui| { + .show(ctx, move |ui| { ui.label("Shows how you can fill an area with a widget."); - ui.add_sized(ui.available_size(), TextEdit::multiline(&mut self.text)); + let mut text = clone.text.write().unwrap(); + ui.add_sized(ui.available_size(), TextEdit::multiline(&mut *text)); }); Window::new("↔ freely resized") diff --git a/crates/egui_demo_lib/src/demo/text_edit.rs b/crates/egui_demo_lib/src/demo/text_edit.rs index 6ad3f2420..aeb2c35a6 100644 --- a/crates/egui_demo_lib/src/demo/text_edit.rs +++ b/crates/egui_demo_lib/src/demo/text_edit.rs @@ -1,15 +1,23 @@ +use std::sync::{Arc, RwLock}; + /// Showcase [`TextEdit`]. -#[derive(PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] +#[derive(Clone)] pub struct TextEdit { - pub text: String, + pub text: Arc>, +} + +impl PartialEq for TextEdit { + fn eq(&self, other: &Self) -> bool { + *self.text.read().unwrap() == *other.text.read().unwrap() + } } impl Default for TextEdit { fn default() -> Self { Self { - text: "Edit this text".to_owned(), + text: Arc::new(RwLock::new("Edit this text".to_owned())), } } } @@ -20,12 +28,13 @@ impl super::Demo for TextEdit { } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + let clone = self.clone(); egui::Window::new(self.name()) .open(open) .resizable(false) - .show(ctx, |ui| { + .show(ctx, move |ui| { use super::View as _; - self.ui(ui); + clone.clone().ui(ui); }); } } @@ -33,6 +42,7 @@ impl super::Demo for TextEdit { impl super::View for TextEdit { fn ui(&mut self, ui: &mut egui::Ui) { let Self { text } = self; + let text = &mut *text.write().unwrap(); ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 0.0; diff --git a/crates/egui_demo_lib/src/demo/widget_gallery.rs b/crates/egui_demo_lib/src/demo/widget_gallery.rs index d91ae395e..4545cc074 100644 --- a/crates/egui_demo_lib/src/demo/widget_gallery.rs +++ b/crates/egui_demo_lib/src/demo/widget_gallery.rs @@ -1,3 +1,5 @@ +use std::sync::{Arc, RwLock}; + #[derive(Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] enum Enum { @@ -6,9 +8,8 @@ enum Enum { Third, } -/// Shows off one example of each major type of widget. #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct WidgetGallery { +pub struct WidgetGalleryData { enabled: bool, visible: bool, boolean: bool, @@ -26,7 +27,14 @@ pub struct WidgetGallery { texture: Option, } -impl Default for WidgetGallery { +/// Shows off one example of each major type of widget. +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[derive(Clone, Default)] +pub struct WidgetGallery { + data: Arc>, +} + +impl Default for WidgetGalleryData { fn default() -> Self { Self { enabled: true, @@ -50,21 +58,24 @@ impl super::Demo for WidgetGallery { } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + let clone = self.clone(); egui::Window::new(self.name()) .open(open) .resizable(true) .default_width(280.0) - .show(ctx, |ui| { + .show(ctx, move |ui| { use super::View as _; - self.ui(ui); + clone.clone().ui(ui); }); } } impl super::View for WidgetGallery { fn ui(&mut self, ui: &mut egui::Ui) { - ui.add_enabled_ui(self.enabled, |ui| { - ui.set_visible(self.visible); + let enabled = self.data.read().unwrap().enabled; + let visible = self.data.read().unwrap().visible; + ui.add_enabled_ui(enabled, |ui| { + ui.set_visible(visible); egui::Grid::new("my_grid") .num_columns(2) @@ -74,14 +85,14 @@ impl super::View for WidgetGallery { self.gallery_grid_contents(ui); }); }); - + let data = &mut *self.data.write().unwrap(); ui.separator(); ui.horizontal(|ui| { - ui.checkbox(&mut self.visible, "Visible") + ui.checkbox(&mut data.visible, "Visible") .on_hover_text("Uncheck to hide all the widgets."); - if self.visible { - ui.checkbox(&mut self.enabled, "Interactive") + if data.visible { + ui.checkbox(&mut data.enabled, "Interactive") .on_hover_text("Uncheck to inspect how the widgets look when disabled."); } }); @@ -100,7 +111,7 @@ impl super::View for WidgetGallery { impl WidgetGallery { fn gallery_grid_contents(&mut self, ui: &mut egui::Ui) { - let Self { + let WidgetGalleryData { enabled: _, visible: _, boolean, @@ -112,7 +123,7 @@ impl WidgetGallery { #[cfg(feature = "chrono")] date, texture, - } = self; + } = &mut *self.data.write().unwrap(); let texture: &egui::TextureHandle = texture.get_or_insert_with(|| { ui.ctx() diff --git a/crates/egui_demo_lib/src/demo/window_options.rs b/crates/egui_demo_lib/src/demo/window_options.rs index ed142d089..d258fbb43 100644 --- a/crates/egui_demo_lib/src/demo/window_options.rs +++ b/crates/egui_demo_lib/src/demo/window_options.rs @@ -1,6 +1,8 @@ +use std::sync::{Arc, RwLock}; + #[derive(Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct WindowOptions { +pub struct WindowOptionsData { title: String, title_bar: bool, closable: bool, @@ -13,8 +15,19 @@ pub struct WindowOptions { anchor: egui::Align2, anchor_offset: egui::Vec2, } +#[derive(Clone, Default)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct WindowOptions { + data: Arc>, +} -impl Default for WindowOptions { +impl PartialEq for WindowOptions { + fn eq(&self, other: &Self) -> bool { + *self.data.read().unwrap() == *other.data.read().unwrap() + } +} + +impl Default for WindowOptionsData { fn default() -> Self { Self { title: "🗖 Window Options".to_owned(), @@ -37,7 +50,7 @@ impl super::Demo for WindowOptions { } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { - let Self { + let WindowOptionsData { title, title_bar, closable, @@ -48,7 +61,7 @@ impl super::Demo for WindowOptions { anchored, anchor, anchor_offset, - } = self.clone(); + } = self.data.read().unwrap().clone(); let enabled = ctx.input(|i| i.time) - disabled_time > 2.0; if !enabled { @@ -69,70 +82,76 @@ impl super::Demo for WindowOptions { if anchored { window = window.anchor(anchor, anchor_offset); } - window.show(ctx, |ui| self.ui(ui)); + let clone = self.clone(); + window.show(ctx, move |ui| { + let mut clone = clone.clone(); + clone.ui(ui) + }); } } impl super::View for WindowOptions { fn ui(&mut self, ui: &mut egui::Ui) { - let Self { - title, - title_bar, - closable, - collapsible, - resizable, - scroll2, - disabled_time: _, - anchored, - anchor, - anchor_offset, - } = self; - ui.horizontal(|ui| { - ui.label("title:"); - ui.text_edit_singleline(title); - }); + { + let WindowOptionsData { + title, + title_bar, + closable, + collapsible, + resizable, + scroll2, + disabled_time: _, + anchored, + anchor, + anchor_offset, + } = &mut *self.data.write().unwrap(); + ui.horizontal(|ui| { + ui.label("title:"); + ui.text_edit_singleline(title); + }); - ui.horizontal(|ui| { - ui.group(|ui| { - ui.vertical(|ui| { - ui.checkbox(title_bar, "title_bar"); - ui.checkbox(closable, "closable"); - ui.checkbox(collapsible, "collapsible"); - ui.checkbox(resizable, "resizable"); - ui.checkbox(&mut scroll2[0], "hscroll"); - ui.checkbox(&mut scroll2[1], "vscroll"); + ui.horizontal(|ui| { + ui.group(|ui| { + ui.vertical(|ui| { + ui.checkbox(title_bar, "title_bar"); + ui.checkbox(closable, "closable"); + ui.checkbox(collapsible, "collapsible"); + ui.checkbox(resizable, "resizable"); + ui.checkbox(&mut scroll2[0], "hscroll"); + ui.checkbox(&mut scroll2[1], "vscroll"); + }); }); - }); - ui.group(|ui| { - ui.vertical(|ui| { - ui.checkbox(anchored, "anchored"); - ui.set_enabled(*anchored); - ui.horizontal(|ui| { - ui.label("x:"); - ui.selectable_value(&mut anchor[0], egui::Align::LEFT, "Left"); - ui.selectable_value(&mut anchor[0], egui::Align::Center, "Center"); - ui.selectable_value(&mut anchor[0], egui::Align::RIGHT, "Right"); - }); - ui.horizontal(|ui| { - ui.label("y:"); - ui.selectable_value(&mut anchor[1], egui::Align::TOP, "Top"); - ui.selectable_value(&mut anchor[1], egui::Align::Center, "Center"); - ui.selectable_value(&mut anchor[1], egui::Align::BOTTOM, "Bottom"); - }); - ui.horizontal(|ui| { - ui.label("Offset:"); - ui.add(egui::DragValue::new(&mut anchor_offset.x)); - ui.add(egui::DragValue::new(&mut anchor_offset.y)); + ui.group(|ui| { + ui.vertical(|ui| { + ui.checkbox(anchored, "anchored"); + ui.set_enabled(*anchored); + ui.horizontal(|ui| { + ui.label("x:"); + ui.selectable_value(&mut anchor[0], egui::Align::LEFT, "Left"); + ui.selectable_value(&mut anchor[0], egui::Align::Center, "Center"); + ui.selectable_value(&mut anchor[0], egui::Align::RIGHT, "Right"); + }); + ui.horizontal(|ui| { + ui.label("y:"); + ui.selectable_value(&mut anchor[1], egui::Align::TOP, "Top"); + ui.selectable_value(&mut anchor[1], egui::Align::Center, "Center"); + ui.selectable_value(&mut anchor[1], egui::Align::BOTTOM, "Bottom"); + }); + ui.horizontal(|ui| { + ui.label("Offset:"); + ui.add(egui::DragValue::new(&mut anchor_offset.x)); + ui.add(egui::DragValue::new(&mut anchor_offset.y)); + }); }); }); }); - }); + } ui.separator(); ui.horizontal(|ui| { if ui.button("Disable for 2 seconds").clicked() { - self.disabled_time = ui.input(|i| i.time); + self.data.write().unwrap().disabled_time = ui.input(|i| i.time); } egui::reset_button(ui, self); ui.add(crate::egui_github_link_file!()); diff --git a/crates/egui_demo_lib/src/demo/window_with_panels.rs b/crates/egui_demo_lib/src/demo/window_with_panels.rs index 5518f218a..2e3c8aaf2 100644 --- a/crates/egui_demo_lib/src/demo/window_with_panels.rs +++ b/crates/egui_demo_lib/src/demo/window_with_panels.rs @@ -14,7 +14,7 @@ impl super::Demo for WindowWithPanels { .default_height(400.0) .vscroll(false) .open(open); - window.show(ctx, |ui| self.ui(ui)); + window.show(ctx, |ui| Self {}.ui(ui)); } } diff --git a/examples/hello_world_simple/src/main.rs b/examples/hello_world_simple/src/main.rs index 03f0e0382..39a48dc7e 100644 --- a/examples/hello_world_simple/src/main.rs +++ b/examples/hello_world_simple/src/main.rs @@ -1,5 +1,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +use std::sync::{Arc, RwLock}; + use eframe::egui; fn main() -> Result<(), eframe::Error> { @@ -14,8 +16,8 @@ fn main() -> Result<(), eframe::Error> { let mut name = "Arthur".to_owned(); let mut age = 42; - let mut window1_embedded = true; - let mut window2_embedded = true; + let mut window1_embedded = Arc::new(RwLock::new(true)); + let mut window2_embedded = Arc::new(RwLock::new(true)); eframe::run_simple_native("My egui App", options, move |ctx, _frame| { egui::CentralPanel::default().show(ctx, |ui| { @@ -35,11 +37,14 @@ fn main() -> Result<(), eframe::Error> { age += 1; } ui.label(format!("Hello '{name}', age {age}")); + let clone = window1_embedded.clone(); + let embedded = *window1_embedded.read().unwrap(); egui::CollapsingHeader::new("Show Test1").show(ui, |ui| { egui::Window::new("Test1") - .embedded(window1_embedded) - .show(ctx, |ui| { - ui.checkbox(&mut window1_embedded, "Should embedd?"); + .embedded(embedded) + .show(ctx, move |ui| { + ui.checkbox(&mut *clone.write().unwrap(), "Should embedd?"); + let ctx = ui.ctx().clone(); ui.label(format!( "Current window: {}, Current rendering window: {}", ctx.current_viewport(), @@ -47,11 +52,14 @@ fn main() -> Result<(), eframe::Error> { )); }); }); + let clone = window2_embedded.clone(); + let embedded = *window2_embedded.read().unwrap(); egui::CollapsingHeader::new("Shout Test2").show(ui, |ui| { egui::Window::new("Test2") - .embedded(window2_embedded) - .show(ctx, |ui| { - ui.checkbox(&mut window2_embedded, "Should embedd?"); + .embedded(embedded) + .show(ctx, move |ui| { + ui.checkbox(&mut *clone.write().unwrap(), "Should embedd?"); + let ctx = ui.ctx().clone(); ui.label(format!( "Current window: {}, Current rendering window: {}", ctx.current_viewport(),