diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Cargo.lock b/Cargo.lock index c781b851b5a..887aec26ef7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -956,14 +956,38 @@ dependencies = [ "winapi", ] +[[package]] +name = "darling" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +dependencies = [ + "darling_core 0.10.2", + "darling_macro 0.10.2", +] + [[package]] name = "darling" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.13.4", + "darling_macro 0.13.4", +] + +[[package]] +name = "darling_core" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.9.3", + "syn 1.0.109", ] [[package]] @@ -976,7 +1000,18 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.10.0", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +dependencies = [ + "darling_core 0.10.2", + "quote", "syn 1.0.109", ] @@ -986,7 +1021,7 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ - "darling_core", + "darling_core 0.13.4", "quote", "syn 1.0.109", ] @@ -1137,6 +1172,7 @@ dependencies = [ "color-hex", "document-features", "serde", + "serde-diff", ] [[package]] @@ -1187,6 +1223,7 @@ dependencies = [ "nohash-hasher", "ron", "serde", + "serde-diff", ] [[package]] @@ -1320,6 +1357,7 @@ dependencies = [ "document-features", "mint", "serde", + "serde-diff", ] [[package]] @@ -1405,6 +1443,7 @@ dependencies = [ "nohash-hasher", "parking_lot", "serde", + "serde-diff", ] [[package]] @@ -1933,6 +1972,23 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + [[package]] name = "humantime" version = "2.1.0" @@ -2371,7 +2427,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" dependencies = [ - "darling", + "darling 0.13.4", "proc-macro-crate", "proc-macro2", "quote", @@ -2944,6 +3000,20 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +[[package]] +name = "remote_rendering" +version = "0.1.0" +dependencies = [ + "eframe", + "egui", + "egui_demo_lib", + "serde", + "serde-diff", + "serde_derive", + "serde_json", + "tungstenite", +] + [[package]] name = "renderdoc-sys" version = "1.0.0" @@ -3156,6 +3226,27 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-diff" +version = "0.4.1" +source = "git+https://github.com/linguini11/serde-diff.git#e461da41c9339af3ecadf9f4aa61ae91c1f79e97" +dependencies = [ + "serde", + "serde-diff-derive", + "serde_derive", +] + +[[package]] +name = "serde-diff-derive" +version = "0.4.0" +source = "git+https://github.com/linguini11/serde-diff.git#e461da41c9339af3ecadf9f4aa61ae91c1f79e97" +dependencies = [ + "darling 0.10.2", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "serde_derive" version = "1.0.163" @@ -3358,6 +3449,12 @@ dependencies = [ "float-cmp", ] +[[package]] +name = "strsim" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" + [[package]] name = "strsim" version = "0.10.0" @@ -3678,6 +3775,25 @@ dependencies = [ "windows 0.48.0", ] +[[package]] +name = "tungstenite" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" +dependencies = [ + "base64", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "url", + "utf-8", +] + [[package]] name = "type-map" version = "0.5.0" @@ -3803,6 +3919,12 @@ dependencies = [ "svgtypes", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "vec_map" version = "0.8.2" diff --git a/crates/ecolor/Cargo.toml b/crates/ecolor/Cargo.toml index 6a2feb2ee76..2f6a3715b7b 100644 --- a/crates/ecolor/Cargo.toml +++ b/crates/ecolor/Cargo.toml @@ -48,3 +48,7 @@ document-features = { version = "0.2", optional = true } ## Allow serialization using [`serde`](https://docs.rs/serde). serde = { version = "1", optional = true, features = ["derive"] } + +## Allow diffing with serialization using [`serde-diff`](https://docs.rs/serde-diff/latest/serde_diff/) . +#serde-diff = { version = "0.4.1", optional = true } +serde-diff = { git = "https://github.com/linguini11/serde-diff.git", optional = true } diff --git a/crates/ecolor/src/color32.rs b/crates/ecolor/src/color32.rs index 09e68116f09..d8359bbaaf7 100644 --- a/crates/ecolor/src/color32.rs +++ b/crates/ecolor/src/color32.rs @@ -10,6 +10,10 @@ use crate::{gamma_u8_from_linear_f32, linear_f32_from_gamma_u8, linear_f32_from_ #[repr(C)] #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] #[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))] pub struct Color32(pub(crate) [u8; 4]); diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index 87ad0a2d760..b1a9c9f91fc 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -27,7 +27,7 @@ targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"] [features] -default = ["accesskit", "default_fonts", "glow"] +default = ["accesskit", "default_fonts", "glow", "serde"] ## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/). accesskit = ["egui/accesskit", "egui-winit/accesskit"] diff --git a/crates/eframe/src/epi/mod.rs b/crates/eframe/src/epi/mod.rs index 0f6f42290e2..6cbf366fbd7 100644 --- a/crates/eframe/src/epi/mod.rs +++ b/crates/eframe/src/epi/mod.rs @@ -101,6 +101,18 @@ unsafe impl HasRawDisplayHandle for CreationContext<'_> { } } +/// Message types for client to server communication for remote rendering +#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub enum RemoteRenderingMessageType { + /// Initial connection, specifying pixels_per_point as the content + Connect(f32), + /// Request to update pixels_per_point + PixelsPerPoint(f32), + /// Send input events to server + RawInput(egui::RawInput), +} + // ---------------------------------------------------------------------------- /// Implement this trait to write apps that can be compiled for both web/wasm and desktop/native using [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe). @@ -114,6 +126,17 @@ pub trait App { /// To force a repaint, call [`egui::Context::request_repaint`] at any time (e.g. from another thread). fn update(&mut self, ctx: &egui::Context, frame: &mut Frame); + /// Called each time the UI needs repainting when rendering on a remote client, which may be many times per second. + /// + /// Retrieves the `FullOutput` from the client rather than constructing it internally + fn update_remote( + &mut self, + _raw_input: egui::RawInput, + _frame: &Frame, + ) -> (egui::output::FullOutput, f32) { + (egui::output::FullOutput::default(), 2.0) + } + /// Get a handle to the app. /// /// Can be used from web to interact or other external context. diff --git a/crates/eframe/src/lib.rs b/crates/eframe/src/lib.rs index afa602cc94d..86bb3bb66f2 100644 --- a/crates/eframe/src/lib.rs +++ b/crates/eframe/src/lib.rs @@ -15,7 +15,7 @@ //! //! fn main() { //! let native_options = eframe::NativeOptions::default(); -//! eframe::run_native("My egui App", native_options, Box::new(|cc| Box::new(MyEguiApp::new(cc)))); +//! eframe::run_native("My egui App", native_options, Box::new(|cc| Box::new(MyEguiApp::new(cc))), false); //! } //! //! #[derive(Default)] @@ -170,7 +170,7 @@ mod native; /// /// fn main() -> eframe::Result<()> { /// let native_options = eframe::NativeOptions::default(); -/// eframe::run_native("MyApp", native_options, Box::new(|cc| Box::new(MyEguiApp::new(cc)))) +/// eframe::run_native("MyApp", native_options, Box::new(|cc| Box::new(MyEguiApp::new(cc))), false) /// } /// /// #[derive(Default)] @@ -204,6 +204,7 @@ pub fn run_native( app_name: &str, native_options: NativeOptions, app_creator: AppCreator, + remote_rendering: bool, ) -> Result<()> { let renderer = native_options.renderer; @@ -217,13 +218,13 @@ pub fn run_native( #[cfg(feature = "glow")] Renderer::Glow => { log::debug!("Using the glow renderer"); - native::run::run_glow(app_name, native_options, app_creator) + native::run::run_glow(app_name, native_options, app_creator, remote_rendering) } #[cfg(feature = "wgpu")] Renderer::Wgpu => { log::debug!("Using the wgpu renderer"); - native::run::run_wgpu(app_name, native_options, app_creator) + native::run::run_wgpu(app_name, native_options, app_creator, remote_rendering) } } } @@ -282,6 +283,7 @@ pub fn run_simple_native( app_name, native_options, Box::new(|_cc| Box::new(SimpleApp { update_fun })), + false, ) } diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index 4409c1298bd..0024a89021c 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -433,7 +433,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); + let full_output = self.update(app, window, false); 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(); @@ -489,6 +489,7 @@ impl EpiIntegration { &mut self, app: &mut dyn epi::App, window: &winit::window::Window, + remote_rendering: bool, ) -> egui::FullOutput { let frame_start = std::time::Instant::now(); @@ -499,12 +500,23 @@ impl EpiIntegration { let raw_input = self.egui_winit.take_egui_input(window); // 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 remote_rendering { + let (full_output, pixels_per_point) = app.update_remote(raw_input, &self.frame); + // Tessellate throws error if 'run' hasn't been called, so run and ignore result + let raw_input = egui::RawInput { + pixels_per_point: Some(pixels_per_point), + ..Default::default() + }; + let _ = self.egui_ctx.run(raw_input, |_egui_ctx| {}); + self.pending_full_output.append(full_output); + } else { + let full_output = self.egui_ctx.run(raw_input, |egui_ctx| { + crate::profile_scope!("App::update"); + app.update(egui_ctx, &mut self.frame); + }); + self.pending_full_output.append(full_output); + } - self.pending_full_output.append(full_output); let full_output = std::mem::take(&mut self.pending_full_output); { diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 367daea7405..e52f09be5b7 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -75,7 +75,7 @@ trait WinitApp { fn save_and_destroy(&mut self); - fn run_ui_and_paint(&mut self) -> EventResult; + fn run_ui_and_paint(&mut self, remote_rendering: bool) -> EventResult; fn on_event( &mut self, @@ -121,6 +121,7 @@ fn with_event_loop( fn run_and_return( event_loop: &mut EventLoop, mut winit_app: impl WinitApp, + remote_rendering: bool, ) -> Result<()> { use winit::platform::run_return::EventLoopExtRunReturn as _; @@ -146,11 +147,11 @@ fn run_and_return( // See: https://github.com/rust-windowing/winit/issues/1619 winit::event::Event::RedrawEventsCleared if cfg!(windows) => { next_repaint_time = extremely_far_future(); - winit_app.run_ui_and_paint() + winit_app.run_ui_and_paint(remote_rendering) } winit::event::Event::RedrawRequested(_) if !cfg!(windows) => { next_repaint_time = extremely_far_future(); - winit_app.run_ui_and_paint() + winit_app.run_ui_and_paint(remote_rendering) } winit::event::Event::UserEvent(UserEvent::RequestRepaint { when, frame_nr }) => { @@ -196,7 +197,7 @@ fn run_and_return( if cfg!(windows) { // Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280 next_repaint_time = extremely_far_future(); - winit_app.run_ui_and_paint(); + winit_app.run_ui_and_paint(remote_rendering); } else { // Fix for https://github.com/emilk/egui/issues/2425 next_repaint_time = Instant::now(); @@ -269,11 +270,11 @@ fn run_and_exit(event_loop: EventLoop, mut winit_app: impl WinitApp + // See: https://github.com/rust-windowing/winit/issues/1619 winit::event::Event::RedrawEventsCleared if cfg!(windows) => { next_repaint_time = extremely_far_future(); - winit_app.run_ui_and_paint() + winit_app.run_ui_and_paint(false) } winit::event::Event::RedrawRequested(_) if !cfg!(windows) => { next_repaint_time = extremely_far_future(); - winit_app.run_ui_and_paint() + winit_app.run_ui_and_paint(false) } winit::event::Event::UserEvent(UserEvent::RequestRepaint { when, frame_nr }) => { @@ -302,7 +303,7 @@ fn run_and_exit(event_loop: EventLoop, mut winit_app: impl WinitApp + if cfg!(windows) { // Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280 next_repaint_time = extremely_far_future(); - winit_app.run_ui_and_paint(); + winit_app.run_ui_and_paint(false); } else { // Fix for https://github.com/emilk/egui/issues/2425 next_repaint_time = Instant::now(); @@ -805,7 +806,7 @@ mod glow_integration { } } - fn run_ui_and_paint(&mut self) -> EventResult { + fn run_ui_and_paint(&mut self, remote_rendering: bool) -> EventResult { if let Some(running) = &mut self.running { if running.gl_window.window.is_none() { return EventResult::Wait; @@ -838,7 +839,7 @@ mod glow_integration { repaint_after, textures_delta, shapes, - } = integration.update(app.as_mut(), window); + } = integration.update(app.as_mut(), window, remote_rendering); integration.handle_platform_output(window, platform_output); @@ -1044,12 +1045,13 @@ mod glow_integration { app_name: &str, mut native_options: epi::NativeOptions, app_creator: epi::AppCreator, + remote_rendering: bool, ) -> Result<()> { if native_options.run_and_return { with_event_loop(native_options, |event_loop, native_options| { let glow_eframe = GlowWinitApp::new(event_loop, app_name, native_options, app_creator); - run_and_return(event_loop, glow_eframe) + run_and_return(event_loop, glow_eframe, remote_rendering) }) } else { let event_loop = create_event_loop_builder(&mut native_options).build(); @@ -1272,7 +1274,7 @@ mod wgpu_integration { } } - fn run_ui_and_paint(&mut self) -> EventResult { + fn run_ui_and_paint(&mut self, remote_rendering: bool) -> EventResult { if let (Some(running), Some(window)) = (&mut self.running, &self.window) { #[cfg(feature = "puffin")] puffin::GlobalProfiler::lock().new_frame(); @@ -1289,7 +1291,7 @@ mod wgpu_integration { repaint_after, textures_delta, shapes, - } = integration.update(app.as_mut(), window); + } = integration.update(app.as_mut(), window, remote_rendering); integration.handle_platform_output(window, platform_output); @@ -1478,12 +1480,13 @@ mod wgpu_integration { app_name: &str, mut native_options: epi::NativeOptions, app_creator: epi::AppCreator, + remote_rendering: bool, ) -> Result<()> { if native_options.run_and_return { with_event_loop(native_options, |event_loop, native_options| { let wgpu_eframe = WgpuWinitApp::new(event_loop, app_name, native_options, app_creator); - run_and_return(event_loop, wgpu_eframe) + run_and_return(event_loop, wgpu_eframe, remote_rendering) }) } else { let event_loop = create_event_loop_builder(&mut native_options).build(); diff --git a/crates/egui/Cargo.toml b/crates/egui/Cargo.toml index 913a17cd1a8..d4c752aaed6 100644 --- a/crates/egui/Cargo.toml +++ b/crates/egui/Cargo.toml @@ -20,7 +20,11 @@ all-features = true [features] -default = ["default_fonts"] +default = [ + "default_fonts", + "serde", + "serde-diff", +] #remove from default? how to run with certain features? ## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast [`epaint::Vertex`], [`emath::Vec2`] etc to `&[u8]`. bytemuck = ["epaint/bytemuck"] @@ -57,6 +61,8 @@ persistence = ["serde", "epaint/serde", "ron"] ## Allow serialization using [`serde`](https://docs.rs/serde). serde = ["dep:serde", "epaint/serde", "accesskit?/serde"] +serde-diff = ["dep:serde-diff", "epaint/serde-diff"] + ## Change Vertex layout to be compatible with unity unity = ["epaint/unity"] @@ -81,3 +87,6 @@ document-features = { version = "0.2", optional = true } log = { version = "0.4", optional = true, features = ["std"] } ron = { version = "0.8", optional = true } serde = { version = "1", optional = true, features = ["derive", "rc"] } +## Allow diffing with serialization using [`serde-diff`](https://docs.rs/serde-diff/latest/serde_diff/) . +#serde-diff = { version = "0.4.1", optional = true } +serde-diff = { git = "https://github.com/linguini11/serde-diff.git", optional = true } diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index cea031ed0af..354f5ae9aaf 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -6,6 +6,11 @@ use crate::WidgetType; /// /// The backend should use this. #[derive(Clone, Default, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] pub struct FullOutput { /// Non-rendering related output. pub platform_output: PlatformOutput, @@ -56,6 +61,10 @@ impl FullOutput { /// The backend should use this. #[derive(Default, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] pub struct PlatformOutput { /// Set the cursor to this icon. pub cursor_icon: CursorIcon, @@ -87,6 +96,7 @@ pub struct PlatformOutput { pub text_cursor_pos: Option, #[cfg(feature = "accesskit")] + #[cfg_attr(feature = "serde", serde_diff(opaque))] pub accesskit_update: Option, } @@ -158,6 +168,10 @@ impl PlatformOutput { /// What URL to open, and how. #[derive(Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] pub struct OpenUrl { pub url: String, @@ -209,6 +223,10 @@ pub enum UserAttentionType { /// Loosely based on . #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] pub enum CursorIcon { /// Normal cursor icon, whatever that is. Default, @@ -381,6 +399,10 @@ impl Default for CursorIcon { /// In particular, these events may be useful for accessibility, i.e. for screen readers. #[derive(Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] pub enum OutputEvent { /// A widget was clicked. Clicked(WidgetInfo), @@ -430,6 +452,10 @@ impl std::fmt::Debug for OutputEvent { /// Describes a widget such as a [`crate::Button`] or a [`crate::TextEdit`]. #[derive(Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] pub struct WidgetInfo { /// The type of widget this is. pub typ: WidgetType, @@ -453,6 +479,7 @@ pub struct WidgetInfo { pub value: Option, /// Selected range of characters in [`Self::current_text_value`]. + #[cfg_attr(feature = "serde", serde_diff(opaque))] pub text_selection: Option>, } diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index f7eef71a943..63f41213b85 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -511,7 +511,14 @@ pub mod special_emojis { /// The different types of built-in widgets in egui #[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize) +)] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] pub enum WidgetType { Label, // TODO(emilk): emit Label events diff --git a/crates/egui_demo_app/src/main.rs b/crates/egui_demo_app/src/main.rs index 177e24a754a..6bfac1f78b3 100644 --- a/crates/egui_demo_app/src/main.rs +++ b/crates/egui_demo_app/src/main.rs @@ -31,5 +31,6 @@ fn main() -> Result<(), eframe::Error> { "egui demo app", options, Box::new(|cc| Box::new(egui_demo_app::WrapApp::new(cc))), + false, ) } diff --git a/crates/emath/Cargo.toml b/crates/emath/Cargo.toml index d4eec7c7e25..ff6cfe4e461 100644 --- a/crates/emath/Cargo.toml +++ b/crates/emath/Cargo.toml @@ -27,7 +27,6 @@ extra_debug_asserts = [] ## Always enable additional checks. extra_asserts = [] - [dependencies] #! ### Optional dependencies @@ -42,3 +41,7 @@ mint = { version = "0.5.6", optional = true } ## Allow serialization using [`serde`](https://docs.rs/serde). serde = { version = "1", optional = true, features = ["derive"] } + +## Allow diffing with serialization using [`serde-diff`](https://docs.rs/serde-diff/latest/serde_diff/) . +#serde-diff = { version = "0.4.1", optional = true } +serde-diff = { git = "https://github.com/linguini11/serde-diff.git", optional = true } diff --git a/crates/emath/src/pos2.rs b/crates/emath/src/pos2.rs index 16bba20ecb4..4fd075d183b 100644 --- a/crates/emath/src/pos2.rs +++ b/crates/emath/src/pos2.rs @@ -11,16 +11,37 @@ use crate::*; #[repr(C)] #[derive(Clone, Copy, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] #[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))] pub struct Pos2 { /// How far to the right. + #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_f32_custom"))] pub x: f32, /// How far down. + #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_f32_custom"))] pub y: f32, // implicit w = 1 } +#[cfg(feature = "serde")] +fn serialize_f32_custom(x: &f32, s: S) -> Result +where + S: serde::Serializer, +{ + // Workaround for the fact that INFINITY and NEG_INFINITY serialize as null and then fail to deserialize + if *x == f32::INFINITY { + s.serialize_f32(f32::MAX) + } else if *x == f32::NEG_INFINITY { + s.serialize_f32(f32::MIN) + } else { + s.serialize_f32(*x) + } +} + /// `pos2(x, y) == Pos2::new(x, y)` #[inline(always)] pub const fn pos2(x: f32, y: f32) -> Pos2 { diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index b8c6085a224..1eeebb7b57c 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -15,8 +15,12 @@ use crate::*; /// /// Normally the unit is points (logical pixels) in screen space coordinates. #[repr(C)] -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Eq, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] #[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))] pub struct Rect { /// One of the corners of the rectangle, usually the left top one. diff --git a/crates/epaint/Cargo.toml b/crates/epaint/Cargo.toml index 094b6c45ed2..914724c4913 100644 --- a/crates/epaint/Cargo.toml +++ b/crates/epaint/Cargo.toml @@ -66,6 +66,14 @@ mint = ["emath/mint"] ## Allow serialization using [`serde`](https://docs.rs/serde). serde = ["dep:serde", "ahash/serde", "emath/serde", "ecolor/serde"] +## Allow diffing serialization using [`serde-diff`](https://docs.rs/serde-diff). +serde-diff = [ + "dep:serde", + "dep:serde-diff", + "emath/serde-diff", + "ecolor/serde-diff", +] + ## Change Vertex layout to be compatible with unity unity = [] @@ -91,6 +99,10 @@ log = { version = "0.4", optional = true, features = ["std"] } ## Allow serialization using [`serde`](https://docs.rs/serde) . serde = { version = "1", optional = true, features = ["derive", "rc"] } +## Allow diffing with serialization using [`serde-diff`](https://docs.rs/serde-diff/latest/serde_diff/) . +#serde-diff = { version = "0.4.1", optional = true } +serde-diff = { git = "https://github.com/linguini11/serde-diff.git", optional = true } + # native: [target.'cfg(not(target_arch = "wasm32"))'.dependencies] backtrace = { version = "0.3", optional = true } diff --git a/crates/epaint/src/bezier.rs b/crates/epaint/src/bezier.rs index d379a17a859..681adc6748d 100644 --- a/crates/epaint/src/bezier.rs +++ b/crates/epaint/src/bezier.rs @@ -11,6 +11,10 @@ use emath::*; /// See also [`QuadraticBezierShape`]. #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] pub struct CubicBezierShape { /// The first point is the starting point and the last one is the ending point of the curve. /// The middle points are the control points. @@ -374,6 +378,10 @@ impl From for Shape { /// See also [`CubicBezierShape`]. #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] pub struct QuadraticBezierShape { /// The first point is the starting point and the last one is the ending point of the curve. /// The middle point is the control points. diff --git a/crates/epaint/src/image.rs b/crates/epaint/src/image.rs index 35b0885c72a..a78354ddd2e 100644 --- a/crates/epaint/src/image.rs +++ b/crates/epaint/src/image.rs @@ -9,6 +9,10 @@ use crate::{textures::TextureOptions, Color32}; /// See also: [`ColorImage`], [`FontImage`]. #[derive(Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] pub enum ImageData { /// RGBA image. Color(ColorImage), @@ -45,6 +49,10 @@ impl ImageData { /// A 2D RGBA color image in RAM. #[derive(Clone, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] pub struct ColorImage { /// width, height. pub size: [usize; 2], @@ -230,6 +238,10 @@ impl From for ImageData { /// This is roughly interpreted as the opacity of a white image. #[derive(Clone, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] pub struct FontImage { /// width, height pub size: [usize; 2], @@ -332,6 +344,10 @@ fn fast_round(r: f32) -> u8 { /// Either a whole new image, or an update to a rectangular region of it. #[derive(Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] #[must_use = "The painter must take care of this"] pub struct ImageDelta { /// What to set the texture to. diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index 82fc7e0f464..a32d9a30e07 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -68,6 +68,10 @@ pub const WHITE_UV: emath::Pos2 = emath::pos2(0.0, 0.0); /// If you don't want to use a texture, use `TextureId::Managed(0)` and the [`WHITE_UV`] for uv-coord. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] pub enum TextureId { /// Textures allocated using [`TextureManager`]. /// @@ -90,6 +94,11 @@ impl Default for TextureId { /// /// Everything is using logical points. #[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] pub struct ClippedShape( /// Clip / scissor rectangle. /// Only show the part of the [`Shape`] that falls within this. diff --git a/crates/epaint/src/mesh.rs b/crates/epaint/src/mesh.rs index 09025268a11..e62b607be08 100644 --- a/crates/epaint/src/mesh.rs +++ b/crates/epaint/src/mesh.rs @@ -8,6 +8,10 @@ use emath::*; #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[cfg(not(feature = "unity"))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] #[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))] pub struct Vertex { /// Logical pixel coordinates (points). @@ -45,6 +49,10 @@ pub struct Vertex { /// Textured triangles in two dimensions. #[derive(Clone, Debug, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] pub struct Mesh { /// Draw as triangles (i.e. the length is always multiple of three). /// diff --git a/crates/epaint/src/shape.rs b/crates/epaint/src/shape.rs index 04621566157..29e697e6dee 100644 --- a/crates/epaint/src/shape.rs +++ b/crates/epaint/src/shape.rs @@ -20,6 +20,11 @@ pub use crate::{CubicBezierShape, QuadraticBezierShape}; /// and so must be recreated every time `pixels_per_point` changes. #[must_use = "Add a Shape to a Painter"] #[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] pub enum Shape { /// Paint nothing. This can be useful as a placeholder. Noop, @@ -58,7 +63,11 @@ pub enum Shape { CubicBezier(CubicBezierShape), /// Backend-specific painting. - Callback(PaintCallback), + Callback( + #[cfg_attr(feature = "serde", serde(skip))] + #[cfg_attr(all(feature = "serde-diff", feature = "serde"), serde_diff(skip))] + PaintCallback, + ), } #[test] @@ -339,6 +348,10 @@ impl Shape { /// How to paint a circle. #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] pub struct CircleShape { pub center: Pos2, pub radius: f32, @@ -392,6 +405,10 @@ impl From for Shape { /// A path which can be stroked and/or filled (if closed). #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] pub struct PathShape { /// Filled paths should prefer clockwise order. pub points: Vec, @@ -472,6 +489,10 @@ impl From for Shape { /// How to paint a rectangle. #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] pub struct RectShape { pub rect: Rect, @@ -530,6 +551,10 @@ impl From for Shape { #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] /// How rounded the corners of things should be pub struct Rounding { /// Radius of the rounding of the North-West (left top) corner. @@ -621,11 +646,16 @@ impl Rounding { /// This needs to be recreated if `pixels_per_point` (dpi scale) changes. #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] pub struct TextShape { /// Top left corner of the first character. pub pos: Pos2, /// The laid out text, from [`Fonts::layout_job`]. + #[cfg_attr(all(feature = "serde-diff", feature = "serde"), serde_diff(opaque))] pub galley: Arc, /// Add this underline to the whole text. @@ -838,6 +868,15 @@ impl std::cmp::PartialEq for PaintCallback { } } +impl Default for PaintCallback { + fn default() -> Self { + Self { + rect: Rect::default(), + callback: Arc::new(0), + } + } +} + impl From for Shape { #[inline(always)] fn from(shape: PaintCallback) -> Self { diff --git a/crates/epaint/src/stroke.rs b/crates/epaint/src/stroke.rs index acfa4ef1b96..c2974d9c7ef 100644 --- a/crates/epaint/src/stroke.rs +++ b/crates/epaint/src/stroke.rs @@ -7,6 +7,10 @@ use super::*; /// The default stroke is the same as [`Stroke::none`]. #[derive(Clone, Copy, Debug, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] pub struct Stroke { pub width: f32, pub color: Color32, diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index d6d81d88b61..33748905457 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -264,6 +264,7 @@ pub struct TextWrapping { /// Try to break text so that no row is wider than this. /// Set to [`f32::INFINITY`] to turn off wrapping. /// Note that `\n` always produces a new line. + #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_f32_custom"))] pub max_width: f32, /// Maximum amount of rows the text should have. @@ -277,6 +278,21 @@ pub struct TextWrapping { pub overflow_character: Option, } +#[cfg(feature = "serde")] +fn serialize_f32_custom(x: &f32, s: S) -> Result +where + S: serde::Serializer, +{ + // Workaround for the fact that INFINITY and NEG_INFINITY serialize as null and then fail to deserialize + if *x == f32::INFINITY { + s.serialize_f32(f32::MAX) + } else if *x == f32::NEG_INFINITY { + s.serialize_f32(f32::MIN) + } else { + s.serialize_f32(*x) + } +} + impl std::hash::Hash for TextWrapping { #[inline] fn hash(&self, state: &mut H) { @@ -311,7 +327,7 @@ impl Default for TextWrapping { /// You can create a [`Galley`] using [`crate::Fonts::layout_job`]; /// /// This needs to be recreated if `pixels_per_point` (dpi scale) changes. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Galley { /// The job that this galley is the result of. diff --git a/crates/epaint/src/textures.rs b/crates/epaint/src/textures.rs index e6b423746c8..839e08f27ca 100644 --- a/crates/epaint/src/textures.rs +++ b/crates/epaint/src/textures.rs @@ -148,6 +148,10 @@ impl TextureMeta { /// How the texture texels are filtered. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] pub struct TextureOptions { /// How to filter when magnifying (when texels are larger than pixels). pub magnification: TextureFilter, @@ -180,6 +184,10 @@ impl Default for TextureOptions { /// How the texture texels are filtered. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] pub enum TextureFilter { /// Show the nearest pixel value. /// @@ -198,6 +206,10 @@ pub enum TextureFilter { /// These are commands given to the integration painter. #[derive(Clone, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + all(feature = "serde-diff", feature = "serde"), + derive(serde_diff::SerdeDiff) +)] #[must_use = "The painter must take care of this"] pub struct TexturesDelta { /// New or changed textures. Apply before painting. diff --git a/examples/confirm_exit/src/main.rs b/examples/confirm_exit/src/main.rs index 05b45bd0b4e..d18ca99cc45 100644 --- a/examples/confirm_exit/src/main.rs +++ b/examples/confirm_exit/src/main.rs @@ -12,6 +12,7 @@ fn main() -> Result<(), eframe::Error> { "Confirm exit", options, Box::new(|_cc| Box::::default()), + false, ) } diff --git a/examples/custom_3d_glow/src/main.rs b/examples/custom_3d_glow/src/main.rs index f72859d23db..c5cb4f8744f 100644 --- a/examples/custom_3d_glow/src/main.rs +++ b/examples/custom_3d_glow/src/main.rs @@ -18,6 +18,7 @@ fn main() -> Result<(), eframe::Error> { "Custom 3D painting in eframe using glow", options, Box::new(|cc| Box::new(MyApp::new(cc))), + false, ) } diff --git a/examples/custom_font/src/main.rs b/examples/custom_font/src/main.rs index bc84223b208..32f8bbcb472 100644 --- a/examples/custom_font/src/main.rs +++ b/examples/custom_font/src/main.rs @@ -12,6 +12,7 @@ fn main() -> Result<(), eframe::Error> { "egui example: custom font", options, Box::new(|cc| Box::new(MyApp::new(cc))), + false, ) } diff --git a/examples/custom_font_style/src/main.rs b/examples/custom_font_style/src/main.rs index 89117b4f01a..bbf8c563037 100644 --- a/examples/custom_font_style/src/main.rs +++ b/examples/custom_font_style/src/main.rs @@ -11,6 +11,7 @@ fn main() -> Result<(), eframe::Error> { "egui example: global font style", options, Box::new(|cc| Box::new(MyApp::new(cc))), + false, ) } diff --git a/examples/custom_window_frame/src/main.rs b/examples/custom_window_frame/src/main.rs index 8ea32fbf74a..130e7708999 100644 --- a/examples/custom_window_frame/src/main.rs +++ b/examples/custom_window_frame/src/main.rs @@ -19,6 +19,7 @@ fn main() -> Result<(), eframe::Error> { "Custom window frame", // unused title options, Box::new(|_cc| Box::::default()), + false, ) } diff --git a/examples/download_image/src/main.rs b/examples/download_image/src/main.rs index 2a2c3540e2c..e361eb747cb 100644 --- a/examples/download_image/src/main.rs +++ b/examples/download_image/src/main.rs @@ -11,6 +11,7 @@ fn main() -> Result<(), eframe::Error> { "Download and show an image with eframe/egui", options, Box::new(|_cc| Box::::default()), + false, ) } diff --git a/examples/file_dialog/src/main.rs b/examples/file_dialog/src/main.rs index 77f88872afc..5a0bf2560cb 100644 --- a/examples/file_dialog/src/main.rs +++ b/examples/file_dialog/src/main.rs @@ -13,6 +13,7 @@ fn main() -> Result<(), eframe::Error> { "Native file dialogs and drag-and-drop files", options, Box::new(|_cc| Box::::default()), + false, ) } diff --git a/examples/hello_world/src/main.rs b/examples/hello_world/src/main.rs index 47c045db51b..a97cd0045c3 100644 --- a/examples/hello_world/src/main.rs +++ b/examples/hello_world/src/main.rs @@ -12,6 +12,7 @@ fn main() -> Result<(), eframe::Error> { "My egui App", options, Box::new(|_cc| Box::::default()), + false, ) } diff --git a/examples/hello_world_par/src/main.rs b/examples/hello_world_par/src/main.rs index b9d03ba6add..aade3c7a8eb 100644 --- a/examples/hello_world_par/src/main.rs +++ b/examples/hello_world_par/src/main.rs @@ -17,6 +17,7 @@ fn main() -> Result<(), eframe::Error> { "My parallel egui App", options, Box::new(|_cc| Box::new(MyApp::new())), + false, ) } diff --git a/examples/keyboard_events/src/main.rs b/examples/keyboard_events/src/main.rs index a51bf19086c..a2b23474437 100644 --- a/examples/keyboard_events/src/main.rs +++ b/examples/keyboard_events/src/main.rs @@ -10,6 +10,7 @@ fn main() -> Result<(), eframe::Error> { "Keyboard events", options, Box::new(|_cc| Box::::default()), + false, ) } diff --git a/examples/puffin_profiler/src/main.rs b/examples/puffin_profiler/src/main.rs index 2cfc5e61d01..0706bc0ae75 100644 --- a/examples/puffin_profiler/src/main.rs +++ b/examples/puffin_profiler/src/main.rs @@ -10,6 +10,7 @@ fn main() -> Result<(), eframe::Error> { "My egui App", options, Box::new(|_cc| Box::::default()), + false, ) } diff --git a/examples/remote_rendering/Cargo.toml b/examples/remote_rendering/Cargo.toml new file mode 100644 index 00000000000..4a493d32736 --- /dev/null +++ b/examples/remote_rendering/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "remote_rendering" +version = "0.1.0" +authors = ["Emma Middleton "] +edition = "2021" +publish = false + +[dependencies] +tungstenite = "0.18.0" +serde = "1" +serde_json = "*" +serde_derive = "*" +egui = { path = "../../crates/egui" } +eframe = { path = "../../crates/eframe" } +egui_demo_lib = { path = "../../crates/egui_demo_lib" } +serde-diff = { git = "https://github.com/linguini11/serde-diff.git" } diff --git a/examples/remote_rendering/README.md b/examples/remote_rendering/README.md new file mode 100644 index 00000000000..c4e3c3cbf2d --- /dev/null +++ b/examples/remote_rendering/README.md @@ -0,0 +1,16 @@ +Supports the ability to render egui remotely using eframe. + +These examples use tungstenite (https://github.com/snapview/tungstenite-rs) for setting up the websocket client and server, but any setup should work. +Server +- Receives connections and information from client +- Specifies egui content, resulting in a FullOutput +- Serializes the FullOutput and sends to client +Client +- Connects to server +- Sends user input information to server +- Receives serialized content from server, deserializes it and displays it in an eframe + +```sh +cargo r -p remote_rendering --bin server +cargo r -p remote_rendering --bin client "ws://localhost:8083" +``` diff --git a/examples/remote_rendering/src/bin/client.rs b/examples/remote_rendering/src/bin/client.rs new file mode 100644 index 00000000000..5936331183a --- /dev/null +++ b/examples/remote_rendering/src/bin/client.rs @@ -0,0 +1,121 @@ +//use serde_diff::Apply; +use serde_diff::Apply; +use std::env; +use std::net::TcpStream; +use tungstenite::stream::MaybeTlsStream; +use tungstenite::{connect, Message, WebSocket}; + +fn main() -> Result<(), eframe::Error> { + let options = eframe::NativeOptions { + initial_window_size: Some(egui::vec2(1100.0, 700.0)), + ..Default::default() + }; + eframe::run_native( + "Debug Client", + options, + Box::new(|_cc| Box::::default()), + true, + ) +} + +fn connect_to_server(pixels_per_point: f32) -> WebSocket> { + let args: Vec = env::args().collect(); + if args.len() == 1 { + panic!("Must provide server url as command line argument: cargo run -- [url]"); + } + let url = &args[1]; + let (mut socket, _) = connect(url).expect("Can't connect to server"); + let connect_message = eframe::RemoteRenderingMessageType::Connect(pixels_per_point); + let serialized_connect_message = serde_json::to_string(&connect_message); + match serialized_connect_message { + Ok(message) => { + let _ = socket.write_message(Message::Text(message)); + socket + } + Err(e) => panic!("Unable to create connect message: {}", e), + } +} + +fn send_pixels_per_point(socket: &mut WebSocket>, pixels_per_point: f32) { + let pixels_per_point_message = + eframe::RemoteRenderingMessageType::PixelsPerPoint(pixels_per_point); + let serialized_pixels_per_point_message = serde_json::to_string(&pixels_per_point_message); + match serialized_pixels_per_point_message { + Ok(message) => { + let _ = socket.write_message(Message::Text(message)); + } + Err(e) => panic!("Unable to create pixels_per_point message: {}", e), + } +} + +fn send_raw_input(socket: &mut WebSocket>, raw_input: egui::RawInput) { + let raw_input_message = eframe::RemoteRenderingMessageType::RawInput(raw_input); + let serialized_raw_input_message = serde_json::to_string(&raw_input_message); + match serialized_raw_input_message { + Ok(message) => { + let _ = socket.write_message(Message::Text(message)); + } + Err(e) => panic!("Unable to create raw input message: {}", e), + } +} + +struct MyApp { + socket: WebSocket>, + pixels_per_point: f32, + full_output: egui::FullOutput, +} + +impl Default for MyApp { + fn default() -> Self { + // Set to appropriate value + let pixels_per_point = 2.0; + let socket = connect_to_server(pixels_per_point); + Self { + socket, + pixels_per_point, + full_output: egui::FullOutput::default(), + } + } +} + +impl eframe::App for MyApp { + fn update(&mut self, _ctx: &egui::Context, _frame: &mut eframe::Frame) {} + fn update_remote( + &mut self, + raw_input: egui::RawInput, + frame: &eframe::Frame, + ) -> (egui::output::FullOutput, f32) { + // Send pixels_per_point value if it has changed + if let Some(ppp) = frame.info().native_pixels_per_point { + if ppp != self.pixels_per_point { + send_pixels_per_point(&mut self.socket, ppp); + self.pixels_per_point = ppp; + } + } + // Send captured input + if !raw_input.events.is_empty() { + send_raw_input(&mut self.socket, raw_input); + } else { + send_raw_input(&mut self.socket, egui::RawInput::default()); + }; + let received_message = self + .socket + .read_message() + .expect("Error reading message from server"); + // Filter out ping messages + if let tungstenite::Message::Ping(_) = received_message { + return (self.full_output.clone(), self.pixels_per_point); + } + + let full_output_diff = received_message.into_text(); + + if let Ok(diff) = full_output_diff { + let mut deserializer = serde_json::Deserializer::from_str(&diff); + let _ = Apply::apply(&mut deserializer, &mut self.full_output); + //let deserialized: egui::FullOutput = serde_json::from_str(&diff).unwrap(); + //self.full_output = deserialized; + } + + (self.full_output.clone(), self.pixels_per_point) + } +} diff --git a/examples/remote_rendering/src/bin/server.rs b/examples/remote_rendering/src/bin/server.rs new file mode 100644 index 00000000000..f8ac9b4a4b2 --- /dev/null +++ b/examples/remote_rendering/src/bin/server.rs @@ -0,0 +1,70 @@ +use serde_diff::Diff; +use std::net::{TcpListener, TcpStream}; +use tungstenite::{accept, Message, WebSocket}; + +fn main() { + let mut client: WebSocket; + let mut current_pixels_per_point: f32 = 2.0; + let mut latest_raw_input: egui::RawInput = egui::RawInput::default(); + let mut context = egui::Context::default(); + let mut previous_full_output = egui::FullOutput::default(); + let mut count = 0; + let mut text = "👍🏾".to_string(); + let mut egui_demo_windows = egui_demo_lib::DemoWindows::default(); + + let server = TcpListener::bind("127.0.0.1:8083").unwrap(); + let stream = server.incoming().next().unwrap().unwrap(); + client = accept(stream).unwrap(); + + loop { + let message = &client.read_message(); + if let Ok(content) = message { + let msg_content = content.clone().into_text(); + if let Ok(msg) = msg_content { + let received_message = serde_json::from_str(&msg); + if let Ok(content) = received_message { + match content { + eframe::RemoteRenderingMessageType::Connect(pixels_per_point) => { + current_pixels_per_point = pixels_per_point; + } + eframe::RemoteRenderingMessageType::PixelsPerPoint(pixels_per_point) => { + current_pixels_per_point = pixels_per_point; + } + eframe::RemoteRenderingMessageType::RawInput(raw_input) => { + println!("raw input message received"); + latest_raw_input = raw_input; + } + } + } + } + } + + let ctx = context.clone(); + ctx.set_pixels_per_point(current_pixels_per_point); + ctx.begin_frame(latest_raw_input.clone()); + egui::Window::new("Hello world!") + .default_pos(egui::pos2(100.0, 0.0)) + .show(&ctx, |ui| { + ui.label(format!("Count: {:?}", count)); + //ui.add(egui::TextEdit::singleline(&mut text)); + }); + + //test + //egui_demo_windows.ui(&ctx); + //test + + ctx.request_repaint(); + let full_output = ctx.end_frame(); + context = ctx; + count += 1; + let old_full_output = previous_full_output.clone(); + //let serialized_full_output = serde_json::to_string(&full_output).unwrap(); + let serialized_full_output = + serde_json::to_string(&Diff::serializable(&old_full_output, &full_output)); + previous_full_output = full_output.clone(); + if let Ok(diff) = serialized_full_output { + //let _ = client.write_message(Message::Text(serialized_full_output)); + let _ = client.write_message(Message::Text(diff)); + } + } +} diff --git a/examples/retained_image/src/main.rs b/examples/retained_image/src/main.rs index 148217fc62a..0486bbcfe2a 100644 --- a/examples/retained_image/src/main.rs +++ b/examples/retained_image/src/main.rs @@ -13,6 +13,7 @@ fn main() -> Result<(), eframe::Error> { "Show an image with eframe/egui", options, Box::new(|_cc| Box::::default()), + false, ) } diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index 4dff5326df9..e6175d2979d 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -12,6 +12,7 @@ fn main() -> Result<(), eframe::Error> { "Take screenshots and display with eframe/egui", options, Box::new(|_cc| Box::::default()), + false, ) } diff --git a/examples/serial_windows/src/main.rs b/examples/serial_windows/src/main.rs index 5e85d646781..f731da2ca84 100644 --- a/examples/serial_windows/src/main.rs +++ b/examples/serial_windows/src/main.rs @@ -20,6 +20,7 @@ fn main() -> Result<(), eframe::Error> { "First Window", options.clone(), Box::new(|_cc| Box::new(MyApp { has_next: true })), + false, )?; std::thread::sleep(std::time::Duration::from_secs(2)); @@ -29,6 +30,7 @@ fn main() -> Result<(), eframe::Error> { "Second Window", options.clone(), Box::new(|_cc| Box::new(MyApp { has_next: true })), + false, )?; std::thread::sleep(std::time::Duration::from_secs(2)); @@ -38,6 +40,7 @@ fn main() -> Result<(), eframe::Error> { "Third Window", options, Box::new(|_cc| Box::new(MyApp { has_next: false })), + false, ) } diff --git a/examples/svg/src/main.rs b/examples/svg/src/main.rs index 5629f8035f2..a606f5af73c 100644 --- a/examples/svg/src/main.rs +++ b/examples/svg/src/main.rs @@ -16,6 +16,7 @@ fn main() -> Result<(), eframe::Error> { "svg example", options, Box::new(|_cc| Box::::default()), + false, ) } diff --git a/examples/user_attention/src/main.rs b/examples/user_attention/src/main.rs index 4f7e521d200..54f56231657 100644 --- a/examples/user_attention/src/main.rs +++ b/examples/user_attention/src/main.rs @@ -15,6 +15,7 @@ fn main() -> eframe::Result<()> { "User attention test", native_options, Box::new(|cc| Box::new(Application::new(cc))), + false, ) }