From c768d1d48e4c8b7620391f4b732ff836bc17adad Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 15 Mar 2022 17:21:52 +0100 Subject: [PATCH 01/32] Context::request_repaint will wake up the UI thread (#1366) This adds a callback (set by `Context::set_request_repaint_callback`) which integration can use to wake up the UI thread. eframe (egui_web and egui_glow) will use this, replacing `epi::Frame::request_repaint`. Existing code calling `epi::Frame::request_repaint` should be changed to instead call `egui::Context::request_repaint`. This is the first callback added to the egui API, which otherwise is completely driven by data. The purpose of this is to remove the confusion between the two `request_repaint` methods (by removing one). Furthermore, it makes `epi::Frame` a lot simpler, allowing future simplifications to it (perhaps no longer having it be `Send+Sync+Clone`). --- CHANGELOG.md | 1 + Cargo.lock | 2 ++ eframe/CHANGELOG.md | 1 + eframe/examples/download_image.rs | 6 +++--- egui-winit/src/epi.rs | 2 -- egui/src/context.rs | 20 +++++++++++++++++++- egui_demo_lib/Cargo.toml | 1 + egui_demo_lib/src/apps/http_app.rs | 3 +-- egui_glow/Cargo.toml | 1 + egui_glow/src/epi_backend.rs | 20 +++++++------------- egui_web/src/backend.rs | 19 ++++++++++--------- epi/src/lib.rs | 18 ------------------ 12 files changed, 46 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 610143f16..ccc18f40c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w ### Added ⭐ * Add `Shape::Callback` for backend-specific painting ([#1351](https://github.com/emilk/egui/pull/1351)). * Added `Frame::canvas` ([#1362](https://github.com/emilk/egui/pull/1362)). +* `Context::request_repaint` will wake up UI thread, if integrations has called `Context::set_request_repaint_callback` ([#1366](https://github.com/emilk/egui/pull/1366)). ### Changed 🔧 * `ClippedMesh` has been replaced with `ClippedPrimitive` ([#1351](https://github.com/emilk/egui/pull/1351)). diff --git a/Cargo.lock b/Cargo.lock index 55dee5c1b..30384431c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1053,6 +1053,7 @@ dependencies = [ "poll-promise", "serde", "syntect", + "tracing", "unicode_names2", ] @@ -1091,6 +1092,7 @@ dependencies = [ "glow", "glutin", "memoffset", + "parking_lot 0.12.0", "tracing", "wasm-bindgen", "web-sys", diff --git a/eframe/CHANGELOG.md b/eframe/CHANGELOG.md index 90cb61266..b7fb979da 100644 --- a/eframe/CHANGELOG.md +++ b/eframe/CHANGELOG.md @@ -7,6 +7,7 @@ NOTE: [`egui_web`](../egui_web/CHANGELOG.md), [`egui-winit`](../egui-winit/CHANG ## Unreleased * Remove the `egui_glium` feature. `eframe` will now always use `egui_glow` as the native backend ([#1357](https://github.com/emilk/egui/pull/1357)). * Change default for `NativeOptions::drag_and_drop_support` to `true` ([#1329](https://github.com/emilk/egui/pull/1329)). +* Removed `Frame::request_repaint` - just call `egui::Context::request_repaint` for the same effect ([#1366](https://github.com/emilk/egui/pull/1366)). ## 0.17.0 - 2022-02-22 diff --git a/eframe/examples/download_image.rs b/eframe/examples/download_image.rs index dede08c42..ba754b75a 100644 --- a/eframe/examples/download_image.rs +++ b/eframe/examples/download_image.rs @@ -20,18 +20,18 @@ impl epi::App for MyApp { "Download and show an image with eframe/egui" } - fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) { + fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { let promise = self.promise.get_or_insert_with(|| { // Begin download. // We download the image using `ehttp`, a library that works both in WASM and on native. // We use the `poll-promise` library to communicate with the UI thread. - let frame = frame.clone(); + let ctx = ctx.clone(); let (sender, promise) = Promise::new(); let request = ehttp::Request::get("https://picsum.photos/seed/1.759706314/1024"); ehttp::fetch(request, move |response| { let image = response.and_then(parse_response); sender.send(image); // send the results back to the UI thread. - frame.request_repaint(); // wake up UI thread + ctx.request_repaint(); // wake up UI thread }); promise }); diff --git a/egui-winit/src/epi.rs b/egui-winit/src/epi.rs index d257ca21e..e09af05ef 100644 --- a/egui-winit/src/epi.rs +++ b/egui-winit/src/epi.rs @@ -233,7 +233,6 @@ impl EpiIntegration { max_texture_side: usize, window: &winit::window::Window, gl: &std::rc::Rc, - repaint_signal: std::sync::Arc, persistence: crate::epi::Persistence, app: Box, ) -> Self { @@ -252,7 +251,6 @@ impl EpiIntegration { native_pixels_per_point: Some(crate::native_pixels_per_point(window)), }, output: Default::default(), - repaint_signal, }); if prefer_dark_mode == Some(true) { diff --git a/egui/src/context.rs b/egui/src/context.rs index fef366f68..75057f2c7 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -48,6 +48,7 @@ struct ContextImpl { /// While positive, keep requesting repaints. Decrement at the end of each frame. repaint_requests: u32, + request_repaint_callbacks: Option>, } impl ContextImpl { @@ -533,11 +534,28 @@ impl Context { impl Context { /// Call this if there is need to repaint the UI, i.e. if you are showing an animation. + /// /// If this is called at least once in a frame, then there will be another frame right after this. /// Call as many times as you wish, only one repaint will be issued. + /// + /// If called from outside the UI thread, the UI thread will wake up and run, + /// provided the egui integration has set that up via [`Self::set_request_repaint_callback`] + /// (this will work on `eframe`). pub fn request_repaint(&self) { // request two frames of repaint, just to cover some corner cases (frame delays): - self.write().repaint_requests = 2; + let mut ctx = self.write(); + ctx.repaint_requests = 2; + if let Some(callback) = &ctx.request_repaint_callbacks { + (callback)(); + } + } + + /// For integrations: this callback will be called when an egui user calls [`Self::request_repaint`]. + /// + /// This lets you wake up a sleeping UI thread. + pub fn set_request_repaint_callback(&self, callback: impl Fn() + Send + Sync + 'static) { + let callback = Box::new(callback); + self.write().request_repaint_callbacks = Some(callback); } /// Tell `egui` which fonts to use. diff --git a/egui_demo_lib/Cargo.toml b/egui_demo_lib/Cargo.toml index 11ff67645..a41f3059e 100644 --- a/egui_demo_lib/Cargo.toml +++ b/egui_demo_lib/Cargo.toml @@ -39,6 +39,7 @@ epi = { version = "0.17.0", path = "../epi" } chrono = { version = "0.4", optional = true, features = ["js-sys", "wasmbind"] } enum-map = { version = "2", features = ["serde"] } +tracing = "0.1" unicode_names2 = { version = "0.5.0", default-features = false } # feature "http": diff --git a/egui_demo_lib/src/apps/http_app.rs b/egui_demo_lib/src/apps/http_app.rs index c8215b425..63ae9f013 100644 --- a/egui_demo_lib/src/apps/http_app.rs +++ b/egui_demo_lib/src/apps/http_app.rs @@ -78,11 +78,10 @@ impl epi::App for HttpApp { if trigger_fetch { let ctx = ctx.clone(); - let frame = frame.clone(); let (sender, promise) = Promise::new(); let request = ehttp::Request::get(&self.url); ehttp::fetch(request, move |response| { - frame.request_repaint(); // wake up UI thread + ctx.request_repaint(); // wake up UI thread let resource = response.map(|response| Resource::from_response(&ctx, response)); sender.send(resource); }); diff --git a/egui_glow/Cargo.toml b/egui_glow/Cargo.toml index 924210a1e..90dfa4862 100644 --- a/egui_glow/Cargo.toml +++ b/egui_glow/Cargo.toml @@ -64,6 +64,7 @@ epi = { version = "0.17.0", path = "../epi", optional = true } bytemuck = "1.7" glow = "0.11" memoffset = "0.6" +parking_lot = "0.12" tracing = "0.1" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/egui_glow/src/epi_backend.rs b/egui_glow/src/epi_backend.rs index 57c3f46ef..bef12fad2 100644 --- a/egui_glow/src/epi_backend.rs +++ b/egui_glow/src/epi_backend.rs @@ -3,14 +3,6 @@ use egui_winit::winit; struct RequestRepaintEvent; -struct GlowRepaintSignal(std::sync::Mutex>); - -impl epi::backend::RepaintSignal for GlowRepaintSignal { - fn request_repaint(&self) { - self.0.lock().unwrap().send_event(RequestRepaintEvent).ok(); - } -} - #[allow(unsafe_code)] fn create_display( window_builder: winit::window::WindowBuilder, @@ -56,10 +48,6 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { let (gl_window, gl) = create_display(window_builder, &event_loop); let gl = std::rc::Rc::new(gl); - let repaint_signal = std::sync::Arc::new(GlowRepaintSignal(std::sync::Mutex::new( - event_loop.create_proxy(), - ))); - let mut painter = crate::Painter::new(gl.clone(), None, "") .unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error)); let mut integration = egui_winit::epi::EpiIntegration::new( @@ -67,11 +55,17 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { painter.max_texture_side(), gl_window.window(), &gl, - repaint_signal, persistence, app, ); + { + let event_loop_proxy = parking_lot::Mutex::new(event_loop.create_proxy()); + integration.egui_ctx.set_request_repaint_callback(move || { + event_loop_proxy.lock().send_event(RequestRepaintEvent).ok(); + }); + } + let mut is_focused = true; event_loop.run(move |event, _, control_flow| { diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index 50313303b..b85d1a08d 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -50,12 +50,6 @@ impl NeedRepaint { } } -impl epi::backend::RepaintSignal for NeedRepaint { - fn request_repaint(&self) { - self.0.store(true, SeqCst); - } -} - // ---------------------------------------------------------------------------- fn web_location() -> epi::Location { @@ -150,8 +144,6 @@ impl AppRunner { let prefer_dark_mode = crate::prefer_dark_mode(); - let needs_repaint: std::sync::Arc = Default::default(); - let frame = epi::Frame::new(epi::backend::FrameData { info: epi::IntegrationInfo { name: "egui_web", @@ -163,10 +155,19 @@ impl AppRunner { native_pixels_per_point: Some(native_pixels_per_point()), }, output: Default::default(), - repaint_signal: needs_repaint.clone(), }); + let needs_repaint: std::sync::Arc = Default::default(); + let egui_ctx = egui::Context::default(); + + { + let needs_repaint = needs_repaint.clone(); + egui_ctx.set_request_repaint_callback(move || { + needs_repaint.0.store(true, SeqCst); + }); + } + load_memory(&egui_ctx); if prefer_dark_mode == Some(true) { egui_ctx.set_visuals(egui::Visuals::dark()); diff --git a/epi/src/lib.rs b/epi/src/lib.rs index 033a4a788..c618c0d8e 100644 --- a/epi/src/lib.rs +++ b/epi/src/lib.rs @@ -359,13 +359,6 @@ impl Frame { self.lock().output.drag_window = true; } - /// This signals the [`egui`] integration that a repaint is required. - /// - /// Call this e.g. when a background process finishes in an async context and/or background thread. - pub fn request_repaint(&self) { - self.lock().repaint_signal.request_repaint(); - } - /// for integrations only: call once per frame pub fn take_app_output(&self) -> crate::backend::AppOutput { std::mem::take(&mut self.lock().output) @@ -524,14 +517,6 @@ pub const APP_KEY: &str = "app"; pub mod backend { use super::*; - /// How to signal the [`egui`] integration that a repaint is required. - pub trait RepaintSignal: Send + Sync { - /// This signals the [`egui`] integration that a repaint is required. - /// - /// Call this e.g. when a background process finishes in an async context and/or background thread. - fn request_repaint(&self); - } - /// The data required by [`Frame`] each frame. pub struct FrameData { /// Information about the integration. @@ -539,9 +524,6 @@ pub mod backend { /// Where the app can issue commands back to the integration. pub output: AppOutput, - - /// If you need to request a repaint from another thread, clone this and send it to that other thread. - pub repaint_signal: std::sync::Arc, } /// Action that can be taken by the user app. From c8f6cae3624e2c1303f7d7ca0158ae3b07b8ac15 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 16 Mar 2022 15:39:48 +0100 Subject: [PATCH 02/32] eframe app creation refactor (#1363) * Change how eframe apps are created * eframe: re-export epi::* so users don't need to care about what epi is --- eframe/examples/confirm_exit.rs | 20 ++-- eframe/examples/custom_3d.rs | 60 ++++++------ eframe/examples/custom_font.rs | 93 +++++++++---------- eframe/examples/download_image.rs | 16 ++-- eframe/examples/file_dialog.rs | 30 +++--- eframe/examples/hello_world.rs | 20 ++-- eframe/examples/image.rs | 22 ++--- eframe/examples/svg.rs | 26 +++--- eframe/src/lib.rs | 92 +++++++++--------- egui-winit/src/epi.rs | 57 ++++-------- egui/src/lib.rs | 4 +- egui_demo_app/src/lib.rs | 3 +- egui_demo_app/src/main.rs | 5 +- egui_demo_lib/src/apps/color_test.rs | 4 - egui_demo_lib/src/apps/demo/app.rs | 26 ------ egui_demo_lib/src/apps/fractal_clock.rs | 4 - egui_demo_lib/src/apps/http_app.rs | 4 - .../src/easy_mark/easy_mark_editor.rs | 4 - egui_demo_lib/src/wrap_app.rs | 51 +++++----- egui_glium/examples/native_texture.rs | 70 +++++++------- egui_glium/examples/pure_glium.rs | 36 +++---- egui_glium/src/lib.rs | 1 - egui_glow/examples/pure_glow.rs | 72 +++++++------- egui_glow/src/epi_backend.rs | 33 +++++-- egui_web/src/backend.rs | 18 ++-- epi/src/lib.rs | 60 ++++++------ 26 files changed, 387 insertions(+), 444 deletions(-) diff --git a/eframe/examples/confirm_exit.rs b/eframe/examples/confirm_exit.rs index f1e8a359d..1e9035fa4 100644 --- a/eframe/examples/confirm_exit.rs +++ b/eframe/examples/confirm_exit.rs @@ -1,6 +1,11 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release -use eframe::{egui, epi}; +use eframe::egui; + +fn main() { + let options = eframe::NativeOptions::default(); + eframe::run_native("Confirm exit", options, |_cc| Box::new(MyApp::default())); +} #[derive(Default)] struct MyApp { @@ -8,17 +13,13 @@ struct MyApp { is_exiting: bool, } -impl epi::App for MyApp { - fn name(&self) -> &str { - "Confirm exit" - } - +impl eframe::App for MyApp { fn on_exit_event(&mut self) -> bool { self.is_exiting = true; self.can_exit } - fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) { + fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { ui.heading("Try to close the window"); }); @@ -42,8 +43,3 @@ impl epi::App for MyApp { } } } - -fn main() { - let options = eframe::NativeOptions::default(); - eframe::run_native(Box::new(MyApp::default()), options); -} diff --git a/eframe/examples/custom_3d.rs b/eframe/examples/custom_3d.rs index 548918f74..8b14f130d 100644 --- a/eframe/examples/custom_3d.rs +++ b/eframe/examples/custom_3d.rs @@ -8,25 +8,42 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release -use eframe::{egui, epi}; +use eframe::egui; use parking_lot::Mutex; use std::sync::Arc; -#[derive(Default)] +fn main() { + let options = eframe::NativeOptions::default(); + eframe::run_native("Custom 3D painting in eframe", options, |cc| { + Box::new(MyApp::new(cc)) + }); +} + struct MyApp { - rotating_triangle: Arc>>, + /// Behind an `Arc>` so we can pass it to [`egui::PaintCallback`] and paint later. + rotating_triangle: Arc>, angle: f32, } -impl epi::App for MyApp { - fn name(&self) -> &str { - "Custom 3D painting inside an egui window" +impl MyApp { + fn new(cc: &eframe::CreationContext<'_>) -> Self { + Self { + rotating_triangle: Arc::new(Mutex::new(RotatingTriangle::new(&cc.gl))), + angle: 0.0, + } } +} - fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { +impl eframe::App for MyApp { + fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { - ui.heading("Here is some 3D stuff:"); + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 0.0; + ui.label("The triangle is being painted using "); + ui.hyperlink_to("glow", "https://github.com/grovesNL/glow"); + ui.label(" (OpenGL)."); + }); egui::ScrollArea::both().show(ui, |ui| { egui::Frame::dark_canvas(ui.style()).show(ui, |ui| { @@ -35,14 +52,10 @@ impl epi::App for MyApp { ui.label("Drag to rotate!"); }); }); + } - let mut frame = egui::Frame::window(&*ctx.style()); - frame.fill = frame.fill.linear_multiply(0.5); // transparent - egui::Window::new("3D stuff in a window") - .frame(frame) - .show(ctx, |ui| { - self.custom_painting(ui); - }); + fn on_exit(&mut self, gl: &glow::Context) { + self.rotating_triangle.lock().destroy(gl) } } @@ -53,17 +66,15 @@ impl MyApp { self.angle += response.drag_delta().x * 0.01; + // Clone locals so we can move them into the paint callback: let angle = self.angle; let rotating_triangle = self.rotating_triangle.clone(); - let callback = egui::epaint::PaintCallback { + let callback = egui::PaintCallback { rect, callback: std::sync::Arc::new(move |render_ctx| { if let Some(painter) = render_ctx.downcast_ref::() { - let mut rotating_triangle = rotating_triangle.lock(); - let rotating_triangle = rotating_triangle - .get_or_insert_with(|| RotatingTriangle::new(painter.gl())); - rotating_triangle.paint(painter.gl(), angle); + rotating_triangle.lock().paint(painter.gl(), angle); } else { eprintln!("Can't do custom painting because we are not using a glow context"); } @@ -163,9 +174,7 @@ impl RotatingTriangle { } } - // TODO: figure out how to call this in a nice way - #[allow(unused)] - fn destroy(self, gl: &glow::Context) { + fn destroy(&self, gl: &glow::Context) { use glow::HasContext as _; unsafe { gl.delete_program(self.program); @@ -186,8 +195,3 @@ impl RotatingTriangle { } } } - -fn main() { - let options = eframe::NativeOptions::default(); - eframe::run_native(Box::new(MyApp::default()), options); -} diff --git a/eframe/examples/custom_font.rs b/eframe/examples/custom_font.rs index 0192d32ca..dc74fe0d0 100644 --- a/eframe/examples/custom_font.rs +++ b/eframe/examples/custom_font.rs @@ -1,66 +1,61 @@ -use eframe::{egui, epi}; +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release + +use eframe::egui; + +fn main() { + let options = eframe::NativeOptions::default(); + eframe::run_native("egui example: custom font", options, |cc| { + Box::new(MyApp::new(cc)) + }); +} + +fn setup_custom_fonts(ctx: &egui::Context) { + // Start with the default fonts (we will be adding to them rather than replacing them). + let mut fonts = egui::FontDefinitions::default(); + + // Install my own font (maybe supporting non-latin characters). + // .ttf and .otf files supported. + fonts.font_data.insert( + "my_font".to_owned(), + egui::FontData::from_static(include_bytes!("../../epaint/fonts/Hack-Regular.ttf")), + ); + + // Put my font first (highest priority) for proportional text: + fonts + .families + .entry(egui::FontFamily::Proportional) + .or_default() + .insert(0, "my_font".to_owned()); + + // Put my font as last fallback for monospace: + fonts + .families + .entry(egui::FontFamily::Monospace) + .or_default() + .push("my_font".to_owned()); + + // Tell egui to use these fonts: + ctx.set_fonts(fonts); +} struct MyApp { text: String, } -impl Default for MyApp { - fn default() -> Self { +impl MyApp { + fn new(cc: &eframe::CreationContext<'_>) -> Self { + setup_custom_fonts(&cc.egui_ctx); Self { text: "Edit this text field if you want".to_owned(), } } } -impl epi::App for MyApp { - fn name(&self) -> &str { - "egui example: custom font" - } - - fn setup( - &mut self, - ctx: &egui::Context, - _frame: &epi::Frame, - _storage: Option<&dyn epi::Storage>, - _gl: &std::rc::Rc, - ) { - // Start with the default fonts (we will be adding to them rather than replacing them). - let mut fonts = egui::FontDefinitions::default(); - - // Install my own font (maybe supporting non-latin characters). - // .ttf and .otf files supported. - fonts.font_data.insert( - "my_font".to_owned(), - egui::FontData::from_static(include_bytes!("../../epaint/fonts/Hack-Regular.ttf")), - ); - - // Put my font first (highest priority) for proportional text: - fonts - .families - .entry(egui::FontFamily::Proportional) - .or_default() - .insert(0, "my_font".to_owned()); - - // Put my font as last fallback for monospace: - fonts - .families - .entry(egui::FontFamily::Monospace) - .or_default() - .push("my_font".to_owned()); - - // Tell egui to use these fonts: - ctx.set_fonts(fonts); - } - - fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { +impl eframe::App for MyApp { + fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { ui.heading("egui using custom fonts"); ui.text_edit_multiline(&mut self.text); }); } } - -fn main() { - let options = eframe::NativeOptions::default(); - eframe::run_native(Box::new(MyApp::default()), options); -} diff --git a/eframe/examples/download_image.rs b/eframe/examples/download_image.rs index ba754b75a..f79587624 100644 --- a/eframe/examples/download_image.rs +++ b/eframe/examples/download_image.rs @@ -1,12 +1,16 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release -use eframe::{egui, epi}; +use eframe::egui; use egui_extras::RetainedImage; use poll_promise::Promise; fn main() { let options = eframe::NativeOptions::default(); - eframe::run_native(Box::new(MyApp::default()), options); + eframe::run_native( + "Download and show an image with eframe/egui", + options, + |_cc| Box::new(MyApp::default()), + ); } #[derive(Default)] @@ -15,12 +19,8 @@ struct MyApp { promise: Option>>, } -impl epi::App for MyApp { - fn name(&self) -> &str { - "Download and show an image with eframe/egui" - } - - fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { +impl eframe::App for MyApp { + fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) { let promise = self.promise.get_or_insert_with(|| { // Begin download. // We download the image using `ehttp`, a library that works both in WASM and on native. diff --git a/eframe/examples/file_dialog.rs b/eframe/examples/file_dialog.rs index f4b2f5a91..66d0f3fc6 100644 --- a/eframe/examples/file_dialog.rs +++ b/eframe/examples/file_dialog.rs @@ -1,6 +1,18 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release -use eframe::{egui, epi}; +use eframe::egui; + +fn main() { + let options = eframe::NativeOptions { + drag_and_drop_support: true, + ..Default::default() + }; + eframe::run_native( + "Native file dialogs and drag-and-drop files", + options, + |_cc| Box::new(MyApp::default()), + ); +} #[derive(Default)] struct MyApp { @@ -8,12 +20,8 @@ struct MyApp { picked_path: Option, } -impl epi::App for MyApp { - fn name(&self) -> &str { - "Native file dialogs and drag-and-drop files" - } - - fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { +impl eframe::App for MyApp { + fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { ui.label("Drag-and-drop files onto the window!"); @@ -93,11 +101,3 @@ impl MyApp { } } } - -fn main() { - let options = eframe::NativeOptions { - drag_and_drop_support: true, - ..Default::default() - }; - eframe::run_native(Box::new(MyApp::default()), options); -} diff --git a/eframe/examples/hello_world.rs b/eframe/examples/hello_world.rs index 77f1479b7..d5a9576f0 100644 --- a/eframe/examples/hello_world.rs +++ b/eframe/examples/hello_world.rs @@ -1,6 +1,11 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release -use eframe::{egui, epi}; +use eframe::egui; + +fn main() { + let options = eframe::NativeOptions::default(); + eframe::run_native("My egui App", options, |_cc| Box::new(MyApp::default())); +} struct MyApp { name: String, @@ -16,12 +21,8 @@ impl Default for MyApp { } } -impl epi::App for MyApp { - fn name(&self) -> &str { - "My egui App" - } - - fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) { +impl eframe::App for MyApp { + fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { ui.heading("My egui Application"); ui.horizontal(|ui| { @@ -39,8 +40,3 @@ impl epi::App for MyApp { frame.set_window_size(ctx.used_size()); } } - -fn main() { - let options = eframe::NativeOptions::default(); - eframe::run_native(Box::new(MyApp::default()), options); -} diff --git a/eframe/examples/image.rs b/eframe/examples/image.rs index 97575c235..c91622f39 100644 --- a/eframe/examples/image.rs +++ b/eframe/examples/image.rs @@ -1,8 +1,15 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release -use eframe::{egui, epi}; +use eframe::egui; use egui_extras::RetainedImage; +fn main() { + let options = eframe::NativeOptions::default(); + eframe::run_native("Show an image with eframe/egui", options, |_cc| { + Box::new(MyApp::default()) + }); +} + struct MyApp { image: RetainedImage, } @@ -19,12 +26,8 @@ impl Default for MyApp { } } -impl epi::App for MyApp { - fn name(&self) -> &str { - "Show an image with eframe/egui" - } - - fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { +impl eframe::App for MyApp { + fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { ui.heading("This is an image:"); self.image.show(ui); @@ -37,8 +40,3 @@ impl epi::App for MyApp { }); } } - -fn main() { - let options = eframe::NativeOptions::default(); - eframe::run_native(Box::new(MyApp::default()), options); -} diff --git a/eframe/examples/svg.rs b/eframe/examples/svg.rs index b7d362f92..a4e71bf74 100644 --- a/eframe/examples/svg.rs +++ b/eframe/examples/svg.rs @@ -4,7 +4,15 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release -use eframe::{egui, epi}; +use eframe::egui; + +fn main() { + let options = eframe::NativeOptions { + initial_window_size: Some(egui::vec2(1000.0, 700.0)), + ..Default::default() + }; + eframe::run_native("svg example", options, |_cc| Box::new(MyApp::default())); +} struct MyApp { svg_image: egui_extras::RetainedImage, @@ -22,12 +30,8 @@ impl Default for MyApp { } } -impl epi::App for MyApp { - fn name(&self) -> &str { - "svg example" - } - - fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { +impl eframe::App for MyApp { + fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { ui.heading("SVG example"); ui.label("The SVG is rasterized and displayed as a texture."); @@ -39,11 +43,3 @@ impl epi::App for MyApp { }); } } - -fn main() { - let options = eframe::NativeOptions { - initial_window_size: Some(egui::vec2(1000.0, 700.0)), - ..Default::default() - }; - eframe::run_native(Box::new(MyApp::default()), options); -} diff --git a/eframe/src/lib.rs b/eframe/src/lib.rs index fcb55cecb..ffa4f568e 100644 --- a/eframe/src/lib.rs +++ b/eframe/src/lib.rs @@ -16,27 +16,32 @@ //! //! ## Usage, native: //! ``` no_run -//! use eframe::{epi, egui}; +//! use eframe::egui; +//! +//! fn main() { +//! let native_options = eframe::NativeOptions::default(); +//! eframe::run_native("My egui App", native_options, |cc| Box::new(MyEguiApp::new(cc))); +//! } //! //! #[derive(Default)] //! struct MyEguiApp {} //! -//! impl epi::App for MyEguiApp { -//! fn name(&self) -> &str { -//! "My egui App" -//! } +//! impl MyEguiApp { +//! fn new(cc: &eframe::CreationContext<'_>) -> Self { +//! // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals. +//! // Restore app state using cc.storage (requires the "persistence" feature). +//! // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use +//! // for e.g. egui::PaintCallback. +//! Self::default() +//! } +//! } //! -//! fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) { +//! impl eframe::App for MyEguiApp { +//! fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) { //! egui::CentralPanel::default().show(ctx, |ui| { //! ui.heading("Hello World!"); //! }); //! } -//!} -//! -//! fn main() { -//! let app = MyEguiApp::default(); -//! let native_options = eframe::NativeOptions::default(); -//! eframe::run_native(Box::new(app), native_options); //! } //! ``` //! @@ -49,8 +54,7 @@ //! #[cfg(target_arch = "wasm32")] //! #[wasm_bindgen] //! pub fn start(canvas_id: &str) -> Result<(), eframe::wasm_bindgen::JsValue> { -//! let app = MyEguiApp::default(); -//! eframe::start_web(canvas_id, Box::new(app)) +//! eframe::start_web(canvas_id, |cc| Box::new(MyApp::new(cc))) //! } //! ``` @@ -65,10 +69,11 @@ )] #![allow(clippy::needless_doctest_main)] +// Re-export all useful libraries: pub use {egui, egui::emath, egui::epaint, epi}; -#[cfg(not(target_arch = "wasm32"))] -pub use epi::NativeOptions; +// Re-export everything in `epi` so `eframe` users don't have to care about what `epi` is: +pub use epi::*; // ---------------------------------------------------------------------------- // When compiling for web @@ -83,16 +88,6 @@ pub use egui_web::wasm_bindgen; /// fill the whole width of the browser. /// This can be changed by overriding [`epi::Frame::max_size_points`]. /// -/// ### Usage, native: -/// ``` no_run -/// fn main() { -/// let app = MyEguiApp::default(); -/// let native_options = eframe::NativeOptions::default(); -/// eframe::run_native(Box::new(app), native_options); -/// } -/// ``` -/// -/// ### Web /// ``` no_run /// #[cfg(target_arch = "wasm32")] /// use wasm_bindgen::prelude::*; @@ -104,45 +99,54 @@ pub use egui_web::wasm_bindgen; /// #[cfg(target_arch = "wasm32")] /// #[wasm_bindgen] /// pub fn start(canvas_id: &str) -> Result<(), eframe::wasm_bindgen::JsValue> { -/// let app = MyEguiApp::default(); -/// eframe::start_web(canvas_id, Box::new(app)) +/// eframe::start_web(canvas_id, |cc| Box::new(MyEguiApp::new(cc))) /// } /// ``` #[cfg(target_arch = "wasm32")] -pub fn start_web(canvas_id: &str, app: Box) -> Result<(), wasm_bindgen::JsValue> { - egui_web::start(canvas_id, app)?; +pub fn start_web(canvas_id: &str, app_creator: AppCreator) -> Result<(), wasm_bindgen::JsValue> { + egui_web::start(canvas_id, app_creator)?; Ok(()) } // ---------------------------------------------------------------------------- // When compiling natively +/// This is how you start a native (desktop) app. +/// +/// The first argument is name of your app, used for the title bar of the native window +/// and the save location of persistence (see [`epi::App::save`]). +/// /// Call from `fn main` like this: /// ``` no_run -/// use eframe::{epi, egui}; +/// use eframe::egui; +/// +/// fn main() { +/// let native_options = eframe::NativeOptions::default(); +/// eframe::run_native("MyApp", native_options, |cc| Box::new(MyEguiApp::new(cc))); +/// } /// /// #[derive(Default)] /// struct MyEguiApp {} /// -/// impl epi::App for MyEguiApp { -/// fn name(&self) -> &str { -/// "My egui App" -/// } +/// impl MyEguiApp { +/// fn new(cc: &eframe::CreationContext<'_>) -> Self { +/// // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals. +/// // Restore app state using cc.storage (requires the "persistence" feature). +/// // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use +/// // for e.g. egui::PaintCallback. +/// Self::default() +/// } +/// } /// -/// fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) { +/// impl eframe::App for MyEguiApp { +/// fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) { /// egui::CentralPanel::default().show(ctx, |ui| { /// ui.heading("Hello World!"); /// }); /// } -///} -/// -/// fn main() { -/// let app = MyEguiApp::default(); -/// let native_options = eframe::NativeOptions::default(); -/// eframe::run_native(Box::new(app), native_options); /// } /// ``` #[cfg(not(target_arch = "wasm32"))] -pub fn run_native(app: Box, native_options: epi::NativeOptions) -> ! { - egui_glow::run(app, &native_options) +pub fn run_native(app_name: &str, native_options: NativeOptions, app_creator: AppCreator) -> ! { + egui_glow::run(app_name, &native_options, app_creator) } diff --git a/egui-winit/src/epi.rs b/egui-winit/src/epi.rs index e09af05ef..2674db33d 100644 --- a/egui-winit/src/epi.rs +++ b/egui-winit/src/epi.rs @@ -216,12 +216,11 @@ impl Persistence { /// Everything needed to make a winit-based integration for [`epi`]. pub struct EpiIntegration { - frame: epi::Frame, - persistence: crate::epi::Persistence, + pub frame: epi::Frame, + pub persistence: crate::epi::Persistence, pub egui_ctx: egui::Context, pending_full_output: egui::FullOutput, egui_winit: crate::State, - pub app: Box, /// When set, it is time to quit quit: bool, can_drag_window: bool, @@ -232,9 +231,7 @@ impl EpiIntegration { integration_name: &'static str, max_texture_side: usize, window: &winit::window::Window, - gl: &std::rc::Rc, persistence: crate::epi::Persistence, - app: Box, ) -> Self { let egui_ctx = egui::Context::default(); @@ -259,41 +256,21 @@ impl EpiIntegration { egui_ctx.set_visuals(egui::Visuals::light()); } - let mut slf = Self { + Self { frame, persistence, egui_ctx, egui_winit: crate::State::new(max_texture_side, window), pending_full_output: Default::default(), - app, quit: false, can_drag_window: false, - }; - - slf.setup(window, gl); - if slf.app.warm_up_enabled() { - slf.warm_up(window); } - - slf } - fn setup(&mut self, window: &winit::window::Window, gl: &std::rc::Rc) { - self.app - .setup(&self.egui_ctx, &self.frame, self.persistence.storage(), gl); - let app_output = self.frame.take_app_output(); - - if app_output.quit { - self.quit = self.app.on_exit_event(); - } - - crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output); - } - - fn warm_up(&mut self, window: &winit::window::Window) { + pub fn warm_up(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) { let saved_memory: egui::Memory = self.egui_ctx.memory().clone(); self.egui_ctx.memory().set_everything_is_visible(true); - let full_output = self.update(window); + let full_output = self.update(app, window); self.pending_full_output.append(full_output); // Handle it next frame *self.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge. self.egui_ctx.clear_animations(); @@ -304,11 +281,11 @@ impl EpiIntegration { self.quit } - pub fn on_event(&mut self, event: &winit::event::WindowEvent<'_>) { + pub fn on_event(&mut self, app: &mut dyn epi::App, event: &winit::event::WindowEvent<'_>) { use winit::event::{ElementState, MouseButton, WindowEvent}; match event { - WindowEvent::CloseRequested => self.quit = self.app.on_exit_event(), + WindowEvent::CloseRequested => self.quit = app.on_exit_event(), WindowEvent::Destroyed => self.quit = true, WindowEvent::MouseInput { button: MouseButton::Left, @@ -321,12 +298,16 @@ impl EpiIntegration { self.egui_winit.on_event(&self.egui_ctx, event); } - pub fn update(&mut self, window: &winit::window::Window) -> egui::FullOutput { + pub fn update( + &mut self, + app: &mut dyn epi::App, + window: &winit::window::Window, + ) -> egui::FullOutput { let frame_start = instant::Instant::now(); let raw_input = self.egui_winit.take_egui_input(window); let full_output = self.egui_ctx.run(raw_input, |egui_ctx| { - self.app.update(egui_ctx, &self.frame); + app.update(egui_ctx, &self.frame); }); self.pending_full_output.append(full_output); let full_output = std::mem::take(&mut self.pending_full_output); @@ -336,7 +317,7 @@ impl EpiIntegration { app_output.drag_window &= self.can_drag_window; // Necessary on Windows; see https://github.com/emilk/egui/pull/1108 self.can_drag_window = false; if app_output.quit { - self.quit = self.app.on_exit_event(); + self.quit = app.on_exit_event(); } crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output); } @@ -356,15 +337,9 @@ impl EpiIntegration { .handle_platform_output(window, &self.egui_ctx, platform_output); } - pub fn maybe_autosave(&mut self, window: &winit::window::Window) { + pub fn maybe_autosave(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) { self.persistence - .maybe_autosave(&mut *self.app, &self.egui_ctx, window); - } - - pub fn on_exit(&mut self, window: &winit::window::Window) { - self.app.on_exit(); - self.persistence - .save(&mut *self.app, &self.egui_ctx, window); + .maybe_autosave(&mut *app, &self.egui_ctx, window); } } diff --git a/egui/src/lib.rs b/egui/src/lib.rs index 07e283e42..e3a604150 100644 --- a/egui/src/lib.rs +++ b/egui/src/lib.rs @@ -386,8 +386,8 @@ pub use epaint::{ color, mutex, text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak}, textures::TexturesDelta, - AlphaImage, ClippedPrimitive, Color32, ColorImage, ImageData, Mesh, Rgba, Rounding, Shape, - Stroke, TextureHandle, TextureId, + AlphaImage, ClippedPrimitive, Color32, ColorImage, ImageData, Mesh, PaintCallback, Rgba, + Rounding, Shape, Stroke, TextureHandle, TextureId, }; pub mod text { diff --git a/egui_demo_app/src/lib.rs b/egui_demo_app/src/lib.rs index 487933f27..033aaa155 100644 --- a/egui_demo_app/src/lib.rs +++ b/egui_demo_app/src/lib.rs @@ -19,6 +19,5 @@ pub fn start(canvas_id: &str) -> Result<(), wasm_bindgen::JsValue> { // Redirect tracing to console.log and friends: tracing_wasm::set_as_global_default(); - let app = egui_demo_lib::WrapApp::default(); - eframe::start_web(canvas_id, Box::new(app)) + eframe::start_web(canvas_id, |cc| Box::new(egui_demo_lib::WrapApp::new(cc))) } diff --git a/egui_demo_app/src/main.rs b/egui_demo_app/src/main.rs index 1ad429b9b..30425599b 100644 --- a/egui_demo_app/src/main.rs +++ b/egui_demo_app/src/main.rs @@ -10,12 +10,13 @@ fn main() { // Log to stdout (if you run with `RUST_LOG=debug`). tracing_subscriber::fmt::init(); - let app = egui_demo_lib::WrapApp::default(); let options = eframe::NativeOptions { // Let's show off that we support transparent windows transparent: true, drag_and_drop_support: true, ..Default::default() }; - eframe::run_native(Box::new(app), options); + eframe::run_native("egui demo app", options, |cc| { + Box::new(egui_demo_lib::WrapApp::new(cc)) + }); } diff --git a/egui_demo_lib/src/apps/color_test.rs b/egui_demo_lib/src/apps/color_test.rs index ca25fd177..cf002269f 100644 --- a/egui_demo_lib/src/apps/color_test.rs +++ b/egui_demo_lib/src/apps/color_test.rs @@ -30,10 +30,6 @@ impl Default for ColorTest { } impl epi::App for ColorTest { - fn name(&self) -> &str { - "🎨 Color test" - } - fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) { egui::CentralPanel::default().show(ctx, |ui| { if frame.is_web() { diff --git a/egui_demo_lib/src/apps/demo/app.rs b/egui_demo_lib/src/apps/demo/app.rs index 5544a51ad..c01aa8b9c 100644 --- a/egui_demo_lib/src/apps/demo/app.rs +++ b/egui_demo_lib/src/apps/demo/app.rs @@ -1,7 +1,3 @@ -/// Demonstrates how to make an app using egui. -/// -/// Implements `epi::App` so it can be used with -/// [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium), [`egui_glow`](https://github.com/emilk/egui/tree/master/egui_glow) and [`egui_web`](https://github.com/emilk/egui/tree/master/egui_web). #[derive(Default)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] @@ -10,28 +6,6 @@ pub struct DemoApp { } impl epi::App for DemoApp { - fn name(&self) -> &str { - "✨ Demos" - } - - fn setup( - &mut self, - _ctx: &egui::Context, - _frame: &epi::Frame, - _storage: Option<&dyn epi::Storage>, - _gl: &std::rc::Rc, - ) { - #[cfg(feature = "persistence")] - if let Some(storage) = _storage { - *self = epi::get_value(storage, epi::APP_KEY).unwrap_or_default(); - } - } - - #[cfg(feature = "persistence")] - fn save(&mut self, storage: &mut dyn epi::Storage) { - epi::set_value(storage, epi::APP_KEY, self); - } - fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { self.demo_windows.ui(ctx); } diff --git a/egui_demo_lib/src/apps/fractal_clock.rs b/egui_demo_lib/src/apps/fractal_clock.rs index 943a01396..51a03f4c9 100644 --- a/egui_demo_lib/src/apps/fractal_clock.rs +++ b/egui_demo_lib/src/apps/fractal_clock.rs @@ -33,10 +33,6 @@ impl Default for FractalClock { } impl epi::App for FractalClock { - fn name(&self) -> &str { - "🕑 Fractal Clock" - } - fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { egui::CentralPanel::default() .frame(Frame::dark_canvas(&ctx.style())) diff --git a/egui_demo_lib/src/apps/http_app.rs b/egui_demo_lib/src/apps/http_app.rs index 63ae9f013..a2c9097c7 100644 --- a/egui_demo_lib/src/apps/http_app.rs +++ b/egui_demo_lib/src/apps/http_app.rs @@ -54,10 +54,6 @@ impl Default for HttpApp { } impl epi::App for HttpApp { - fn name(&self) -> &str { - "⬇ HTTP" - } - fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) { egui::TopBottomPanel::bottom("http_bottom").show(ctx, |ui| { let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true); diff --git a/egui_demo_lib/src/easy_mark/easy_mark_editor.rs b/egui_demo_lib/src/easy_mark/easy_mark_editor.rs index dc066e383..30350e842 100644 --- a/egui_demo_lib/src/easy_mark/easy_mark_editor.rs +++ b/egui_demo_lib/src/easy_mark/easy_mark_editor.rs @@ -30,10 +30,6 @@ impl Default for EasyMarkEditor { } impl epi::App for EasyMarkEditor { - fn name(&self) -> &str { - "🖹 EasyMark editor" - } - fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { egui::TopBottomPanel::bottom("easy_mark_bottom").show(ctx, |ui| { let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true); diff --git a/egui_demo_lib/src/wrap_app.rs b/egui_demo_lib/src/wrap_app.rs index 0a1195978..d8d701d3f 100644 --- a/egui_demo_lib/src/wrap_app.rs +++ b/egui_demo_lib/src/wrap_app.rs @@ -12,14 +12,26 @@ pub struct Apps { } impl Apps { - fn iter_mut(&mut self) -> impl Iterator { + fn iter_mut(&mut self) -> impl Iterator { vec![ - ("demo", &mut self.demo as &mut dyn epi::App), - ("easymark", &mut self.easy_mark_editor as &mut dyn epi::App), + ("✨ Demos", "demo", &mut self.demo as &mut dyn epi::App), + ( + "🖹 EasyMark editor", + "easymark", + &mut self.easy_mark_editor as &mut dyn epi::App, + ), #[cfg(feature = "http")] - ("http", &mut self.http as &mut dyn epi::App), - ("clock", &mut self.clock as &mut dyn epi::App), - ("colors", &mut self.color_test as &mut dyn epi::App), + ("⬇ HTTP", "http", &mut self.http as &mut dyn epi::App), + ( + "🕑 Fractal Clock", + "clock", + &mut self.clock as &mut dyn epi::App, + ), + ( + "🎨 Color test", + "colors", + &mut self.color_test as &mut dyn epi::App, + ), ] .into_iter() } @@ -37,24 +49,17 @@ pub struct WrapApp { dropped_files: Vec, } -impl epi::App for WrapApp { - fn name(&self) -> &str { - "egui demo apps" - } - - fn setup( - &mut self, - _ctx: &egui::Context, - _frame: &epi::Frame, - _storage: Option<&dyn epi::Storage>, - _gl: &std::rc::Rc, - ) { +impl WrapApp { + pub fn new(_cc: &epi::CreationContext<'_>) -> Self { #[cfg(feature = "persistence")] - if let Some(storage) = _storage { - *self = epi::get_value(storage, epi::APP_KEY).unwrap_or_default(); + if let Some(storage) = _cc.storage { + return epi::get_value(storage, epi::APP_KEY).unwrap_or_default(); } + Self::default() } +} +impl epi::App for WrapApp { #[cfg(feature = "persistence")] fn save(&mut self, storage: &mut dyn epi::Storage) { epi::set_value(storage, epi::APP_KEY, self); @@ -111,7 +116,7 @@ impl epi::App for WrapApp { let mut found_anchor = false; - for (anchor, app) in self.apps.iter_mut() { + for (_name, anchor, app) in self.apps.iter_mut() { if anchor == self.selected_anchor || ctx.memory().everything_is_visible() { app.update(ctx, frame); found_anchor = true; @@ -138,9 +143,9 @@ impl WrapApp { ui.checkbox(&mut self.backend_panel.open, "💻 Backend"); ui.separator(); - for (anchor, app) in self.apps.iter_mut() { + for (name, anchor, _app) in self.apps.iter_mut() { if ui - .selectable_label(self.selected_anchor == anchor, app.name()) + .selectable_label(self.selected_anchor == anchor, name) .clicked() { self.selected_anchor = anchor.to_owned(); diff --git a/egui_glium/examples/native_texture.rs b/egui_glium/examples/native_texture.rs index a5197ccf1..8d65c5281 100644 --- a/egui_glium/examples/native_texture.rs +++ b/egui_glium/examples/native_texture.rs @@ -4,41 +4,6 @@ use glium::glutin; -fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display { - let window_builder = glutin::window::WindowBuilder::new() - .with_resizable(true) - .with_inner_size(glutin::dpi::LogicalSize { - width: 800.0, - height: 600.0, - }) - .with_title("egui_glium example"); - - let context_builder = glutin::ContextBuilder::new() - .with_depth_buffer(0) - .with_srgb(true) - .with_stencil_buffer(0) - .with_vsync(true); - - glium::Display::new(window_builder, context_builder, event_loop).unwrap() -} - -fn load_glium_image(png_data: &[u8]) -> glium::texture::RawImage2d { - // Load image using the image crate: - let image = image::load_from_memory(png_data).unwrap().to_rgba8(); - let image_dimensions = image.dimensions(); - - // Premultiply alpha: - let pixels: Vec<_> = image - .into_vec() - .chunks_exact(4) - .map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3])) - .flat_map(|color| color.to_array()) - .collect(); - - // Convert to glium image: - glium::texture::RawImage2d::from_raw_rgba(pixels, image_dimensions) -} - fn main() { let event_loop = glutin::event_loop::EventLoop::with_user_event(); let display = create_display(&event_loop); @@ -127,3 +92,38 @@ fn main() { } }); } + +fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display { + let window_builder = glutin::window::WindowBuilder::new() + .with_resizable(true) + .with_inner_size(glutin::dpi::LogicalSize { + width: 800.0, + height: 600.0, + }) + .with_title("egui_glium example"); + + let context_builder = glutin::ContextBuilder::new() + .with_depth_buffer(0) + .with_srgb(true) + .with_stencil_buffer(0) + .with_vsync(true); + + glium::Display::new(window_builder, context_builder, event_loop).unwrap() +} + +fn load_glium_image(png_data: &[u8]) -> glium::texture::RawImage2d { + // Load image using the image crate: + let image = image::load_from_memory(png_data).unwrap().to_rgba8(); + let image_dimensions = image.dimensions(); + + // Premultiply alpha: + let pixels: Vec<_> = image + .into_vec() + .chunks_exact(4) + .map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3])) + .flat_map(|color| color.to_array()) + .collect(); + + // Convert to glium image: + glium::texture::RawImage2d::from_raw_rgba(pixels, image_dimensions) +} diff --git a/egui_glium/examples/pure_glium.rs b/egui_glium/examples/pure_glium.rs index 52808d7b7..4fe900f71 100644 --- a/egui_glium/examples/pure_glium.rs +++ b/egui_glium/examples/pure_glium.rs @@ -4,24 +4,6 @@ use glium::glutin; -fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display { - let window_builder = glutin::window::WindowBuilder::new() - .with_resizable(true) - .with_inner_size(glutin::dpi::LogicalSize { - width: 800.0, - height: 600.0, - }) - .with_title("egui_glium example"); - - let context_builder = glutin::ContextBuilder::new() - .with_depth_buffer(0) - .with_srgb(true) - .with_stencil_buffer(0) - .with_vsync(true); - - glium::Display::new(window_builder, context_builder, event_loop).unwrap() -} - fn main() { let event_loop = glutin::event_loop::EventLoop::with_user_event(); let display = create_display(&event_loop); @@ -89,3 +71,21 @@ fn main() { } }); } + +fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display { + let window_builder = glutin::window::WindowBuilder::new() + .with_resizable(true) + .with_inner_size(glutin::dpi::LogicalSize { + width: 800.0, + height: 600.0, + }) + .with_title("egui_glium example"); + + let context_builder = glutin::ContextBuilder::new() + .with_depth_buffer(0) + .with_srgb(true) + .with_stencil_buffer(0) + .with_vsync(true); + + glium::Display::new(window_builder, context_builder, event_loop).unwrap() +} diff --git a/egui_glium/src/lib.rs b/egui_glium/src/lib.rs index 9e6760e53..1ecfda86a 100644 --- a/egui_glium/src/lib.rs +++ b/egui_glium/src/lib.rs @@ -2,7 +2,6 @@ //! //! The main type you want to use is [`EguiGlium`]. //! -//! This library is an [`epi`] backend. //! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead. // Forbid warnings in release builds: diff --git a/egui_glow/examples/pure_glow.rs b/egui_glow/examples/pure_glow.rs index 870f41215..4b832878c 100644 --- a/egui_glow/examples/pure_glow.rs +++ b/egui_glow/examples/pure_glow.rs @@ -2,42 +2,6 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release -fn create_display( - event_loop: &glutin::event_loop::EventLoop<()>, -) -> ( - glutin::WindowedContext, - glow::Context, -) { - let window_builder = glutin::window::WindowBuilder::new() - .with_resizable(true) - .with_inner_size(glutin::dpi::LogicalSize { - width: 800.0, - height: 600.0, - }) - .with_title("egui_glow example"); - - let gl_window = unsafe { - glutin::ContextBuilder::new() - .with_depth_buffer(0) - .with_srgb(true) - .with_stencil_buffer(0) - .with_vsync(true) - .build_windowed(window_builder, event_loop) - .unwrap() - .make_current() - .unwrap() - }; - - let gl = unsafe { glow::Context::from_loader_function(|s| gl_window.get_proc_address(s)) }; - - unsafe { - use glow::HasContext as _; - gl.enable(glow::FRAMEBUFFER_SRGB); - } - - (gl_window, gl) -} - fn main() { let mut clear_color = [0.1, 0.1, 0.1]; @@ -116,3 +80,39 @@ fn main() { } }); } + +fn create_display( + event_loop: &glutin::event_loop::EventLoop<()>, +) -> ( + glutin::WindowedContext, + glow::Context, +) { + let window_builder = glutin::window::WindowBuilder::new() + .with_resizable(true) + .with_inner_size(glutin::dpi::LogicalSize { + width: 800.0, + height: 600.0, + }) + .with_title("egui_glow example"); + + let gl_window = unsafe { + glutin::ContextBuilder::new() + .with_depth_buffer(0) + .with_srgb(true) + .with_stencil_buffer(0) + .with_vsync(true) + .build_windowed(window_builder, event_loop) + .unwrap() + .make_current() + .unwrap() + }; + + let gl = unsafe { glow::Context::from_loader_function(|s| gl_window.get_proc_address(s)) }; + + unsafe { + use glow::HasContext as _; + gl.enable(glow::FRAMEBUFFER_SRGB); + } + + (gl_window, gl) +} diff --git a/egui_glow/src/epi_backend.rs b/egui_glow/src/epi_backend.rs index bef12fad2..32705bf25 100644 --- a/egui_glow/src/epi_backend.rs +++ b/egui_glow/src/epi_backend.rs @@ -39,24 +39,23 @@ pub use epi::NativeOptions; /// Run an egui app #[allow(unsafe_code)] -pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { - let persistence = egui_winit::epi::Persistence::from_app_name(app.name()); +pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi::AppCreator) -> ! { + let persistence = egui_winit::epi::Persistence::from_app_name(app_name); let window_settings = persistence.load_window_settings(); let window_builder = - egui_winit::epi::window_builder(native_options, &window_settings).with_title(app.name()); + egui_winit::epi::window_builder(native_options, &window_settings).with_title(app_name); let event_loop = winit::event_loop::EventLoop::with_user_event(); let (gl_window, gl) = create_display(window_builder, &event_loop); let gl = std::rc::Rc::new(gl); let mut painter = crate::Painter::new(gl.clone(), None, "") .unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error)); + let mut integration = egui_winit::epi::EpiIntegration::new( "egui_glow", painter.max_texture_side(), gl_window.window(), - &gl, persistence, - app, ); { @@ -66,6 +65,17 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { }); } + let mut app = app_creator(&epi::CreationContext { + egui_ctx: integration.egui_ctx.clone(), + integration_info: integration.frame.info(), + storage: integration.persistence.storage(), + gl: gl.clone(), + }); + + if app.warm_up_enabled() { + integration.warm_up(app.as_mut(), gl_window.window()); + } + let mut is_focused = true; event_loop.run(move |event, _, control_flow| { @@ -84,7 +94,7 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { needs_repaint, textures_delta, shapes, - } = integration.update(gl_window.window()); + } = integration.update(app.as_mut(), gl_window.window()); integration.handle_platform_output(gl_window.window(), platform_output); @@ -92,7 +102,7 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { // paint: { - let color = integration.app.clear_color(); + let color = app.clear_color(); unsafe { use glow::HasContext as _; gl.disable(glow::SCISSOR_TEST); @@ -120,7 +130,7 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { }; } - integration.maybe_autosave(gl_window.window()); + integration.maybe_autosave(app.as_mut(), gl_window.window()); }; match event { @@ -139,7 +149,7 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { gl_window.resize(physical_size); } - integration.on_event(&event); + integration.on_event(app.as_mut(), &event); if integration.should_quit() { *control_flow = winit::event_loop::ControlFlow::Exit; } @@ -147,7 +157,10 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { gl_window.window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead } winit::event::Event::LoopDestroyed => { - integration.on_exit(gl_window.window()); + integration + .persistence + .save(&mut *app, &integration.egui_ctx, gl_window.window()); + app.on_exit(&gl); painter.destroy(); } winit::event::Event::UserEvent(RequestRepaintEvent) => { diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index b85d1a08d..624243502 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -139,7 +139,7 @@ pub struct AppRunner { } impl AppRunner { - pub fn new(canvas_id: &str, app: Box) -> Result { + pub fn new(canvas_id: &str, app_creator: epi::AppCreator) -> Result { let painter = WrappedGlowPainter::new(canvas_id).map_err(JsValue::from)?; let prefer_dark_mode = crate::prefer_dark_mode(); @@ -177,6 +177,13 @@ impl AppRunner { let storage = LocalStorage::default(); + let app = app_creator(&epi::CreationContext { + egui_ctx: egui_ctx.clone(), + integration_info: frame.info(), + storage: Some(&storage), + gl: painter.painter.gl().clone(), + }); + let mut runner = Self { frame, egui_ctx, @@ -194,11 +201,6 @@ impl AppRunner { runner.input.raw.max_texture_side = Some(runner.painter.max_texture_side()); - let gl = runner.painter.painter.gl(); - runner - .app - .setup(&runner.egui_ctx, &runner.frame, Some(&runner.storage), gl); - Ok(runner) } @@ -327,8 +329,8 @@ impl AppRunner { /// Install event listeners to register different input events /// and start running the given app. -pub fn start(canvas_id: &str, app: Box) -> Result { - let mut runner = AppRunner::new(canvas_id, app)?; +pub fn start(canvas_id: &str, app_creator: epi::AppCreator) -> Result { + let mut runner = AppRunner::new(canvas_id, app_creator)?; runner.warm_up()?; start_runner(runner) } diff --git a/epi/src/lib.rs b/epi/src/lib.rs index c618c0d8e..ddc1add07 100644 --- a/epi/src/lib.rs +++ b/epi/src/lib.rs @@ -98,6 +98,30 @@ pub use glow; // Re-export for user convenience use std::sync::{Arc, Mutex}; +/// The is is how your app is created. +/// +/// You can use the [`CreationContext`] to setup egui, restore state, setup OpenGL things, etc. +pub type AppCreator = fn(&CreationContext<'_>) -> Box; + +/// Data that is passed to [`AppCreator`] that can be used to setup and initialize your app. +pub struct CreationContext<'s> { + /// The egui Context. + /// + /// You can use this to customize the look of egui, e.g to call [`egui::Context::set_fonts`], + /// [`egui::Context::set_visuals`] etc. + pub egui_ctx: egui::Context, + + /// Information about the surrounding environment. + pub integration_info: IntegrationInfo, + + /// You can use the storage to restore app state(requires the "persistence" feature). + pub storage: Option<&'s dyn Storage>, + + /// The [`glow::Context`] allows you to initialize OpenGL resources (e.g. shaders) that + /// you might want to use later from a [`egui::PaintCallback`]. + pub gl: std::rc::Rc, +} + // ---------------------------------------------------------------------------- /// Implement this trait to write apps that can be compiled both natively using the [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium) crate, @@ -109,39 +133,20 @@ pub trait App { /// /// The [`egui::Context`] and [`Frame`] can be cloned and saved if you like. /// - /// To force a repaint, call either [`egui::Context::request_repaint`] during the call to `update`, - /// or call [`Frame::request_repaint`] at any time (e.g. from another thread). + /// 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: &Frame); - /// Called exactly once at startup, before any call to [`Self::update`]. - /// - /// Allows you to do setup code, e.g to call [`egui::Context::set_fonts`], - /// [`egui::Context::set_visuals`] etc. - /// - /// Also allows you to restore state, if there is a storage (requires the "persistence" feature). - /// - /// The [`glow::Context`] allows you to initialize OpenGL resources (e.g. shaders) that - /// you might want to use later from a [`egui::PaintCallback`]. - fn setup( - &mut self, - _ctx: &egui::Context, - _frame: &Frame, - _storage: Option<&dyn Storage>, - _gl: &std::rc::Rc, - ) { - } - /// Called on shutdown, and perhaps at regular intervals. Allows you to save state. /// /// Only called when the "persistence" feature is enabled. /// - /// On web the states is stored to "Local Storage". + /// On web the state is stored to "Local Storage". /// On native the path is picked using [`directories_next::ProjectDirs::data_dir`](https://docs.rs/directories-next/2.0.0/directories_next/struct.ProjectDirs.html#method.data_dir) which is: /// * Linux: `/home/UserName/.local/share/APPNAME` /// * macOS: `/Users/UserName/Library/Application Support/APPNAME` /// * Windows: `C:\Users\UserName\AppData\Roaming\APPNAME` /// - /// where `APPNAME` is what is returned by [`Self::name()`]. + /// where `APPNAME` is what is given to `eframe::run_native`. fn save(&mut self, _storage: &mut dyn Storage) {} /// Called before an exit that can be aborted. @@ -156,17 +161,14 @@ pub trait App { true } - /// Called once on shutdown (before or after [`Self::save`]). If you need to abort an exit use - /// [`Self::on_exit_event`] - fn on_exit(&mut self) {} + /// Called once on shutdown, after [`Self::save`]. + /// + /// If you need to abort an exit use [`Self::on_exit_event`]. + fn on_exit(&mut self, _gl: &glow::Context) {} // --------- // Settings: - /// The name of your App, used for the title bar of native windows - /// and the save location of persistence (see [`Self::save`]). - fn name(&self) -> &str; - /// Time between automatic calls to [`Self::save`] fn auto_save_interval(&self) -> std::time::Duration { std::time::Duration::from_secs(30) From c69f39e8697c56d50b1f53199abea9122573353b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 18 Mar 2022 14:23:07 +0100 Subject: [PATCH 03/32] Box the app creator (#1373) --- eframe/examples/confirm_exit.rs | 6 +++++- eframe/examples/custom_3d.rs | 8 +++++--- eframe/examples/custom_font.rs | 8 +++++--- eframe/examples/download_image.rs | 2 +- eframe/examples/file_dialog.rs | 2 +- eframe/examples/hello_world.rs | 6 +++++- eframe/examples/image.rs | 8 +++++--- eframe/examples/svg.rs | 6 +++++- eframe/src/lib.rs | 8 ++++---- egui_demo_app/src/lib.rs | 5 ++++- egui_demo_app/src/main.rs | 8 +++++--- epi/src/lib.rs | 2 +- 12 files changed, 46 insertions(+), 23 deletions(-) diff --git a/eframe/examples/confirm_exit.rs b/eframe/examples/confirm_exit.rs index 1e9035fa4..8bc9bea3b 100644 --- a/eframe/examples/confirm_exit.rs +++ b/eframe/examples/confirm_exit.rs @@ -4,7 +4,11 @@ use eframe::egui; fn main() { let options = eframe::NativeOptions::default(); - eframe::run_native("Confirm exit", options, |_cc| Box::new(MyApp::default())); + eframe::run_native( + "Confirm exit", + options, + Box::new(|_cc| Box::new(MyApp::default())), + ); } #[derive(Default)] diff --git a/eframe/examples/custom_3d.rs b/eframe/examples/custom_3d.rs index 8b14f130d..54f9bf8ed 100644 --- a/eframe/examples/custom_3d.rs +++ b/eframe/examples/custom_3d.rs @@ -15,9 +15,11 @@ use std::sync::Arc; fn main() { let options = eframe::NativeOptions::default(); - eframe::run_native("Custom 3D painting in eframe", options, |cc| { - Box::new(MyApp::new(cc)) - }); + eframe::run_native( + "Custom 3D painting in eframe", + options, + Box::new(|cc| Box::new(MyApp::new(cc))), + ); } struct MyApp { diff --git a/eframe/examples/custom_font.rs b/eframe/examples/custom_font.rs index dc74fe0d0..b1408b008 100644 --- a/eframe/examples/custom_font.rs +++ b/eframe/examples/custom_font.rs @@ -4,9 +4,11 @@ use eframe::egui; fn main() { let options = eframe::NativeOptions::default(); - eframe::run_native("egui example: custom font", options, |cc| { - Box::new(MyApp::new(cc)) - }); + eframe::run_native( + "egui example: custom font", + options, + Box::new(|cc| Box::new(MyApp::new(cc))), + ); } fn setup_custom_fonts(ctx: &egui::Context) { diff --git a/eframe/examples/download_image.rs b/eframe/examples/download_image.rs index f79587624..9cbb51d11 100644 --- a/eframe/examples/download_image.rs +++ b/eframe/examples/download_image.rs @@ -9,7 +9,7 @@ fn main() { eframe::run_native( "Download and show an image with eframe/egui", options, - |_cc| Box::new(MyApp::default()), + Box::new(|_cc| Box::new(MyApp::default())), ); } diff --git a/eframe/examples/file_dialog.rs b/eframe/examples/file_dialog.rs index 66d0f3fc6..1cec45a4e 100644 --- a/eframe/examples/file_dialog.rs +++ b/eframe/examples/file_dialog.rs @@ -10,7 +10,7 @@ fn main() { eframe::run_native( "Native file dialogs and drag-and-drop files", options, - |_cc| Box::new(MyApp::default()), + Box::new(|_cc| Box::new(MyApp::default())), ); } diff --git a/eframe/examples/hello_world.rs b/eframe/examples/hello_world.rs index d5a9576f0..93156c735 100644 --- a/eframe/examples/hello_world.rs +++ b/eframe/examples/hello_world.rs @@ -4,7 +4,11 @@ use eframe::egui; fn main() { let options = eframe::NativeOptions::default(); - eframe::run_native("My egui App", options, |_cc| Box::new(MyApp::default())); + eframe::run_native( + "My egui App", + options, + Box::new(|_cc| Box::new(MyApp::default())), + ); } struct MyApp { diff --git a/eframe/examples/image.rs b/eframe/examples/image.rs index c91622f39..5b890220c 100644 --- a/eframe/examples/image.rs +++ b/eframe/examples/image.rs @@ -5,9 +5,11 @@ use egui_extras::RetainedImage; fn main() { let options = eframe::NativeOptions::default(); - eframe::run_native("Show an image with eframe/egui", options, |_cc| { - Box::new(MyApp::default()) - }); + eframe::run_native( + "Show an image with eframe/egui", + options, + Box::new(|_cc| Box::new(MyApp::default())), + ); } struct MyApp { diff --git a/eframe/examples/svg.rs b/eframe/examples/svg.rs index a4e71bf74..fd3f32f7e 100644 --- a/eframe/examples/svg.rs +++ b/eframe/examples/svg.rs @@ -11,7 +11,11 @@ fn main() { initial_window_size: Some(egui::vec2(1000.0, 700.0)), ..Default::default() }; - eframe::run_native("svg example", options, |_cc| Box::new(MyApp::default())); + eframe::run_native( + "svg example", + options, + Box::new(|_cc| Box::new(MyApp::default())), + ); } struct MyApp { diff --git a/eframe/src/lib.rs b/eframe/src/lib.rs index ffa4f568e..6c7e7f8df 100644 --- a/eframe/src/lib.rs +++ b/eframe/src/lib.rs @@ -20,7 +20,7 @@ //! //! fn main() { //! let native_options = eframe::NativeOptions::default(); -//! eframe::run_native("My egui App", native_options, |cc| Box::new(MyEguiApp::new(cc))); +//! eframe::run_native("My egui App", native_options, Box::new(|cc| Box::new(MyEguiApp::new(cc)))); //! } //! //! #[derive(Default)] @@ -54,7 +54,7 @@ //! #[cfg(target_arch = "wasm32")] //! #[wasm_bindgen] //! pub fn start(canvas_id: &str) -> Result<(), eframe::wasm_bindgen::JsValue> { -//! eframe::start_web(canvas_id, |cc| Box::new(MyApp::new(cc))) +//! eframe::start_web(canvas_id, Box::new(|cc| Box::new(MyApp::new(cc)))) //! } //! ``` @@ -99,7 +99,7 @@ pub use egui_web::wasm_bindgen; /// #[cfg(target_arch = "wasm32")] /// #[wasm_bindgen] /// pub fn start(canvas_id: &str) -> Result<(), eframe::wasm_bindgen::JsValue> { -/// eframe::start_web(canvas_id, |cc| Box::new(MyEguiApp::new(cc))) +/// eframe::start_web(canvas_id, Box::new(|cc| Box::new(MyEguiApp::new(cc)))) /// } /// ``` #[cfg(target_arch = "wasm32")] @@ -122,7 +122,7 @@ pub fn start_web(canvas_id: &str, app_creator: AppCreator) -> Result<(), wasm_bi /// /// fn main() { /// let native_options = eframe::NativeOptions::default(); -/// eframe::run_native("MyApp", native_options, |cc| Box::new(MyEguiApp::new(cc))); +/// eframe::run_native("MyApp", native_options, Box::new(|cc| Box::new(MyEguiApp::new(cc)))); /// } /// /// #[derive(Default)] diff --git a/egui_demo_app/src/lib.rs b/egui_demo_app/src/lib.rs index 033aaa155..8d7993356 100644 --- a/egui_demo_app/src/lib.rs +++ b/egui_demo_app/src/lib.rs @@ -19,5 +19,8 @@ pub fn start(canvas_id: &str) -> Result<(), wasm_bindgen::JsValue> { // Redirect tracing to console.log and friends: tracing_wasm::set_as_global_default(); - eframe::start_web(canvas_id, |cc| Box::new(egui_demo_lib::WrapApp::new(cc))) + eframe::start_web( + canvas_id, + Box::new(|cc| Box::new(egui_demo_lib::WrapApp::new(cc))), + ) } diff --git a/egui_demo_app/src/main.rs b/egui_demo_app/src/main.rs index 30425599b..43d701032 100644 --- a/egui_demo_app/src/main.rs +++ b/egui_demo_app/src/main.rs @@ -16,7 +16,9 @@ fn main() { drag_and_drop_support: true, ..Default::default() }; - eframe::run_native("egui demo app", options, |cc| { - Box::new(egui_demo_lib::WrapApp::new(cc)) - }); + eframe::run_native( + "egui demo app", + options, + Box::new(|cc| Box::new(egui_demo_lib::WrapApp::new(cc))), + ); } diff --git a/epi/src/lib.rs b/epi/src/lib.rs index ddc1add07..005b132ca 100644 --- a/epi/src/lib.rs +++ b/epi/src/lib.rs @@ -101,7 +101,7 @@ use std::sync::{Arc, Mutex}; /// The is is how your app is created. /// /// You can use the [`CreationContext`] to setup egui, restore state, setup OpenGL things, etc. -pub type AppCreator = fn(&CreationContext<'_>) -> Box; +pub type AppCreator = Box) -> Box>; /// Data that is passed to [`AppCreator`] that can be used to setup and initialize your app. pub struct CreationContext<'s> { From cecb48af0340c665d1a70ba0256c172d70a83bf1 Mon Sep 17 00:00:00 2001 From: zam-5 <90490360+zam-5@users.noreply.github.com> Date: Sat, 19 Mar 2022 08:00:18 -0400 Subject: [PATCH 04/32] Added plot_ui::plot_clicked() (#1372) --- egui/src/widgets/plot/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/egui/src/widgets/plot/mod.rs b/egui/src/widgets/plot/mod.rs index c0254c125..3885fc117 100644 --- a/egui/src/widgets/plot/mod.rs +++ b/egui/src/widgets/plot/mod.rs @@ -767,6 +767,11 @@ impl PlotUi { self.response.hovered() } + /// Returns `true` if the plot was clicked by the primary button. + pub fn plot_clicked(&self) -> bool { + self.response.clicked() + } + /// The pointer position in plot coordinates. Independent of whether the pointer is in the plot area. pub fn pointer_coordinate(&self) -> Option { // We need to subtract the drag delta to keep in sync with the frame-delayed screen transform: From 0d47e577752d39bccec96ca11b0efb918bc0c0e6 Mon Sep 17 00:00:00 2001 From: jean-airoldie <25088801+jean-airoldie@users.noreply.github.com> Date: Sat, 19 Mar 2022 08:01:33 -0400 Subject: [PATCH 05/32] Add Tessellator::set_clip_rect (#1369) This allows the user to set the outer rectangle used for culling, which is required to be able to implement its own tessellate_shapes. --- epaint/src/tessellator.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/epaint/src/tessellator.rs b/epaint/src/tessellator.rs index f46af30dc..95fde7f80 100644 --- a/epaint/src/tessellator.rs +++ b/epaint/src/tessellator.rs @@ -695,6 +695,11 @@ impl Tessellator { } } + /// Set the `Rect` to use for culling. + pub fn set_clip_rect(&mut self, clip_rect: Rect) { + self.clip_rect = clip_rect; + } + /// Tessellate a single [`Shape`] into a [`Mesh`]. /// /// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles). From f6af7bda27555b111fe551ba120be326e91e7422 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 19 Mar 2022 13:30:29 +0100 Subject: [PATCH 06/32] Better error message when trying to upload too large texture Closes https://github.com/emilk/egui/issues/1370 --- egui_glow/src/painter.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/egui_glow/src/painter.rs b/egui_glow/src/painter.rs index 8e68836c0..db24433be 100644 --- a/egui_glow/src/painter.rs +++ b/egui_glow/src/painter.rs @@ -485,7 +485,20 @@ impl Painter { fn upload_texture_srgb(&mut self, pos: Option<[usize; 2]>, [w, h]: [usize; 2], data: &[u8]) { assert_eq!(data.len(), w * h * 4); - assert!(w >= 1 && h >= 1); + assert!( + w >= 1 && h >= 1, + "Got a texture image of size {}x{}. A texture must at least be one texel wide.", + w, + h + ); + assert!( + w <= self.max_texture_side && h <= self.max_texture_side, + "Got a texture image of size {}x{}, but the maximum supported texture side is only {}", + w, + h, + self.max_texture_side + ); + unsafe { self.gl.tex_parameter_i32( glow::TEXTURE_2D, From 12c31e980b91b0c34fe1f7b99d0f824d578c9981 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 19 Mar 2022 13:30:43 +0100 Subject: [PATCH 07/32] Add Ui::push_id (#1374) --- CHANGELOG.md | 3 ++- egui/src/ui.rs | 25 +++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccc18f40c..6678da2cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,10 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w ## Unreleased ### Added ⭐ -* Add `Shape::Callback` for backend-specific painting ([#1351](https://github.com/emilk/egui/pull/1351)). +* Added `Shape::Callback` for backend-specific painting ([#1351](https://github.com/emilk/egui/pull/1351)). * Added `Frame::canvas` ([#1362](https://github.com/emilk/egui/pull/1362)). * `Context::request_repaint` will wake up UI thread, if integrations has called `Context::set_request_repaint_callback` ([#1366](https://github.com/emilk/egui/pull/1366)). +* Added `Ui::push_id` ([#1374](https://github.com/emilk/egui/pull/1374)). ### Changed 🔧 * `ClippedMesh` has been replaced with `ClippedPrimitive` ([#1351](https://github.com/emilk/egui/pull/1351)). diff --git a/egui/src/ui.rs b/egui/src/ui.rs index eb4425934..ae4a96011 100644 --- a/egui/src/ui.rs +++ b/egui/src/ui.rs @@ -1546,6 +1546,26 @@ impl Ui { crate::Frame::group(self.style()).show(self, add_contents) } + /// Create a child Ui with an explicit [`Id`]. + /// + /// ``` + /// # egui::__run_test_ui(|ui| { + /// for i in 0..10 { + /// // `ui.make_persistent_id("foo")` here will produce the same id each loop. + /// ui.push_id(i, |ui| { + /// // `ui.make_persistent_id("foo")` here will produce different id:s + /// }); + /// } + /// # }); + /// ``` + pub fn push_id( + &mut self, + id_source: impl Hash, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> InnerResponse { + self.scope_dyn(Box::new(add_contents), Id::new(id_source)) + } + /// Create a scoped child ui. /// /// You can use this to temporarily change the [`Style`] of a sub-region, for instance: @@ -1559,16 +1579,17 @@ impl Ui { /// # }); /// ``` pub fn scope(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse { - self.scope_dyn(Box::new(add_contents)) + self.scope_dyn(Box::new(add_contents), Id::new("child")) } fn scope_dyn<'c, R>( &mut self, add_contents: Box R + 'c>, + id_source: Id, ) -> InnerResponse { let child_rect = self.available_rect_before_wrap(); let next_auto_id_source = self.next_auto_id_source; - let mut child_ui = self.child_ui(child_rect, *self.layout()); + let mut child_ui = self.child_ui_with_id_source(child_rect, *self.layout(), id_source); self.next_auto_id_source = next_auto_id_source; // HACK: we want `scope` to only increment this once, so that `ui.scope` is equivalent to `ui.allocate_space`. let ret = add_contents(&mut child_ui); let response = self.allocate_rect(child_ui.min_rect(), Sense::hover()); From 6ce859435160d9c155b97d6728f91806cd39ca71 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 19 Mar 2022 13:34:08 +0100 Subject: [PATCH 08/32] README.md: add links to license files Closes https://github.com/emilk/egui/issues/1367 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 76f53ca82..2b058698f 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ [![Documentation](https://docs.rs/egui/badge.svg)](https://docs.rs/egui) [![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) [![Build Status](https://github.com/emilk/egui/workflows/CI/badge.svg)](https://github.com/emilk/egui/actions?workflow=CI) -![MIT](https://img.shields.io/badge/license-MIT-blue.svg) -![Apache](https://img.shields.io/badge/license-Apache-blue.svg) +[![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/emilk/egui/blob/master/LICENSE-MIT) +[![Apache](https://img.shields.io/badge/license-Apache-blue.svg)](https://github.com/emilk/egui/blob/master/LICENSE-APACHE) [![Discord](https://img.shields.io/discord/900275882684477440?label=egui%20discord)](https://discord.gg/JFcEma9bJq) 👉 [Click to run the web demo](https://www.egui.rs/#demo) 👈 From 465c96122cf6aea72e7fbd8d6e7c2c048e88424f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 19 Mar 2022 13:47:30 +0100 Subject: [PATCH 09/32] egui_web: by default, use full web browser size (#1378) * egui_web: by default, use full web browser size Closes https://github.com/emilk/egui/issues/1377 * Remove max_size_points from demo app --- eframe/CHANGELOG.md | 3 ++- eframe/src/lib.rs | 4 ---- egui_demo_lib/src/backend_panel.rs | 17 ----------------- egui_demo_lib/src/wrap_app.rs | 4 ---- egui_web/CHANGELOG.md | 3 ++- egui_web/src/lib.rs | 5 ----- epi/src/lib.rs | 9 ++++----- 7 files changed, 8 insertions(+), 37 deletions(-) diff --git a/eframe/CHANGELOG.md b/eframe/CHANGELOG.md index b7fb979da..a03be5deb 100644 --- a/eframe/CHANGELOG.md +++ b/eframe/CHANGELOG.md @@ -5,9 +5,10 @@ NOTE: [`egui_web`](../egui_web/CHANGELOG.md), [`egui-winit`](../egui-winit/CHANG ## Unreleased -* Remove the `egui_glium` feature. `eframe` will now always use `egui_glow` as the native backend ([#1357](https://github.com/emilk/egui/pull/1357)). * Change default for `NativeOptions::drag_and_drop_support` to `true` ([#1329](https://github.com/emilk/egui/pull/1329)). +* Remove the `egui_glium` feature. `eframe` will now always use `egui_glow` as the native backend ([#1357](https://github.com/emilk/egui/pull/1357)). * Removed `Frame::request_repaint` - just call `egui::Context::request_repaint` for the same effect ([#1366](https://github.com/emilk/egui/pull/1366)). +* Use full browser width by default ([#1378](https://github.com/emilk/egui/pull/1378)). ## 0.17.0 - 2022-02-22 diff --git a/eframe/src/lib.rs b/eframe/src/lib.rs index 6c7e7f8df..732d60a11 100644 --- a/eframe/src/lib.rs +++ b/eframe/src/lib.rs @@ -84,10 +84,6 @@ pub use egui_web::wasm_bindgen; /// Install event listeners to register different input events /// and start running the given app. /// -/// For performance reasons (on some browsers) the egui canvas does not, by default, -/// fill the whole width of the browser. -/// This can be changed by overriding [`epi::Frame::max_size_points`]. -/// /// ``` no_run /// #[cfg(target_arch = "wasm32")] /// use wasm_bindgen::prelude::*; diff --git a/egui_demo_lib/src/backend_panel.rs b/egui_demo_lib/src/backend_panel.rs index e54b97d1d..0daa3a45a 100644 --- a/egui_demo_lib/src/backend_panel.rs +++ b/egui_demo_lib/src/backend_panel.rs @@ -54,10 +54,6 @@ pub struct BackendPanel { #[cfg_attr(feature = "serde", serde(skip))] pixels_per_point: Option, - /// maximum size of the web browser canvas - max_size_points_ui: egui::Vec2, - pub max_size_points_active: egui::Vec2, - #[cfg_attr(feature = "serde", serde(skip))] frame_history: crate::frame_history::FrameHistory, @@ -70,8 +66,6 @@ impl Default for BackendPanel { open: false, run_mode: Default::default(), pixels_per_point: Default::default(), - max_size_points_ui: egui::Vec2::new(1024.0, 2048.0), - max_size_points_active: egui::Vec2::new(1024.0, 2048.0), frame_history: Default::default(), egui_windows: Default::default(), } @@ -157,17 +151,6 @@ impl BackendPanel { ui.hyperlink("https://github.com/emilk/egui"); ui.separator(); - - ui.add( - egui::Slider::new(&mut self.max_size_points_ui.x, 512.0..=f32::INFINITY) - .logarithmic(true) - .largest_finite(8192.0) - .text("Max width"), - ) - .on_hover_text("Maximum width of the egui region of the web page."); - if !ui.ctx().is_using_pointer() { - self.max_size_points_active = self.max_size_points_ui; - } } show_integration_name(ui, &frame.info()); diff --git a/egui_demo_lib/src/wrap_app.rs b/egui_demo_lib/src/wrap_app.rs index d8d701d3f..e80240e47 100644 --- a/egui_demo_lib/src/wrap_app.rs +++ b/egui_demo_lib/src/wrap_app.rs @@ -65,10 +65,6 @@ impl epi::App for WrapApp { epi::set_value(storage, epi::APP_KEY, self); } - fn max_size_points(&self) -> egui::Vec2 { - self.backend_panel.max_size_points_active - } - fn clear_color(&self) -> egui::Rgba { egui::Rgba::TRANSPARENT // we set a `CentralPanel` fill color in `demo_windows.rs` } diff --git a/egui_web/CHANGELOG.md b/egui_web/CHANGELOG.md index 91bee2c73..ecffcb7f9 100644 --- a/egui_web/CHANGELOG.md +++ b/egui_web/CHANGELOG.md @@ -3,8 +3,9 @@ All notable changes to the `egui_web` integration will be noted in this file. ## Unreleased -* egui code will no longer be called after panic ([#1306](https://github.com/emilk/egui/pull/1306)) +* egui code will no longer be called after panic ([#1306](https://github.com/emilk/egui/pull/1306)). * Remove the "webgl" feature. `egui_web` now always use `glow` (which in turn wraps WebGL) ([#1356](https://github.com/emilk/egui/pull/1356)). +* Use full browser width by default ([#1378](https://github.com/emilk/egui/pull/1378)). ## 0.17.0 - 2022-02-22 diff --git a/egui_web/src/lib.rs b/egui_web/src/lib.rs index 062c9ac0d..8bddd04a3 100644 --- a/egui_web/src/lib.rs +++ b/egui_web/src/lib.rs @@ -3,11 +3,6 @@ //! This library is an [`epi`] backend. //! //! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead. -//! -//! ## Specifying the size of the egui canvas -//! For performance reasons (on some browsers) the egui canvas does not, by default, -//! fill the whole width of the browser. -//! This can be changed by overriding [`epi::App::max_size_points`]. // Forbid warnings in release builds: #![cfg_attr(not(debug_assertions), deny(warnings))] diff --git a/epi/src/lib.rs b/epi/src/lib.rs index 005b132ca..d1bda8d06 100644 --- a/epi/src/lib.rs +++ b/epi/src/lib.rs @@ -176,13 +176,12 @@ pub trait App { /// The size limit of the web app canvas. /// - /// By default the size if limited to 1024x2048. + /// By default the max size is [`egui::Vec2::INFINITY`], i.e. unlimited. /// - /// A larger canvas can lead to bad frame rates on some browsers on some platforms. - /// In particular, Firefox on Mac and Linux is really bad at handling large WebGL canvases: - /// (unfixed since 2014). + /// A large canvas can lead to bad frame rates on some older browsers on some platforms + /// (see ). fn max_size_points(&self) -> egui::Vec2 { - egui::Vec2::new(1024.0, 2048.0) + egui::Vec2::INFINITY } /// Background color for the app, e.g. what is sent to `gl.clearColor`. From 8bb381d50bf181c4ca34ae1d92176e2672fd2b6e Mon Sep 17 00:00:00 2001 From: Zachary Kohnen Date: Sun, 20 Mar 2022 20:30:38 +0100 Subject: [PATCH 10/32] Fix code that could lead to a possible deadlock. (#1380) * Fix code that could lead to a possible deadlock. Drop implementations are not called until the end of a statement. The statement changed in this commit therefore took 4 read locks on a RwLock which can lead to problems if a write lock is requested between any of these read locks. The code looks like it would only hold one lock at a time but it does not drop any of the locks until after the arithmatic operations complete, which leads to this situation. See https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=996079046184329f3a9df1cd19c87da8 to see this in action. The fix is to just take one lock and share it between the three calls to num_presses, letting it drop at the end of the scope. * Fix code that may cause a deadlock in `MenuRoot::stationary_interaction` The issue here is related to that in 9673b8f2a08302c10ffcfd063f2dbdec4301d3e2 in that the lock is not dropped when it is expected. Since the `RwLockReadGuard` produced by `ctx.input()` has a reference taken from it (one into `pointer`), the lock cannot be dropped until that reference is no longre valid, which is the end of the scope (in this case this function). The following `ctx.input()` then attempts to aquire a second lock on the `RwLock`, creating the same situation that was found in the referenced commit. This has been resolved by holding one lock on the input for the whole function. * Reference this PR from comments in the code for future maintainers * Add the change to the changelog * Use full link to PR * Use full link to PR Co-authored-by: Emil Ernerfeldt --- CHANGELOG.md | 2 +- egui/src/menu.rs | 12 ++++++------ egui/src/widgets/slider.rs | 12 ++++++++---- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6678da2cb..3612db709 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w ### Fixed 🐛 * Fixed ComboBoxes always being rendered left-aligned ([#1304](https://github.com/emilk/egui/pull/1304)). - +* Fixed ui code that could lead to a deadlock ([#1380](https://github.com/emilk/egui/pull/1380)) ## 0.17.0 - 2022-02-22 - Improved font selection and image handling diff --git a/egui/src/menu.rs b/egui/src/menu.rs index 46146821a..1906150d0 100644 --- a/egui/src/menu.rs +++ b/egui/src/menu.rs @@ -277,10 +277,10 @@ impl MenuRoot { root: &mut MenuRootManager, id: Id, ) -> MenuResponse { - let pointer = &response.ctx.input().pointer; - if (response.clicked() && root.is_menu_open(id)) - || response.ctx.input().key_pressed(Key::Escape) - { + // Lock the input once for the whole function call (see https://github.com/emilk/egui/pull/1380). + let input = response.ctx.input(); + + if (response.clicked() && root.is_menu_open(id)) || input.key_pressed(Key::Escape) { // menu open and button clicked or esc pressed return MenuResponse::Close; } else if (response.clicked() && !root.is_menu_open(id)) @@ -290,8 +290,8 @@ impl MenuRoot { // or button hovered while other menu is open let pos = response.rect.left_bottom(); return MenuResponse::Create(pos, id); - } else if pointer.any_pressed() && pointer.primary_down() { - if let Some(pos) = pointer.interact_pos() { + } else if input.pointer.any_pressed() && input.pointer.primary_down() { + if let Some(pos) = input.pointer.interact_pos() { if let Some(root) = root.inner.as_mut() { if root.id == id { // pressed somewhere while this menu is open diff --git a/egui/src/widgets/slider.rs b/egui/src/widgets/slider.rs index 23480abee..c24a5fb9c 100644 --- a/egui/src/widgets/slider.rs +++ b/egui/src/widgets/slider.rs @@ -455,10 +455,14 @@ impl<'a> Slider<'a> { fn value_ui(&mut self, ui: &mut Ui, position_range: RangeInclusive) -> Response { // If `DragValue` is controlled from the keyboard and `step` is defined, set speed to `step` - let change = ui.input().num_presses(Key::ArrowUp) as i32 - + ui.input().num_presses(Key::ArrowRight) as i32 - - ui.input().num_presses(Key::ArrowDown) as i32 - - ui.input().num_presses(Key::ArrowLeft) as i32; + let change = { + // Hold one lock rather than 4 (see https://github.com/emilk/egui/pull/1380). + let input = ui.input(); + + input.num_presses(Key::ArrowUp) as i32 + input.num_presses(Key::ArrowRight) as i32 + - input.num_presses(Key::ArrowDown) as i32 + - input.num_presses(Key::ArrowLeft) as i32 + }; let speed = match self.step { Some(step) if change != 0 => step, _ => self.current_gradient(&position_range), From 734d4c57adb8f69308fb8ba609e33a6eceeda53c Mon Sep 17 00:00:00 2001 From: jean-airoldie <25088801+jean-airoldie@users.noreply.github.com> Date: Sun, 20 Mar 2022 15:38:48 -0400 Subject: [PATCH 11/32] Expose more epaint tessellator methods (#1384) * Expose more tessellator method * Make public the Tessellator methods to tessellate a circle, a mesh, a rectangle, a line, a path, a quadratic and cubic bezier curve. * Add doc to tessellate_text. * Add Mesh::append_ref method. * Make tessellate_text take a reference * Fix breaking change in benchmark --- egui_demo_lib/benches/benchmark.rs | 2 +- epaint/src/mesh.rs | 22 +- epaint/src/tessellator.rs | 486 ++++++++++++++++------------- 3 files changed, 291 insertions(+), 219 deletions(-) diff --git a/egui_demo_lib/benches/benchmark.rs b/egui_demo_lib/benches/benchmark.rs index 4fdeb7301..9b988b3fb 100644 --- a/egui_demo_lib/benches/benchmark.rs +++ b/egui_demo_lib/benches/benchmark.rs @@ -129,7 +129,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { let font_image_size = fonts.font_image_size(); c.bench_function("tessellate_text", |b| { b.iter(|| { - tessellator.tessellate_text(font_image_size, text_shape.clone(), &mut mesh); + tessellator.tessellate_text(font_image_size, &text_shape, &mut mesh); mesh.clear(); }) }); diff --git a/epaint/src/mesh.rs b/epaint/src/mesh.rs index 800e6120e..7a5e0dcdb 100644 --- a/epaint/src/mesh.rs +++ b/epaint/src/mesh.rs @@ -91,16 +91,28 @@ impl Mesh { if self.is_empty() { *self = other; } else { + self.append_ref(&other); + } + } + + /// Append all the indices and vertices of `other` to `self` without + /// taking ownership. + pub fn append_ref(&mut self, other: &Mesh) { + crate::epaint_assert!(other.is_valid()); + + if !self.is_empty() { assert_eq!( self.texture_id, other.texture_id, "Can't merge Mesh using different textures" ); - - let index_offset = self.vertices.len() as u32; - self.indices - .extend(other.indices.iter().map(|index| index + index_offset)); - self.vertices.extend(other.vertices.iter()); + } else { + self.texture_id = other.texture_id; } + + let index_offset = self.vertices.len() as u32; + self.indices + .extend(other.indices.iter().map(|index| index + index_offset)); + self.vertices.extend(other.vertices.iter()); } #[inline(always)] diff --git a/epaint/src/tessellator.rs b/epaint/src/tessellator.rs index 95fde7f80..440f356e6 100644 --- a/epaint/src/tessellator.rs +++ b/epaint/src/tessellator.rs @@ -706,9 +706,6 @@ impl Tessellator { /// * `shape`: the shape to tessellate. /// * `out`: triangles are appended to this. pub fn tessellate_shape(&mut self, tex_size: [usize; 2], shape: Shape, out: &mut Mesh) { - let clip_rect = self.clip_rect; - let options = &self.options; - match shape { Shape::Noop => {} Shape::Vec(vec) => { @@ -716,26 +713,8 @@ impl Tessellator { self.tessellate_shape(tex_size, shape, out); } } - Shape::Circle(CircleShape { - center, - radius, - fill, - stroke, - }) => { - if radius <= 0.0 { - return; - } - - if options.coarse_tessellation_culling - && !clip_rect.expand(radius + stroke.width).contains(center) - { - return; - } - - self.scratchpad_path.clear(); - self.scratchpad_path.add_circle(center, radius); - self.scratchpad_path.fill(fill, options, out); - self.scratchpad_path.stroke_closed(stroke, options, out); + Shape::Circle(circle) => { + self.tessellate_circle(circle, out); } Shape::Mesh(mesh) => { if !mesh.is_valid() { @@ -743,44 +722,29 @@ impl Tessellator { return; } - if options.coarse_tessellation_culling && !clip_rect.intersects(mesh.calc_bounds()) + if self.options.coarse_tessellation_culling + && !self.clip_rect.intersects(mesh.calc_bounds()) { return; } - out.append(mesh); } - Shape::LineSegment { points, stroke } => { - if stroke.is_empty() { - return; - } - - if options.coarse_tessellation_culling - && !clip_rect - .intersects(Rect::from_two_pos(points[0], points[1]).expand(stroke.width)) - { - return; - } - - self.scratchpad_path.clear(); - self.scratchpad_path.add_line_segment(points); - self.scratchpad_path.stroke_open(stroke, options, out); - } + Shape::LineSegment { points, stroke } => self.tessellate_line(points, stroke, out), Shape::Path(path_shape) => { - self.tessellate_path(path_shape, out); + self.tessellate_path(&path_shape, out); } Shape::Rect(rect_shape) => { self.tessellate_rect(&rect_shape, out); } Shape::Text(text_shape) => { - if options.debug_paint_text_rects { + if self.options.debug_paint_text_rects { let rect = text_shape.galley.rect.translate(text_shape.pos.to_vec2()); self.tessellate_rect( &RectShape::stroke(rect.expand(0.5), 2.0, (0.5, Color32::GREEN)), out, ); } - self.tessellate_text(tex_size, text_shape, out); + self.tessellate_text(tex_size, &text_shape, out); } Shape::QuadraticBezier(quadratic_shape) => { self.tessellate_quadratic_bezier(quadratic_shape, out); @@ -792,7 +756,266 @@ impl Tessellator { } } - pub(crate) fn tessellate_quadratic_bezier( + /// Tessellate a single [`CircleShape`] into a [`Mesh`]. + /// + /// * `shape`: the circle to tessellate. + /// * `out`: triangles are appended to this. + pub fn tessellate_circle(&mut self, shape: CircleShape, out: &mut Mesh) { + let CircleShape { + center, + radius, + fill, + stroke, + } = shape; + + if radius <= 0.0 { + return; + } + + if self.options.coarse_tessellation_culling + && !self + .clip_rect + .expand(radius + stroke.width) + .contains(center) + { + return; + } + + self.scratchpad_path.clear(); + self.scratchpad_path.add_circle(center, radius); + self.scratchpad_path.fill(fill, &self.options, out); + self.scratchpad_path + .stroke_closed(stroke, &self.options, out); + } + + /// Tessellate a single [`Mesh`] into a [`Mesh`]. + /// + /// * `mesh`: the mesh to tessellate. + /// * `out`: triangles are appended to this. + pub fn tessellate_mesh(&mut self, mesh: &Mesh, out: &mut Mesh) { + if !mesh.is_valid() { + crate::epaint_assert!(false, "Invalid Mesh in Shape::Mesh"); + return; + } + + if self.options.coarse_tessellation_culling + && !self.clip_rect.intersects(mesh.calc_bounds()) + { + return; + } + + out.append_ref(mesh); + } + + /// Tessellate a line segment between the two points with the given stoken into a [`Mesh`]. + /// + /// * `shape`: the mesh to tessellate. + /// * `out`: triangles are appended to this. + pub fn tessellate_line(&mut self, points: [Pos2; 2], stroke: Stroke, out: &mut Mesh) { + if stroke.is_empty() { + return; + } + + if self.options.coarse_tessellation_culling + && !self + .clip_rect + .intersects(Rect::from_two_pos(points[0], points[1]).expand(stroke.width)) + { + return; + } + + self.scratchpad_path.clear(); + self.scratchpad_path.add_line_segment(points); + self.scratchpad_path.stroke_open(stroke, &self.options, out); + } + + /// Tessellate a single [`PathShape`] into a [`Mesh`]. + /// + /// * `path_shape`: the path to tessellate. + /// * `out`: triangles are appended to this. + pub fn tessellate_path(&mut self, path_shape: &PathShape, out: &mut Mesh) { + if path_shape.points.len() < 2 { + return; + } + + if self.options.coarse_tessellation_culling + && !path_shape.visual_bounding_rect().intersects(self.clip_rect) + { + return; + } + + let PathShape { + points, + closed, + fill, + stroke, + } = path_shape; + + self.scratchpad_path.clear(); + if *closed { + self.scratchpad_path.add_line_loop(points); + } else { + self.scratchpad_path.add_open_points(points); + } + + if *fill != Color32::TRANSPARENT { + crate::epaint_assert!( + closed, + "You asked to fill a path that is not closed. That makes no sense." + ); + self.scratchpad_path.fill(*fill, &self.options, out); + } + let typ = if *closed { + PathType::Closed + } else { + PathType::Open + }; + self.scratchpad_path + .stroke(typ, *stroke, &self.options, out); + } + + /// Tessellate a single [`Rect`] into a [`Mesh`]. + /// + /// * `rect`: the rectangle to tessellate. + /// * `out`: triangles are appended to this. + pub fn tessellate_rect(&mut self, rect: &RectShape, out: &mut Mesh) { + let RectShape { + mut rect, + rounding, + fill, + stroke, + } = *rect; + + if self.options.coarse_tessellation_culling + && !rect.expand(stroke.width).intersects(self.clip_rect) + { + return; + } + if rect.is_negative() { + return; + } + + // It is common to (sometimes accidentally) create an infinitely sized rectangle. + // Make sure we can handle that: + rect.min = rect.min.at_least(pos2(-1e7, -1e7)); + rect.max = rect.max.at_most(pos2(1e7, 1e7)); + + let path = &mut self.scratchpad_path; + path.clear(); + path::rounded_rectangle(&mut self.scratchpad_points, rect, rounding); + path.add_line_loop(&self.scratchpad_points); + path.fill(fill, &self.options, out); + path.stroke_closed(stroke, &self.options, out); + } + + /// Tessellate a single [`TextShape`] into a [`Mesh`]. + /// + /// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles). + /// * `text_shape`: the text to tessellate. + /// * `out`: triangles are appended to this. + pub fn tessellate_text( + &mut self, + tex_size: [usize; 2], + text_shape: &TextShape, + out: &mut Mesh, + ) { + let TextShape { + pos: galley_pos, + galley, + underline, + override_text_color, + angle, + } = text_shape; + + if galley.is_empty() { + return; + } + + out.vertices.reserve(galley.num_vertices); + out.indices.reserve(galley.num_indices); + + // The contents of the galley is already snapped to pixel coordinates, + // but we need to make sure the galley ends up on the start of a physical pixel: + let galley_pos = pos2( + self.options.round_to_pixel(galley_pos.x), + self.options.round_to_pixel(galley_pos.y), + ); + + let uv_normalizer = vec2(1.0 / tex_size[0] as f32, 1.0 / tex_size[1] as f32); + + let rotator = Rot2::from_angle(*angle); + + for row in &galley.rows { + if row.visuals.mesh.is_empty() { + continue; + } + + let mut row_rect = row.visuals.mesh_bounds; + if *angle != 0.0 { + row_rect = row_rect.rotate_bb(rotator); + } + row_rect = row_rect.translate(galley_pos.to_vec2()); + + if self.options.coarse_tessellation_culling && !self.clip_rect.intersects(row_rect) { + // culling individual lines of text is important, since a single `Shape::Text` + // can span hundreds of lines. + continue; + } + + let index_offset = out.vertices.len() as u32; + + out.indices.extend( + row.visuals + .mesh + .indices + .iter() + .map(|index| index + index_offset), + ); + + out.vertices.extend( + row.visuals + .mesh + .vertices + .iter() + .enumerate() + .map(|(i, vertex)| { + let Vertex { pos, uv, mut color } = *vertex; + + if let Some(override_text_color) = override_text_color { + if row.visuals.glyph_vertex_range.contains(&i) { + color = *override_text_color; + } + } + + let offset = if *angle == 0.0 { + pos.to_vec2() + } else { + rotator * pos.to_vec2() + }; + + Vertex { + pos: galley_pos + offset, + uv: (uv.to_vec2() * uv_normalizer).to_pos2(), + color, + } + }), + ); + + if *underline != Stroke::none() { + self.scratchpad_path.clear(); + self.scratchpad_path + .add_line_segment([row_rect.left_bottom(), row_rect.right_bottom()]); + self.scratchpad_path + .stroke_open(*underline, &self.options, out); + } + } + } + + /// Tessellate a single [`QuadraticBezierShape`] into a [`Mesh`]. + /// + /// * `quadratic_shape`: the shape to tessellate. + /// * `out`: triangles are appended to this. + pub fn tessellate_quadratic_bezier( &mut self, quadratic_shape: QuadraticBezierShape, out: &mut Mesh, @@ -817,11 +1040,11 @@ impl Tessellator { ); } - pub(crate) fn tessellate_cubic_bezier( - &mut self, - cubic_shape: CubicBezierShape, - out: &mut Mesh, - ) { + /// Tessellate a single [`CubicBezierShape`] into a [`Mesh`]. + /// + /// * `cubic_shape`: the shape to tessellate. + /// * `out`: triangles are appended to this. + pub fn tessellate_cubic_bezier(&mut self, cubic_shape: CubicBezierShape, out: &mut Mesh) { let options = &self.options; let clip_rect = self.clip_rect; if options.coarse_tessellation_culling @@ -872,169 +1095,6 @@ impl Tessellator { }; self.scratchpad_path.stroke(typ, stroke, &self.options, out); } - - pub(crate) fn tessellate_path(&mut self, path_shape: PathShape, out: &mut Mesh) { - if path_shape.points.len() < 2 { - return; - } - - if self.options.coarse_tessellation_culling - && !path_shape.visual_bounding_rect().intersects(self.clip_rect) - { - return; - } - - let PathShape { - points, - closed, - fill, - stroke, - } = path_shape; - - self.scratchpad_path.clear(); - if closed { - self.scratchpad_path.add_line_loop(&points); - } else { - self.scratchpad_path.add_open_points(&points); - } - - if fill != Color32::TRANSPARENT { - crate::epaint_assert!( - closed, - "You asked to fill a path that is not closed. That makes no sense." - ); - self.scratchpad_path.fill(fill, &self.options, out); - } - let typ = if closed { - PathType::Closed - } else { - PathType::Open - }; - self.scratchpad_path.stroke(typ, stroke, &self.options, out); - } - - pub(crate) fn tessellate_rect(&mut self, rect: &RectShape, out: &mut Mesh) { - let RectShape { - mut rect, - rounding, - fill, - stroke, - } = *rect; - - if self.options.coarse_tessellation_culling - && !rect.expand(stroke.width).intersects(self.clip_rect) - { - return; - } - if rect.is_negative() { - return; - } - - // It is common to (sometimes accidentally) create an infinitely sized rectangle. - // Make sure we can handle that: - rect.min = rect.min.at_least(pos2(-1e7, -1e7)); - rect.max = rect.max.at_most(pos2(1e7, 1e7)); - - let path = &mut self.scratchpad_path; - path.clear(); - path::rounded_rectangle(&mut self.scratchpad_points, rect, rounding); - path.add_line_loop(&self.scratchpad_points); - path.fill(fill, &self.options, out); - path.stroke_closed(stroke, &self.options, out); - } - - pub fn tessellate_text(&mut self, tex_size: [usize; 2], text_shape: TextShape, out: &mut Mesh) { - let TextShape { - pos: galley_pos, - galley, - underline, - override_text_color, - angle, - } = text_shape; - - if galley.is_empty() { - return; - } - - out.vertices.reserve(galley.num_vertices); - out.indices.reserve(galley.num_indices); - - // The contents of the galley is already snapped to pixel coordinates, - // but we need to make sure the galley ends up on the start of a physical pixel: - let galley_pos = pos2( - self.options.round_to_pixel(galley_pos.x), - self.options.round_to_pixel(galley_pos.y), - ); - - let uv_normalizer = vec2(1.0 / tex_size[0] as f32, 1.0 / tex_size[1] as f32); - - let rotator = Rot2::from_angle(angle); - - for row in &galley.rows { - if row.visuals.mesh.is_empty() { - continue; - } - - let mut row_rect = row.visuals.mesh_bounds; - if angle != 0.0 { - row_rect = row_rect.rotate_bb(rotator); - } - row_rect = row_rect.translate(galley_pos.to_vec2()); - - if self.options.coarse_tessellation_culling && !self.clip_rect.intersects(row_rect) { - // culling individual lines of text is important, since a single `Shape::Text` - // can span hundreds of lines. - continue; - } - - let index_offset = out.vertices.len() as u32; - - out.indices.extend( - row.visuals - .mesh - .indices - .iter() - .map(|index| index + index_offset), - ); - - out.vertices.extend( - row.visuals - .mesh - .vertices - .iter() - .enumerate() - .map(|(i, vertex)| { - let Vertex { pos, uv, mut color } = *vertex; - - if let Some(override_text_color) = override_text_color { - if row.visuals.glyph_vertex_range.contains(&i) { - color = override_text_color; - } - } - - let offset = if angle == 0.0 { - pos.to_vec2() - } else { - rotator * pos.to_vec2() - }; - - Vertex { - pos: galley_pos + offset, - uv: (uv.to_vec2() * uv_normalizer).to_pos2(), - color, - } - }), - ); - - if underline != Stroke::none() { - self.scratchpad_path.clear(); - self.scratchpad_path - .add_line_segment([row_rect.left_bottom(), row_rect.right_bottom()]); - self.scratchpad_path - .stroke_open(underline, &self.options, out); - } - } - } } /// Turns [`Shape`]:s into sets of triangles. From 861e129ace34ada1a39fd843c42537ee50559777 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 20 Mar 2022 20:39:39 +0100 Subject: [PATCH 12/32] Add Shape::image convenience method --- epaint/src/shape.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/epaint/src/shape.rs b/epaint/src/shape.rs index 1d846fb21..57d3cef1a 100644 --- a/epaint/src/shape.rs +++ b/epaint/src/shape.rs @@ -2,7 +2,7 @@ use crate::{ text::{FontId, Fonts, Galley}, - Color32, Mesh, Stroke, + Color32, Mesh, Stroke, TextureId, }; use emath::*; @@ -184,6 +184,12 @@ impl Shape { Self::Mesh(mesh) } + pub fn image(texture_id: TextureId, rect: Rect, uv: Rect, tint: Color32) -> Self { + let mut mesh = Mesh::with_texture(texture_id); + mesh.add_rect_with_uv(rect, uv, tint); + Shape::mesh(mesh) + } + /// The visual bounding rectangle (includes stroke widths) pub fn visual_bounding_rect(&self) -> Rect { match self { From d20be45c4cd9081c49f1934200fb137dad2919d6 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 20 Mar 2022 22:49:44 +0100 Subject: [PATCH 13/32] Add egui_assert to ensure texture size is <= max_texture_side --- egui/src/context.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/egui/src/context.rs b/egui/src/context.rs index 75057f2c7..536461de9 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -685,8 +685,19 @@ impl Context { name: impl Into, image: impl Into, ) -> TextureHandle { + let name = name.into(); + let image = image.into(); + let max_texture_side = self.input().max_texture_side; + crate::egui_assert!( + image.width() <= max_texture_side && image.height() <= max_texture_side, + "Texture {:?} has size {}x{}, but the maximum texture side is {}", + name, + image.width(), + image.height(), + max_texture_side + ); let tex_mngr = self.tex_manager(); - let tex_id = tex_mngr.write().alloc(name.into(), image.into()); + let tex_id = tex_mngr.write().alloc(name, image); TextureHandle::new(tex_mngr, tex_id) } From e369626d3db4e8a6dd55fd10b224a3835b4040f9 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 20 Mar 2022 23:04:44 +0100 Subject: [PATCH 14/32] glow: move where FRAMEBUFFER_SRGB is enabled --- egui_glow/examples/pure_glow.rs | 5 ----- egui_glow/src/epi_backend.rs | 5 ----- egui_glow/src/painter.rs | 5 +++++ 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/egui_glow/examples/pure_glow.rs b/egui_glow/examples/pure_glow.rs index 4b832878c..06abc5c29 100644 --- a/egui_glow/examples/pure_glow.rs +++ b/egui_glow/examples/pure_glow.rs @@ -109,10 +109,5 @@ fn create_display( let gl = unsafe { glow::Context::from_loader_function(|s| gl_window.get_proc_address(s)) }; - unsafe { - use glow::HasContext as _; - gl.enable(glow::FRAMEBUFFER_SRGB); - } - (gl_window, gl) } diff --git a/egui_glow/src/epi_backend.rs b/egui_glow/src/epi_backend.rs index 32705bf25..726116732 100644 --- a/egui_glow/src/epi_backend.rs +++ b/egui_glow/src/epi_backend.rs @@ -25,11 +25,6 @@ fn create_display( let gl = unsafe { glow::Context::from_loader_function(|s| gl_window.get_proc_address(s)) }; - unsafe { - use glow::HasContext as _; - gl.enable(glow::FRAMEBUFFER_SRGB); - } - (gl_window, gl) } diff --git a/egui_glow/src/painter.rs b/egui_glow/src/painter.rs index db24433be..804bc19ed 100644 --- a/egui_glow/src/painter.rs +++ b/egui_glow/src/painter.rs @@ -264,6 +264,11 @@ impl Painter { glow::ONE, ); + if !cfg!(target_arch = "wasm32") { + self.gl.enable(glow::FRAMEBUFFER_SRGB); + check_for_gl_error(&self.gl, "FRAMEBUFFER_SRGB"); + } + let width_in_points = width_in_pixels as f32 / pixels_per_point; let height_in_points = height_in_pixels as f32 / pixels_per_point; From fde9c232b3d6d8c5ea918a537ba7893cd946ff22 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 20 Mar 2022 23:05:16 +0100 Subject: [PATCH 15/32] Improve the introspection paint stats --- egui/src/introspection.rs | 9 ++++++--- epaint/src/stats.rs | 8 ++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/egui/src/introspection.rs b/egui/src/introspection.rs index e5b9c7ea1..7e286d894 100644 --- a/egui/src/introspection.rs +++ b/egui/src/introspection.rs @@ -101,11 +101,14 @@ impl Widget for &epaint::stats::PaintStats { ui.label("Intermediate:"); label(ui, shapes, "shapes").on_hover_text("Boxes, circles, etc"); - label(ui, shape_text, "text (mostly cached)"); + ui.horizontal(|ui| { + label(ui, shape_text, "text"); + ui.small("(mostly cached)"); + }); label(ui, shape_path, "paths"); label(ui, shape_mesh, "nested meshes"); label(ui, shape_vec, "nested shapes"); - ui.label(format!("{} callbacks", num_callbacks)); + ui.label(format!("{:6} callbacks", num_callbacks)); ui.add_space(10.0); ui.label("Text shapes:"); @@ -115,7 +118,7 @@ impl Widget for &epaint::stats::PaintStats { ui.add_space(10.0); ui.label("Tessellated (and culled):"); - label(ui, clipped_primitives, "clipped_primitives") + label(ui, clipped_primitives, "primitives lists") .on_hover_text("Number of separate clip rectangles"); label(ui, vertices, "vertices"); label(ui, indices, "indices").on_hover_text("Three 32-bit indices per triangles"); diff --git a/epaint/src/stats.rs b/epaint/src/stats.rs index 2316d905a..47f3e9058 100644 --- a/epaint/src/stats.rs +++ b/epaint/src/stats.rs @@ -126,17 +126,17 @@ impl AllocInfo { pub fn format(&self, what: &str) -> String { if self.num_allocs() == 0 { - format!("{:6} {:14}", 0, what) + format!("{:6} {:16}", 0, what) } else if self.num_allocs() == 1 { format!( - "{:6} {:14} {} 1 allocation", + "{:6} {:16} {} 1 allocation", self.num_elements, what, self.megabytes() ) } else if self.element_size != ElementSize::Heterogenous { format!( - "{:6} {:14} {} {:3} allocations", + "{:6} {:16} {} {:3} allocations", self.num_elements(), what, self.megabytes(), @@ -144,7 +144,7 @@ impl AllocInfo { ) } else { format!( - "{:6} {:14} {} {:3} allocations", + "{:6} {:16} {} {:3} allocations", "", what, self.megabytes(), From ccbddcfe951e01c55efd0ed19f2f2ab5edfad5d9 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 20 Mar 2022 23:08:19 +0100 Subject: [PATCH 16/32] Add example of how to move text cursor in a TextEdit --- egui/src/lib.rs | 5 +++-- egui_demo_lib/src/apps/demo/text_edit.rs | 25 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/egui/src/lib.rs b/egui/src/lib.rs index e3a604150..c6ab4b0ea 100644 --- a/egui/src/lib.rs +++ b/egui/src/lib.rs @@ -391,9 +391,10 @@ pub use epaint::{ }; pub mod text { + pub use crate::text_edit::CCursorRange; pub use epaint::text::{ - FontData, FontDefinitions, FontFamily, Fonts, Galley, LayoutJob, LayoutSection, TextFormat, - TAB_SIZE, + cursor::CCursor, FontData, FontDefinitions, FontFamily, Fonts, Galley, LayoutJob, + LayoutSection, TextFormat, TAB_SIZE, }; } diff --git a/egui_demo_lib/src/apps/demo/text_edit.rs b/egui_demo_lib/src/apps/demo/text_edit.rs index b294115b2..e62dd17bc 100644 --- a/egui_demo_lib/src/apps/demo/text_edit.rs +++ b/egui_demo_lib/src/apps/demo/text_edit.rs @@ -64,6 +64,7 @@ impl super::View for TextEdit { anything_selected, egui::Label::new("Press ctrl+T to toggle the case of selected text (cmd+T on Mac)"), ); + if ui .input_mut() .consume_key(egui::Modifiers::COMMAND, egui::Key::T) @@ -82,5 +83,29 @@ impl super::View for TextEdit { text.insert_text(&new_text, selected_chars.start); } } + + ui.horizontal(|ui| { + ui.label("Move cursor to the:"); + + if ui.button("start").clicked() { + let text_edit_id = output.response.id; + if let Some(mut state) = egui::TextEdit::load_state(ui.ctx(), text_edit_id) { + let ccursor = egui::text::CCursor::new(0); + state.set_ccursor_range(Some(egui::text::CCursorRange::one(ccursor))); + state.store(ui.ctx(), text_edit_id); + ui.ctx().memory().request_focus(text_edit_id); // give focus back to the `TextEdit`. + } + } + + if ui.button("end").clicked() { + let text_edit_id = output.response.id; + if let Some(mut state) = egui::TextEdit::load_state(ui.ctx(), text_edit_id) { + let ccursor = egui::text::CCursor::new(text.chars().count()); + state.set_ccursor_range(Some(egui::text::CCursorRange::one(ccursor))); + state.store(ui.ctx(), text_edit_id); + ui.ctx().memory().request_focus(text_edit_id); // give focus back to the `TextEdit`. + } + } + }); } } From fda8189cbab18e0acab8db972400e4a4ca0d915e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 21 Mar 2022 16:54:29 +0100 Subject: [PATCH 17/32] Move lints list to `.carg/config.toml` (#1394) That way they apply to all crates equally. See https://github.com/EmbarkStudios/rust-ecosystem/issues/22 for why. --- .cargo/config.toml | 85 +++++++++++++++++++++++++++++++++++ eframe/src/lib.rs | 10 +---- egui-winit/src/lib.rs | 78 -------------------------------- egui/src/lib.rs | 79 -------------------------------- egui_demo_app/src/lib.rs | 5 --- egui_demo_app/src/main.rs | 5 --- egui_demo_lib/src/lib.rs | 79 -------------------------------- egui_extras/src/lib.rs | 79 -------------------------------- egui_glium/src/lib.rs | 79 -------------------------------- egui_web/src/backend.rs | 2 +- egui_web/src/glow_wrapping.rs | 2 +- egui_web/src/lib.rs | 11 ++--- egui_web/src/screen_reader.rs | 1 + egui_web/src/text_agent.rs | 6 +-- emath/src/lib.rs | 79 -------------------------------- epaint/src/lib.rs | 79 -------------------------------- epi/src/lib.rs | 81 --------------------------------- 17 files changed, 96 insertions(+), 664 deletions(-) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 000000000..b809c283f --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,85 @@ +[target.'cfg(all())'] +rustflags = [ + # Global lints/warnings. + # See https://github.com/EmbarkStudios/rust-ecosystem/issues/22 for why we do this here + "-Dunsafe_code", + "-Wclippy::all", + "-Wclippy::await_holding_lock", + "-Wclippy::char_lit_as_u8", + "-Wclippy::checked_conversions", + "-Wclippy::dbg_macro", + "-Wclippy::debug_assert_with_mut_call", + "-Wclippy::disallowed_method", + "-Wclippy::doc_markdown", + "-Wclippy::empty_enum", + "-Wclippy::enum_glob_use", + # "-Wclippy::equatable_if_let", // Enable when we update MSRV + "-Wclippy::exit", + "-Wclippy::expl_impl_clone_on_copy", + "-Wclippy::explicit_deref_methods", + "-Wclippy::explicit_into_iter_loop", + "-Wclippy::fallible_impl_from", + "-Wclippy::filter_map_next", + "-Wclippy::flat_map_option", + "-Wclippy::float_cmp_const", + "-Wclippy::fn_params_excessive_bools", + "-Wclippy::from_iter_instead_of_collect", + "-Wclippy::if_let_mutex", + "-Wclippy::implicit_clone", + "-Wclippy::imprecise_flops", + "-Wclippy::inefficient_to_string", + "-Wclippy::invalid_upcast_comparisons", + # "-Wclippy::iter_not_returning_iterator", // Enable when we update MSRV + "-Wclippy::large_digit_groups", + "-Wclippy::large_stack_arrays", + "-Wclippy::large_types_passed_by_value", + "-Wclippy::let_unit_value", + "-Wclippy::linkedlist", + "-Wclippy::lossy_float_literal", + "-Wclippy::macro_use_imports", + "-Wclippy::manual_ok_or", + "-Wclippy::map_err_ignore", + "-Wclippy::map_flatten", + "-Wclippy::map_unwrap_or", + "-Wclippy::match_on_vec_items", + "-Wclippy::match_same_arms", + "-Wclippy::match_wild_err_arm", + "-Wclippy::match_wildcard_for_single_variants", + "-Wclippy::mem_forget", + "-Wclippy::mismatched_target_os", + "-Wclippy::missing_enforced_import_renames", + "-Wclippy::missing_errors_doc", + "-Wclippy::missing_safety_doc", + # "-Wclippy::mod_module_files", // Enable when we update MSRV + "-Wclippy::mut_mut", + "-Wclippy::mutex_integer", + "-Wclippy::needless_borrow", + "-Wclippy::needless_continue", + "-Wclippy::needless_for_each", + "-Wclippy::needless_pass_by_value", + "-Wclippy::option_option", + "-Wclippy::path_buf_push_overwrite", + "-Wclippy::ptr_as_ptr", + "-Wclippy::rc_mutex", + "-Wclippy::ref_option_ref", + "-Wclippy::rest_pat_in_fully_bound_structs", + "-Wclippy::same_functions_in_if_condition", + "-Wclippy::semicolon_if_nothing_returned", + "-Wclippy::single_match_else", + "-Wclippy::string_add_assign", + "-Wclippy::string_add", + "-Wclippy::string_lit_as_bytes", + "-Wclippy::string_to_string", + "-Wclippy::todo", + "-Wclippy::trait_duplication_in_bounds", + "-Wclippy::unimplemented", + "-Wclippy::unnested_or_patterns", + "-Wclippy::unused_self", + "-Wclippy::useless_transmute", + "-Wclippy::verbose_file_reads", + "-Wclippy::zero_sized_map_values", + "-Wfuture_incompatible", + "-Wnonstandard_style", + "-Wrust_2018_idioms", + "-Wrustdoc::missing_crate_level_docs", +] diff --git a/eframe/src/lib.rs b/eframe/src/lib.rs index 732d60a11..68b060b00 100644 --- a/eframe/src/lib.rs +++ b/eframe/src/lib.rs @@ -58,15 +58,6 @@ //! } //! ``` -// Forbid warnings in release builds: -#![cfg_attr(not(debug_assertions), deny(warnings))] -#![forbid(unsafe_code)] -#![warn( - clippy::all, - missing_docs, - rust_2018_idioms, - rustdoc::missing_crate_level_docs -)] #![allow(clippy::needless_doctest_main)] // Re-export all useful libraries: @@ -143,6 +134,7 @@ pub fn start_web(canvas_id: &str, app_creator: AppCreator) -> Result<(), wasm_bi /// } /// ``` #[cfg(not(target_arch = "wasm32"))] +#[allow(clippy::needless_pass_by_value)] pub fn run_native(app_name: &str, native_options: NativeOptions, app_creator: AppCreator) -> ! { egui_glow::run(app_name, &native_options, app_creator) } diff --git a/egui-winit/src/lib.rs b/egui-winit/src/lib.rs index a4a362200..731724cf2 100644 --- a/egui-winit/src/lib.rs +++ b/egui-winit/src/lib.rs @@ -3,84 +3,6 @@ //! The library translates winit events to egui, handled copy/paste, //! updates the cursor, open links clicked in egui, etc. -#![forbid(unsafe_code)] -#![warn( - clippy::all, - clippy::await_holding_lock, - clippy::char_lit_as_u8, - clippy::checked_conversions, - clippy::dbg_macro, - clippy::debug_assert_with_mut_call, - clippy::disallowed_method, - clippy::doc_markdown, - clippy::empty_enum, - clippy::enum_glob_use, - clippy::exit, - clippy::expl_impl_clone_on_copy, - clippy::explicit_deref_methods, - clippy::explicit_into_iter_loop, - clippy::fallible_impl_from, - clippy::filter_map_next, - clippy::flat_map_option, - clippy::float_cmp_const, - clippy::fn_params_excessive_bools, - clippy::from_iter_instead_of_collect, - clippy::if_let_mutex, - clippy::implicit_clone, - clippy::imprecise_flops, - clippy::inefficient_to_string, - clippy::invalid_upcast_comparisons, - clippy::large_digit_groups, - clippy::large_stack_arrays, - clippy::large_types_passed_by_value, - clippy::let_unit_value, - clippy::linkedlist, - clippy::lossy_float_literal, - clippy::macro_use_imports, - clippy::manual_ok_or, - clippy::map_err_ignore, - clippy::map_flatten, - clippy::map_unwrap_or, - clippy::match_on_vec_items, - clippy::match_same_arms, - clippy::match_wild_err_arm, - clippy::match_wildcard_for_single_variants, - clippy::mem_forget, - clippy::mismatched_target_os, - clippy::missing_errors_doc, - clippy::missing_safety_doc, - clippy::mut_mut, - clippy::mutex_integer, - clippy::needless_borrow, - clippy::needless_continue, - clippy::needless_for_each, - clippy::needless_pass_by_value, - clippy::option_option, - clippy::path_buf_push_overwrite, - clippy::ptr_as_ptr, - clippy::ref_option_ref, - clippy::rest_pat_in_fully_bound_structs, - clippy::same_functions_in_if_condition, - clippy::semicolon_if_nothing_returned, - clippy::single_match_else, - clippy::string_add_assign, - clippy::string_add, - clippy::string_lit_as_bytes, - clippy::string_to_string, - clippy::todo, - clippy::trait_duplication_in_bounds, - clippy::unimplemented, - clippy::unnested_or_patterns, - clippy::unused_self, - clippy::useless_transmute, - clippy::verbose_file_reads, - clippy::zero_sized_map_values, - future_incompatible, - nonstandard_style, - rust_2018_idioms, - rustdoc::missing_crate_level_docs -)] -#![allow(clippy::float_cmp)] #![allow(clippy::manual_range_contains)] pub use winit; diff --git a/egui/src/lib.rs b/egui/src/lib.rs index c6ab4b0ea..129afaa17 100644 --- a/egui/src/lib.rs +++ b/egui/src/lib.rs @@ -273,85 +273,6 @@ //! # }); //! ``` -// Forbid warnings in release builds: -#![cfg_attr(not(debug_assertions), deny(warnings))] -#![forbid(unsafe_code)] -#![warn( - clippy::all, - clippy::await_holding_lock, - clippy::char_lit_as_u8, - clippy::checked_conversions, - clippy::dbg_macro, - clippy::debug_assert_with_mut_call, - clippy::disallowed_method, - clippy::doc_markdown, - clippy::empty_enum, - clippy::enum_glob_use, - clippy::exit, - clippy::expl_impl_clone_on_copy, - clippy::explicit_deref_methods, - clippy::explicit_into_iter_loop, - clippy::fallible_impl_from, - clippy::filter_map_next, - clippy::flat_map_option, - clippy::float_cmp_const, - clippy::fn_params_excessive_bools, - clippy::from_iter_instead_of_collect, - clippy::if_let_mutex, - clippy::implicit_clone, - clippy::imprecise_flops, - clippy::inefficient_to_string, - clippy::invalid_upcast_comparisons, - clippy::large_digit_groups, - clippy::large_stack_arrays, - clippy::large_types_passed_by_value, - clippy::let_unit_value, - clippy::linkedlist, - clippy::lossy_float_literal, - clippy::macro_use_imports, - clippy::manual_ok_or, - clippy::map_err_ignore, - clippy::map_flatten, - clippy::map_unwrap_or, - clippy::match_on_vec_items, - clippy::match_same_arms, - clippy::match_wild_err_arm, - clippy::match_wildcard_for_single_variants, - clippy::mem_forget, - clippy::mismatched_target_os, - clippy::missing_errors_doc, - clippy::missing_safety_doc, - clippy::mut_mut, - clippy::mutex_integer, - clippy::needless_borrow, - clippy::needless_continue, - clippy::needless_for_each, - clippy::needless_pass_by_value, - clippy::option_option, - clippy::path_buf_push_overwrite, - clippy::ptr_as_ptr, - clippy::ref_option_ref, - clippy::rest_pat_in_fully_bound_structs, - clippy::same_functions_in_if_condition, - clippy::semicolon_if_nothing_returned, - clippy::single_match_else, - clippy::string_add_assign, - clippy::string_add, - clippy::string_lit_as_bytes, - clippy::string_to_string, - clippy::todo, - clippy::trait_duplication_in_bounds, - clippy::unimplemented, - clippy::unnested_or_patterns, - clippy::unused_self, - clippy::useless_transmute, - clippy::verbose_file_reads, - clippy::zero_sized_map_values, - future_incompatible, - nonstandard_style, - rust_2018_idioms, - rustdoc::missing_crate_level_docs -)] #![allow(clippy::float_cmp)] #![allow(clippy::manual_range_contains)] diff --git a/egui_demo_app/src/lib.rs b/egui_demo_app/src/lib.rs index 8d7993356..a4444edf6 100644 --- a/egui_demo_app/src/lib.rs +++ b/egui_demo_app/src/lib.rs @@ -1,8 +1,3 @@ -// Forbid warnings in release builds: -#![cfg_attr(not(debug_assertions), deny(warnings))] -#![forbid(unsafe_code)] -#![warn(clippy::all, rust_2018_idioms)] - #[cfg(target_arch = "wasm32")] use eframe::wasm_bindgen::{self, prelude::*}; diff --git a/egui_demo_app/src/main.rs b/egui_demo_app/src/main.rs index 43d701032..c99e2688a 100644 --- a/egui_demo_app/src/main.rs +++ b/egui_demo_app/src/main.rs @@ -1,10 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release -// Forbid warnings in release builds: -#![cfg_attr(not(debug_assertions), deny(warnings))] -#![forbid(unsafe_code)] -#![warn(clippy::all, rust_2018_idioms)] - // When compiling natively: fn main() { // Log to stdout (if you run with `RUST_LOG=debug`). diff --git a/egui_demo_lib/src/lib.rs b/egui_demo_lib/src/lib.rs index ca15d3482..87e9b2d9a 100644 --- a/egui_demo_lib/src/lib.rs +++ b/egui_demo_lib/src/lib.rs @@ -2,85 +2,6 @@ //! //! The demo-code is also used in benchmarks and tests. -// Forbid warnings in release builds: -#![cfg_attr(not(debug_assertions), deny(warnings))] -#![forbid(unsafe_code)] -#![warn( - clippy::all, - clippy::await_holding_lock, - clippy::char_lit_as_u8, - clippy::checked_conversions, - clippy::dbg_macro, - clippy::debug_assert_with_mut_call, - clippy::disallowed_method, - clippy::doc_markdown, - clippy::empty_enum, - clippy::enum_glob_use, - clippy::exit, - clippy::expl_impl_clone_on_copy, - clippy::explicit_deref_methods, - clippy::explicit_into_iter_loop, - clippy::fallible_impl_from, - clippy::filter_map_next, - clippy::flat_map_option, - clippy::float_cmp_const, - clippy::fn_params_excessive_bools, - clippy::from_iter_instead_of_collect, - clippy::if_let_mutex, - clippy::implicit_clone, - clippy::imprecise_flops, - clippy::inefficient_to_string, - clippy::invalid_upcast_comparisons, - clippy::large_digit_groups, - clippy::large_stack_arrays, - clippy::large_types_passed_by_value, - clippy::let_unit_value, - clippy::linkedlist, - clippy::lossy_float_literal, - clippy::macro_use_imports, - clippy::manual_ok_or, - clippy::map_err_ignore, - clippy::map_flatten, - clippy::map_unwrap_or, - clippy::match_on_vec_items, - clippy::match_same_arms, - clippy::match_wild_err_arm, - clippy::match_wildcard_for_single_variants, - clippy::mem_forget, - clippy::mismatched_target_os, - clippy::missing_errors_doc, - clippy::missing_safety_doc, - clippy::mut_mut, - clippy::mutex_integer, - clippy::needless_borrow, - clippy::needless_continue, - clippy::needless_for_each, - clippy::needless_pass_by_value, - clippy::option_option, - clippy::path_buf_push_overwrite, - clippy::ptr_as_ptr, - clippy::ref_option_ref, - clippy::rest_pat_in_fully_bound_structs, - clippy::same_functions_in_if_condition, - clippy::semicolon_if_nothing_returned, - clippy::single_match_else, - clippy::string_add_assign, - clippy::string_add, - clippy::string_lit_as_bytes, - clippy::string_to_string, - clippy::todo, - clippy::trait_duplication_in_bounds, - clippy::unimplemented, - clippy::unnested_or_patterns, - clippy::unused_self, - clippy::useless_transmute, - clippy::verbose_file_reads, - clippy::zero_sized_map_values, - future_incompatible, - nonstandard_style, - rust_2018_idioms, - rustdoc::missing_crate_level_docs -)] #![allow(clippy::float_cmp)] #![allow(clippy::manual_range_contains)] diff --git a/egui_extras/src/lib.rs b/egui_extras/src/lib.rs index c51f54a8c..c71114423 100644 --- a/egui_extras/src/lib.rs +++ b/egui_extras/src/lib.rs @@ -1,84 +1,5 @@ //! This is a crate that adds some features on top top of [`egui`](https://github.com/emilk/egui). This crate are for experimental features, and features that require big dependencies that does not belong in `egui`. -// Forbid warnings in release builds: -#![cfg_attr(not(debug_assertions), deny(warnings))] -#![forbid(unsafe_code)] -#![warn( - clippy::all, - clippy::await_holding_lock, - clippy::char_lit_as_u8, - clippy::checked_conversions, - clippy::dbg_macro, - clippy::debug_assert_with_mut_call, - clippy::disallowed_method, - clippy::doc_markdown, - clippy::empty_enum, - clippy::enum_glob_use, - clippy::exit, - clippy::expl_impl_clone_on_copy, - clippy::explicit_deref_methods, - clippy::explicit_into_iter_loop, - clippy::fallible_impl_from, - clippy::filter_map_next, - clippy::flat_map_option, - clippy::float_cmp_const, - clippy::fn_params_excessive_bools, - clippy::from_iter_instead_of_collect, - clippy::if_let_mutex, - clippy::implicit_clone, - clippy::imprecise_flops, - clippy::inefficient_to_string, - clippy::invalid_upcast_comparisons, - clippy::large_digit_groups, - clippy::large_stack_arrays, - clippy::large_types_passed_by_value, - clippy::let_unit_value, - clippy::linkedlist, - clippy::lossy_float_literal, - clippy::macro_use_imports, - clippy::manual_ok_or, - clippy::map_err_ignore, - clippy::map_flatten, - clippy::map_unwrap_or, - clippy::match_on_vec_items, - clippy::match_same_arms, - clippy::match_wild_err_arm, - clippy::match_wildcard_for_single_variants, - clippy::mem_forget, - clippy::mismatched_target_os, - clippy::missing_errors_doc, - clippy::missing_safety_doc, - clippy::mut_mut, - clippy::mutex_integer, - clippy::needless_borrow, - clippy::needless_continue, - clippy::needless_for_each, - clippy::needless_pass_by_value, - clippy::option_option, - clippy::path_buf_push_overwrite, - clippy::ptr_as_ptr, - clippy::ref_option_ref, - clippy::rest_pat_in_fully_bound_structs, - clippy::same_functions_in_if_condition, - clippy::semicolon_if_nothing_returned, - clippy::single_match_else, - clippy::string_add_assign, - clippy::string_add, - clippy::string_lit_as_bytes, - clippy::string_to_string, - clippy::todo, - clippy::trait_duplication_in_bounds, - clippy::unimplemented, - clippy::unnested_or_patterns, - clippy::unused_self, - clippy::useless_transmute, - clippy::verbose_file_reads, - clippy::zero_sized_map_values, - future_incompatible, - nonstandard_style, - rust_2018_idioms, - rustdoc::missing_crate_level_docs -)] #![allow(clippy::float_cmp)] #![allow(clippy::manual_range_contains)] diff --git a/egui_glium/src/lib.rs b/egui_glium/src/lib.rs index 1ecfda86a..f5c85e686 100644 --- a/egui_glium/src/lib.rs +++ b/egui_glium/src/lib.rs @@ -4,85 +4,6 @@ //! //! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead. -// Forbid warnings in release builds: -#![cfg_attr(not(debug_assertions), deny(warnings))] -#![forbid(unsafe_code)] -#![warn( - clippy::all, - clippy::await_holding_lock, - clippy::char_lit_as_u8, - clippy::checked_conversions, - clippy::dbg_macro, - clippy::debug_assert_with_mut_call, - clippy::disallowed_method, - clippy::doc_markdown, - clippy::empty_enum, - clippy::enum_glob_use, - clippy::exit, - clippy::expl_impl_clone_on_copy, - clippy::explicit_deref_methods, - clippy::explicit_into_iter_loop, - clippy::fallible_impl_from, - clippy::filter_map_next, - clippy::flat_map_option, - clippy::float_cmp_const, - clippy::fn_params_excessive_bools, - clippy::from_iter_instead_of_collect, - clippy::if_let_mutex, - clippy::implicit_clone, - clippy::imprecise_flops, - clippy::inefficient_to_string, - clippy::invalid_upcast_comparisons, - clippy::large_digit_groups, - clippy::large_stack_arrays, - clippy::large_types_passed_by_value, - clippy::let_unit_value, - clippy::linkedlist, - clippy::lossy_float_literal, - clippy::macro_use_imports, - clippy::manual_ok_or, - clippy::map_err_ignore, - clippy::map_flatten, - clippy::map_unwrap_or, - clippy::match_on_vec_items, - clippy::match_same_arms, - clippy::match_wild_err_arm, - clippy::match_wildcard_for_single_variants, - clippy::mem_forget, - clippy::mismatched_target_os, - clippy::missing_errors_doc, - clippy::missing_safety_doc, - clippy::mut_mut, - clippy::mutex_integer, - clippy::needless_borrow, - clippy::needless_continue, - clippy::needless_for_each, - clippy::needless_pass_by_value, - clippy::option_option, - clippy::path_buf_push_overwrite, - clippy::ptr_as_ptr, - clippy::ref_option_ref, - clippy::rest_pat_in_fully_bound_structs, - clippy::same_functions_in_if_condition, - clippy::semicolon_if_nothing_returned, - clippy::single_match_else, - clippy::string_add_assign, - clippy::string_add, - clippy::string_lit_as_bytes, - clippy::string_to_string, - clippy::todo, - clippy::trait_duplication_in_bounds, - clippy::unimplemented, - clippy::unnested_or_patterns, - clippy::unused_self, - clippy::useless_transmute, - clippy::verbose_file_reads, - clippy::zero_sized_map_values, - future_incompatible, - nonstandard_style, - rust_2018_idioms, - rustdoc::missing_crate_level_docs -)] #![allow(clippy::float_cmp)] #![allow(clippy::manual_range_contains)] diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index 624243502..0ea9c8c08 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -66,7 +66,7 @@ fn web_location() -> epi::Location { let query_map = parse_query_map(&query) .iter() - .map(|(k, v)| (k.to_string(), v.to_string())) + .map(|(k, v)| ((*k).to_string(), (*v).to_string())) .collect(); epi::Location { diff --git a/egui_web/src/glow_wrapping.rs b/egui_web/src/glow_wrapping.rs index c589aad14..a66cf5d4d 100644 --- a/egui_web/src/glow_wrapping.rs +++ b/egui_web/src/glow_wrapping.rs @@ -50,7 +50,7 @@ impl WrappedGlowPainter { pub fn clear(&mut self, clear_color: Rgba) { let canvas_dimension = [self.canvas.width(), self.canvas.height()]; - egui_glow::painter::clear(self.painter.gl(), canvas_dimension, clear_color) + egui_glow::painter::clear(self.painter.gl(), canvas_dimension, clear_color); } pub fn paint_primitives( diff --git a/egui_web/src/lib.rs b/egui_web/src/lib.rs index 8bddd04a3..c0bc1905d 100644 --- a/egui_web/src/lib.rs +++ b/egui_web/src/lib.rs @@ -4,10 +4,7 @@ //! //! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead. -// Forbid warnings in release builds: -#![cfg_attr(not(debug_assertions), deny(warnings))] -#![forbid(unsafe_code)] -#![warn(clippy::all, rustdoc::missing_crate_level_docs, rust_2018_idioms)] +#![allow(clippy::missing_errors_doc)] // So many `-> Result<_, JsValue>` pub mod backend; mod glow_wrapping; @@ -891,11 +888,11 @@ pub(crate) fn webgl1_requires_brightening(gl: &web_sys::WebGlRenderingContext) - !user_agent.contains("Mac OS X") && crate::is_safari_and_webkit_gtk(gl) } -/// detecting Safari and webkitGTK. +/// detecting Safari and `webkitGTK`. /// -/// Safari and webkitGTK use unmasked renderer :Apple GPU +/// Safari and `webkitGTK` use unmasked renderer :Apple GPU /// -/// If we detect safari or webkitGTK returns true. +/// If we detect safari or `webkitGTKs` returns true. /// /// This function used to avoid displaying linear color with `sRGB` supported systems. fn is_safari_and_webkit_gtk(gl: &web_sys::WebGlRenderingContext) -> bool { diff --git a/egui_web/src/screen_reader.rs b/egui_web/src/screen_reader.rs index 11c36efdc..e3ac02b1a 100644 --- a/egui_web/src/screen_reader.rs +++ b/egui_web/src/screen_reader.rs @@ -29,6 +29,7 @@ impl Default for ScreenReader { impl ScreenReader { #[cfg(not(feature = "screen_reader"))] + #[allow(clippy::unused_self)] pub fn speak(&mut self, _text: &str) {} #[cfg(feature = "screen_reader")] diff --git a/egui_web/src/text_agent.rs b/egui_web/src/text_agent.rs index ef2af8d89..c73873549 100644 --- a/egui_web/src/text_agent.rs +++ b/egui_web/src/text_agent.rs @@ -161,7 +161,7 @@ pub fn update_text_agent(runner: MutexGuard<'_, AppRunner>) -> Option<()> { let delta = delta.max(-keyboard_fraction); // Don't move it crazy much - let new_pos_percent = (delta * 100.0).round().to_string() + "%"; + let new_pos_percent = format!("{}%", (delta * 100.0).round()); canvas_style.set_property("position", "absolute").ok()?; canvas_style.set_property("top", &new_pos_percent).ok()?; @@ -217,8 +217,8 @@ pub fn move_text_cursor(cursor: Option, canvas_id: &str) -> Option<( let x = (x - canvas.offset_width() as f32 / 2.0) .min(canvas.client_width() as f32 - bounding_rect.width() as f32); style.set_property("position", "absolute").ok()?; - style.set_property("top", &(y.to_string() + "px")).ok()?; - style.set_property("left", &(x.to_string() + "px")).ok() + style.set_property("top", &format!("{}px", y)).ok()?; + style.set_property("left", &format!("{}px", x)).ok() }) } else { style.set_property("position", "absolute").ok()?; diff --git a/emath/src/lib.rs b/emath/src/lib.rs index 97fb41ed2..4f87d9f61 100644 --- a/emath/src/lib.rs +++ b/emath/src/lib.rs @@ -15,85 +15,6 @@ //! For that, use something else ([`glam`](https://docs.rs/glam), [`nalgebra`](https://docs.rs/nalgebra), …) //! and enable the `mint` feature flag in `emath` to enable implicit conversion to/from `emath`. -// Forbid warnings in release builds: -#![cfg_attr(not(debug_assertions), deny(warnings))] -#![forbid(unsafe_code)] -#![warn( - clippy::all, - clippy::await_holding_lock, - clippy::char_lit_as_u8, - clippy::checked_conversions, - clippy::dbg_macro, - clippy::debug_assert_with_mut_call, - clippy::disallowed_method, - clippy::doc_markdown, - clippy::empty_enum, - clippy::enum_glob_use, - clippy::exit, - clippy::expl_impl_clone_on_copy, - clippy::explicit_deref_methods, - clippy::explicit_into_iter_loop, - clippy::fallible_impl_from, - clippy::filter_map_next, - clippy::flat_map_option, - clippy::float_cmp_const, - clippy::fn_params_excessive_bools, - clippy::from_iter_instead_of_collect, - clippy::if_let_mutex, - clippy::implicit_clone, - clippy::imprecise_flops, - clippy::inefficient_to_string, - clippy::invalid_upcast_comparisons, - clippy::large_digit_groups, - clippy::large_stack_arrays, - clippy::large_types_passed_by_value, - clippy::let_unit_value, - clippy::linkedlist, - clippy::lossy_float_literal, - clippy::macro_use_imports, - clippy::manual_ok_or, - clippy::map_err_ignore, - clippy::map_flatten, - clippy::map_unwrap_or, - clippy::match_on_vec_items, - clippy::match_same_arms, - clippy::match_wild_err_arm, - clippy::match_wildcard_for_single_variants, - clippy::mem_forget, - clippy::mismatched_target_os, - clippy::missing_errors_doc, - clippy::missing_safety_doc, - clippy::mut_mut, - clippy::mutex_integer, - clippy::needless_borrow, - clippy::needless_continue, - clippy::needless_for_each, - clippy::needless_pass_by_value, - clippy::option_option, - clippy::path_buf_push_overwrite, - clippy::ptr_as_ptr, - clippy::ref_option_ref, - clippy::rest_pat_in_fully_bound_structs, - clippy::same_functions_in_if_condition, - clippy::semicolon_if_nothing_returned, - clippy::single_match_else, - clippy::string_add_assign, - clippy::string_add, - clippy::string_lit_as_bytes, - clippy::string_to_string, - clippy::todo, - clippy::trait_duplication_in_bounds, - clippy::unimplemented, - clippy::unnested_or_patterns, - clippy::unused_self, - clippy::useless_transmute, - clippy::verbose_file_reads, - clippy::zero_sized_map_values, - future_incompatible, - nonstandard_style, - rust_2018_idioms, - rustdoc::missing_crate_level_docs -)] #![allow(clippy::float_cmp)] #![allow(clippy::manual_range_contains)] diff --git a/epaint/src/lib.rs b/epaint/src/lib.rs index e75d30265..1e27b55f1 100644 --- a/epaint/src/lib.rs +++ b/epaint/src/lib.rs @@ -5,85 +5,6 @@ //! Create some [`Shape`]:s and pass them to [`tessellate_shapes`] to generate [`Mesh`]:es //! that you can then paint using some graphics API of your choice (e.g. OpenGL). -// Forbid warnings in release builds: -#![cfg_attr(not(debug_assertions), deny(warnings))] -#![forbid(unsafe_code)] -#![warn( - clippy::all, - clippy::await_holding_lock, - clippy::char_lit_as_u8, - clippy::checked_conversions, - clippy::dbg_macro, - clippy::debug_assert_with_mut_call, - clippy::disallowed_method, - clippy::doc_markdown, - clippy::empty_enum, - clippy::enum_glob_use, - clippy::exit, - clippy::expl_impl_clone_on_copy, - clippy::explicit_deref_methods, - clippy::explicit_into_iter_loop, - clippy::fallible_impl_from, - clippy::filter_map_next, - clippy::flat_map_option, - clippy::float_cmp_const, - clippy::fn_params_excessive_bools, - clippy::from_iter_instead_of_collect, - clippy::if_let_mutex, - clippy::implicit_clone, - clippy::imprecise_flops, - clippy::inefficient_to_string, - clippy::invalid_upcast_comparisons, - clippy::large_digit_groups, - clippy::large_stack_arrays, - clippy::large_types_passed_by_value, - clippy::let_unit_value, - clippy::linkedlist, - clippy::lossy_float_literal, - clippy::macro_use_imports, - clippy::manual_ok_or, - clippy::map_err_ignore, - clippy::map_flatten, - clippy::map_unwrap_or, - clippy::match_on_vec_items, - clippy::match_same_arms, - clippy::match_wild_err_arm, - clippy::match_wildcard_for_single_variants, - clippy::mem_forget, - clippy::mismatched_target_os, - clippy::missing_errors_doc, - clippy::missing_safety_doc, - clippy::mut_mut, - clippy::mutex_integer, - clippy::needless_borrow, - clippy::needless_continue, - clippy::needless_for_each, - clippy::needless_pass_by_value, - clippy::option_option, - clippy::path_buf_push_overwrite, - clippy::ptr_as_ptr, - clippy::ref_option_ref, - clippy::rest_pat_in_fully_bound_structs, - clippy::same_functions_in_if_condition, - clippy::semicolon_if_nothing_returned, - clippy::single_match_else, - clippy::string_add_assign, - clippy::string_add, - clippy::string_lit_as_bytes, - clippy::string_to_string, - clippy::todo, - clippy::trait_duplication_in_bounds, - clippy::unimplemented, - clippy::unnested_or_patterns, - clippy::unused_self, - clippy::useless_transmute, - clippy::verbose_file_reads, - clippy::zero_sized_map_values, - future_incompatible, - nonstandard_style, - rust_2018_idioms, - rustdoc::missing_crate_level_docs -)] #![allow(clippy::float_cmp)] #![allow(clippy::manual_range_contains)] diff --git a/epi/src/lib.rs b/epi/src/lib.rs index d1bda8d06..9359cccaf 100644 --- a/epi/src/lib.rs +++ b/epi/src/lib.rs @@ -6,87 +6,6 @@ //! //! Start by looking at the [`App`] trait, and implement [`App::update`]. -// Forbid warnings in release builds: -#![cfg_attr(not(debug_assertions), deny(warnings))] -#![forbid(unsafe_code)] -#![warn( - clippy::all, - clippy::await_holding_lock, - clippy::char_lit_as_u8, - clippy::checked_conversions, - clippy::dbg_macro, - clippy::debug_assert_with_mut_call, - clippy::disallowed_method, - clippy::doc_markdown, - clippy::empty_enum, - clippy::enum_glob_use, - clippy::exit, - clippy::expl_impl_clone_on_copy, - clippy::explicit_deref_methods, - clippy::explicit_into_iter_loop, - clippy::fallible_impl_from, - clippy::filter_map_next, - clippy::flat_map_option, - clippy::float_cmp_const, - clippy::fn_params_excessive_bools, - clippy::from_iter_instead_of_collect, - clippy::if_let_mutex, - clippy::implicit_clone, - clippy::imprecise_flops, - clippy::inefficient_to_string, - clippy::invalid_upcast_comparisons, - clippy::large_digit_groups, - clippy::large_stack_arrays, - clippy::large_types_passed_by_value, - clippy::let_unit_value, - clippy::linkedlist, - clippy::lossy_float_literal, - clippy::macro_use_imports, - clippy::manual_ok_or, - clippy::map_err_ignore, - clippy::map_flatten, - clippy::map_unwrap_or, - clippy::match_on_vec_items, - clippy::match_same_arms, - clippy::match_wild_err_arm, - clippy::match_wildcard_for_single_variants, - clippy::mem_forget, - clippy::mismatched_target_os, - clippy::missing_errors_doc, - clippy::missing_safety_doc, - clippy::mut_mut, - clippy::mutex_integer, - clippy::needless_borrow, - clippy::needless_continue, - clippy::needless_for_each, - clippy::needless_pass_by_value, - clippy::option_option, - clippy::path_buf_push_overwrite, - clippy::ptr_as_ptr, - clippy::ref_option_ref, - clippy::rest_pat_in_fully_bound_structs, - clippy::same_functions_in_if_condition, - clippy::semicolon_if_nothing_returned, - clippy::single_match_else, - clippy::string_add_assign, - clippy::string_add, - clippy::string_lit_as_bytes, - clippy::string_to_string, - clippy::todo, - clippy::trait_duplication_in_bounds, - clippy::unimplemented, - clippy::unnested_or_patterns, - clippy::unused_self, - clippy::useless_transmute, - clippy::verbose_file_reads, - clippy::zero_sized_map_values, - future_incompatible, - nonstandard_style, - rust_2018_idioms, - rustdoc::missing_crate_level_docs -)] -#![allow(clippy::float_cmp)] -#![allow(clippy::manual_range_contains)] #![warn(missing_docs)] // Let's keep `epi` well-documented. /// File storage which can be used by native backends. From 339b28b4708450181ff93e1024aec328bac986cb Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 21 Mar 2022 21:44:36 +0100 Subject: [PATCH 18/32] Add Frame::outer_margin, and rename Frame::margin to Frame::inner_margin --- CHANGELOG.md | 2 + egui/src/containers/frame.rs | 61 +++++++++++++++--------- egui/src/containers/popup.rs | 2 +- egui/src/containers/window.rs | 4 +- egui/src/style.rs | 30 ++++++++++++ egui/src/widgets/plot/legend.rs | 3 +- egui_demo_lib/src/syntax_highlighting.rs | 2 +- 7 files changed, 78 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3612db709..f85192891 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,11 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w * Added `Frame::canvas` ([#1362](https://github.com/emilk/egui/pull/1362)). * `Context::request_repaint` will wake up UI thread, if integrations has called `Context::set_request_repaint_callback` ([#1366](https://github.com/emilk/egui/pull/1366)). * Added `Ui::push_id` ([#1374](https://github.com/emilk/egui/pull/1374)). +* Added `Frame::outer_margin`. ### Changed 🔧 * `ClippedMesh` has been replaced with `ClippedPrimitive` ([#1351](https://github.com/emilk/egui/pull/1351)). +* Renamed `Frame::margin` to `Frame::inner_margin`. ### Fixed 🐛 * Fixed ComboBoxes always being rendered left-aligned ([#1304](https://github.com/emilk/egui/pull/1304)). diff --git a/egui/src/containers/frame.rs b/egui/src/containers/frame.rs index 7eabe3833..8d2430ea1 100644 --- a/egui/src/containers/frame.rs +++ b/egui/src/containers/frame.rs @@ -7,8 +7,10 @@ use epaint::*; #[derive(Clone, Copy, Debug, Default, PartialEq)] #[must_use = "You should call .show()"] pub struct Frame { - /// On each side - pub margin: Margin, + /// Margin within the painted frame. + pub inner_margin: Margin, + /// Margin outside the painted frame. + pub outer_margin: Margin, pub rounding: Rounding, pub shadow: Shadow, pub fill: Color32, @@ -23,7 +25,7 @@ impl Frame { /// For when you want to group a few widgets together within a frame. pub fn group(style: &Style) -> Self { Self { - margin: Margin::same(6.0), // symmetric looks best in corners when nesting + inner_margin: Margin::same(6.0), // symmetric looks best in corners when nesting rounding: style.visuals.widgets.noninteractive.rounding, stroke: style.visuals.widgets.noninteractive.bg_stroke, ..Default::default() @@ -32,7 +34,7 @@ impl Frame { pub(crate) fn side_top_panel(style: &Style) -> Self { Self { - margin: Margin::symmetric(8.0, 2.0), + inner_margin: Margin::symmetric(8.0, 2.0), rounding: Rounding::none(), fill: style.visuals.window_fill(), stroke: style.visuals.window_stroke(), @@ -42,7 +44,7 @@ impl Frame { pub(crate) fn central_panel(style: &Style) -> Self { Self { - margin: Margin::symmetric(8.0, 8.0), + inner_margin: Margin::symmetric(8.0, 8.0), rounding: Rounding::none(), fill: style.visuals.window_fill(), stroke: Default::default(), @@ -52,31 +54,34 @@ impl Frame { pub fn window(style: &Style) -> Self { Self { - margin: style.spacing.window_margin, + inner_margin: style.spacing.window_margin, rounding: style.visuals.window_rounding, shadow: style.visuals.window_shadow, fill: style.visuals.window_fill(), stroke: style.visuals.window_stroke(), + ..Default::default() } } pub fn menu(style: &Style) -> Self { Self { - margin: Margin::same(1.0), + inner_margin: Margin::same(1.0), rounding: style.visuals.widgets.noninteractive.rounding, shadow: style.visuals.popup_shadow, fill: style.visuals.window_fill(), stroke: style.visuals.window_stroke(), + ..Default::default() } } pub fn popup(style: &Style) -> Self { Self { - margin: style.spacing.window_margin, + inner_margin: style.spacing.window_margin, rounding: style.visuals.widgets.noninteractive.rounding, shadow: style.visuals.popup_shadow, fill: style.visuals.window_fill(), stroke: style.visuals.window_stroke(), + ..Default::default() } } @@ -86,7 +91,7 @@ impl Frame { /// and in dark mode this will be very dark. pub fn canvas(style: &Style) -> Self { Self { - margin: Margin::symmetric(10.0, 10.0), + inner_margin: Margin::symmetric(10.0, 10.0), rounding: style.visuals.widgets.noninteractive.rounding, fill: style.visuals.extreme_bg_color, stroke: style.visuals.window_stroke(), @@ -119,12 +124,23 @@ impl Frame { self } - /// Margin on each side of the frame. - pub fn margin(mut self, margin: impl Into) -> Self { - self.margin = margin.into(); + /// Margin within the painted frame. + pub fn inner_margin(mut self, inner_margin: impl Into) -> Self { + self.inner_margin = inner_margin.into(); self } + /// Margin outside the painted frame. + pub fn outer_margin(mut self, outer_margin: impl Into) -> Self { + self.outer_margin = outer_margin.into(); + self + } + + #[deprecated = "Renamed inner_margin in egui 0.18"] + pub fn margin(self, margin: impl Into) -> Self { + self.inner_margin(margin) + } + pub fn shadow(mut self, shadow: Shadow) -> Self { self.shadow = shadow; self @@ -150,8 +166,8 @@ impl Frame { let outer_rect_bounds = ui.available_rect_before_wrap(); let mut inner_rect = outer_rect_bounds; - inner_rect.min += Vec2::new(self.margin.left, self.margin.top); - inner_rect.max -= Vec2::new(self.margin.right, self.margin.bottom); + inner_rect.min += self.outer_margin.left_top() + self.inner_margin.left_top(); + inner_rect.max -= self.outer_margin.right_bottom() + self.inner_margin.right_bottom(); // Make sure we don't shrink to the negative: inner_rect.max.x = inner_rect.max.x.max(inner_rect.min.x); @@ -185,7 +201,8 @@ impl Frame { pub fn paint(&self, outer_rect: Rect) -> Shape { let Self { - margin: _, + inner_margin: _, + outer_margin: _, rounding, shadow, fill, @@ -210,15 +227,15 @@ impl Frame { } impl Prepared { - pub fn outer_rect(&self) -> Rect { + fn paint_rect(&self) -> Rect { let mut rect = self.content_ui.min_rect(); - rect.min -= Vec2::new(self.frame.margin.left, self.frame.margin.top); - rect.max += Vec2::new(self.frame.margin.right, self.frame.margin.bottom); + rect.min -= self.frame.inner_margin.left_top(); + rect.max += self.frame.inner_margin.right_bottom(); rect } pub fn end(self, ui: &mut Ui) -> Response { - let outer_rect = self.outer_rect(); + let paint_rect = self.paint_rect(); let Prepared { frame, @@ -226,11 +243,11 @@ impl Prepared { .. } = self; - if ui.is_rect_visible(outer_rect) { - let shape = frame.paint(outer_rect); + if ui.is_rect_visible(paint_rect) { + let shape = frame.paint(paint_rect); ui.painter().set(where_to_put_background, shape); } - ui.allocate_rect(outer_rect, Sense::hover()) + ui.allocate_rect(paint_rect, Sense::hover()) } } diff --git a/egui/src/containers/popup.rs b/egui/src/containers/popup.rs index c8cbdb67f..fa00f3cf9 100644 --- a/egui/src/containers/popup.rs +++ b/egui/src/containers/popup.rs @@ -298,7 +298,7 @@ pub fn popup_below_widget( // Note: we use a separate clip-rect for this area, so the popup can be outside the parent. // See https://github.com/emilk/egui/issues/825 let frame = Frame::popup(ui.style()); - let frame_margin = frame.margin; + let frame_margin = frame.inner_margin + frame.outer_margin; frame .show(ui, |ui| { ui.with_layout(Layout::top_down_justified(Align::LEFT), |ui| { diff --git a/egui/src/containers/window.rs b/egui/src/containers/window.rs index 4e1eb70d5..b059d4324 100644 --- a/egui/src/containers/window.rs +++ b/egui/src/containers/window.rs @@ -301,7 +301,9 @@ impl<'open> Window<'open> { } else { 0.0 }; - let margins = frame.margin.sum() + vec2(0.0, title_bar_height); + let margins = frame.outer_margin.sum() + + frame.inner_margin.sum() + + vec2(0.0, title_bar_height); interact( window_interaction, diff --git a/egui/src/style.rs b/egui/src/style.rs index 9024a002a..d7ffbabb0 100644 --- a/egui/src/style.rs +++ b/egui/src/style.rs @@ -277,6 +277,8 @@ impl Spacing { } } +// ---------------------------------------------------------------------------- + #[derive(Clone, Copy, Debug, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Margin { @@ -312,6 +314,20 @@ impl Margin { pub fn sum(&self) -> Vec2 { Vec2::new(self.left + self.right, self.top + self.bottom) } + + pub fn left_top(&self) -> Vec2 { + Vec2::new(self.left, self.top) + } + + pub fn right_bottom(&self) -> Vec2 { + Vec2::new(self.right, self.bottom) + } +} + +impl From for Margin { + fn from(v: f32) -> Self { + Self::same(v) + } } impl From for Margin { @@ -320,6 +336,20 @@ impl From for Margin { } } +impl std::ops::Add for Margin { + type Output = Self; + fn add(self, other: Self) -> Self { + Self { + left: self.left + other.left, + right: self.right + other.right, + top: self.top + other.top, + bottom: self.bottom + other.bottom, + } + } +} + +// ---------------------------------------------------------------------------- + /// How and when interaction happens. #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] diff --git a/egui/src/widgets/plot/legend.rs b/egui/src/widgets/plot/legend.rs index 6c60c6a36..41bd9d4f1 100644 --- a/egui/src/widgets/plot/legend.rs +++ b/egui/src/widgets/plot/legend.rs @@ -239,11 +239,12 @@ impl Widget for &mut LegendWidget { legend_ui .scope(|ui| { let background_frame = Frame { - margin: vec2(8.0, 4.0).into(), + inner_margin: vec2(8.0, 4.0).into(), rounding: ui.style().visuals.window_rounding, shadow: epaint::Shadow::default(), fill: ui.style().visuals.extreme_bg_color, stroke: ui.style().visuals.window_stroke(), + ..Default::default() } .multiply_with_opacity(config.background_alpha); background_frame diff --git a/egui_demo_lib/src/syntax_highlighting.rs b/egui_demo_lib/src/syntax_highlighting.rs index 765b70b9a..e1aca3324 100644 --- a/egui_demo_lib/src/syntax_highlighting.rs +++ b/egui_demo_lib/src/syntax_highlighting.rs @@ -277,7 +277,7 @@ impl CodeTheme { ui.data().insert_persisted(selected_id, selected_tt); egui::Frame::group(ui.style()) - .margin(egui::Vec2::splat(2.0)) + .inner_margin(egui::Vec2::splat(2.0)) .show(ui, |ui| { // ui.group(|ui| { ui.style_mut().override_text_style = Some(egui::TextStyle::Small); From 5c68edbb155f4bac303a0e0649aabc07d4da7a18 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 21 Mar 2022 21:48:35 +0100 Subject: [PATCH 19/32] Clippy fixes --- eframe/examples/custom_3d.rs | 3 ++- eframe/examples/download_image.rs | 1 + egui_demo_app/src/lib.rs | 2 ++ egui_demo_app/src/main.rs | 2 ++ egui_demo_lib/benches/benchmark.rs | 20 ++++++++++---------- egui_glium/examples/native_texture.rs | 4 ++-- egui_glow/examples/pure_glow.rs | 1 + 7 files changed, 20 insertions(+), 13 deletions(-) diff --git a/eframe/examples/custom_3d.rs b/eframe/examples/custom_3d.rs index 54f9bf8ed..6c4b5e6f0 100644 --- a/eframe/examples/custom_3d.rs +++ b/eframe/examples/custom_3d.rs @@ -7,6 +7,7 @@ //! * [`three-d`](https://github.com/asny/three-d) #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(unsafe_code)] use eframe::egui; @@ -57,7 +58,7 @@ impl eframe::App for MyApp { } fn on_exit(&mut self, gl: &glow::Context) { - self.rotating_triangle.lock().destroy(gl) + self.rotating_triangle.lock().destroy(gl); } } diff --git a/eframe/examples/download_image.rs b/eframe/examples/download_image.rs index 9cbb51d11..71ac412a3 100644 --- a/eframe/examples/download_image.rs +++ b/eframe/examples/download_image.rs @@ -50,6 +50,7 @@ impl eframe::App for MyApp { } } +#[allow(clippy::needless_pass_by_value)] fn parse_response(response: ehttp::Response) -> Result { let content_type = response.content_type().unwrap_or_default(); if content_type.starts_with("image/") { diff --git a/egui_demo_app/src/lib.rs b/egui_demo_app/src/lib.rs index a4444edf6..e81aba48f 100644 --- a/egui_demo_app/src/lib.rs +++ b/egui_demo_app/src/lib.rs @@ -1,3 +1,5 @@ +//! Demo app for egui + #[cfg(target_arch = "wasm32")] use eframe::wasm_bindgen::{self, prelude::*}; diff --git a/egui_demo_app/src/main.rs b/egui_demo_app/src/main.rs index c99e2688a..6d747f0fe 100644 --- a/egui_demo_app/src/main.rs +++ b/egui_demo_app/src/main.rs @@ -1,3 +1,5 @@ +//! Demo app for egui + #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release // When compiling natively: diff --git a/egui_demo_lib/benches/benchmark.rs b/egui_demo_lib/benches/benchmark.rs index 9b988b3fb..9038187f6 100644 --- a/egui_demo_lib/benches/benchmark.rs +++ b/egui_demo_lib/benches/benchmark.rs @@ -17,7 +17,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { demo_windows.ui(ctx); }); ctx.tessellate(full_output.shapes) - }) + }); }); c.bench_function("demo_no_tessellate", |b| { @@ -25,14 +25,14 @@ pub fn criterion_benchmark(c: &mut Criterion) { ctx.run(RawInput::default(), |ctx| { demo_windows.ui(ctx); }) - }) + }); }); let full_output = ctx.run(RawInput::default(), |ctx| { demo_windows.ui(ctx); }); c.bench_function("demo_only_tessellate", |b| { - b.iter(|| ctx.tessellate(full_output.shapes.clone())) + b.iter(|| ctx.tessellate(full_output.shapes.clone())); }); } @@ -45,7 +45,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { ctx.run(RawInput::default(), |ctx| { demo_windows.ui(ctx); }) - }) + }); }); } @@ -56,12 +56,12 @@ pub fn criterion_benchmark(c: &mut Criterion) { c.bench_function("label &str", |b| { b.iter(|| { ui.label("the quick brown fox jumps over the lazy dog"); - }) + }); }); c.bench_function("label format!", |b| { b.iter(|| { ui.label("the quick brown fox jumps over the lazy dog".to_owned()); - }) + }); }); }); }); @@ -77,7 +77,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { let rect = ui.max_rect(); b.iter(|| { painter.rect(rect, 2.0, egui::Color32::RED, (1.0, egui::Color32::WHITE)); - }) + }); }); }); @@ -108,7 +108,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { wrap_width, ); layout(&mut locked_fonts.fonts, job.into()) - }) + }); }); } c.bench_function("text_layout_cached", |b| { @@ -119,7 +119,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { color, wrap_width, ) - }) + }); }); let galley = fonts.layout(LOREM_IPSUM_LONG.to_owned(), font_id, color, wrap_width); @@ -131,7 +131,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { b.iter(|| { tessellator.tessellate_text(font_image_size, &text_shape, &mut mesh); mesh.clear(); - }) + }); }); } } diff --git a/egui_glium/examples/native_texture.rs b/egui_glium/examples/native_texture.rs index 8d65c5281..abaafd465 100644 --- a/egui_glium/examples/native_texture.rs +++ b/egui_glium/examples/native_texture.rs @@ -1,4 +1,4 @@ -//! Example how to use [epi::NativeTexture] with glium. +//! Example how to use [`epi::NativeTexture`] with glium. #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release @@ -111,7 +111,7 @@ fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Disp glium::Display::new(window_builder, context_builder, event_loop).unwrap() } -fn load_glium_image(png_data: &[u8]) -> glium::texture::RawImage2d { +fn load_glium_image(png_data: &[u8]) -> glium::texture::RawImage2d<'_, u8> { // Load image using the image crate: let image = image::load_from_memory(png_data).unwrap().to_rgba8(); let image_dimensions = image.dimensions(); diff --git a/egui_glow/examples/pure_glow.rs b/egui_glow/examples/pure_glow.rs index 06abc5c29..173654b86 100644 --- a/egui_glow/examples/pure_glow.rs +++ b/egui_glow/examples/pure_glow.rs @@ -1,6 +1,7 @@ //! Example how to use pure `egui_glow` without [`epi`]. #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(unsafe_code)] fn main() { let mut clear_color = [0.1, 0.1, 0.1]; From 15254f8235e5905b1c61c9f29aff30ce22db8141 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 21 Mar 2022 22:20:37 +0100 Subject: [PATCH 20/32] Remove the single_threaded/multi_threaded feature flags (#1390) Always use parking_lot for mutexes, i.e. always be multi-threaded. Closes #1379 --- ARCHITECTURE.md | 2 +- CHANGELOG.md | 6 +++- Cargo.lock | 7 ---- README.md | 2 +- egui-winit/Cargo.toml | 1 - egui/Cargo.toml | 7 +--- egui_extras/Cargo.toml | 6 ++-- egui_glium/Cargo.toml | 1 - egui_glow/Cargo.toml | 1 - egui_web/Cargo.toml | 1 - epaint/CHANGELOG.md | 1 + epaint/Cargo.toml | 11 ++---- epaint/src/mutex.rs | 81 ++---------------------------------------- epi/Cargo.toml | 4 +-- sh/check.sh | 10 +++--- 15 files changed, 21 insertions(+), 120 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 5381d3228..397d7fd36 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -19,7 +19,7 @@ Examples: `Vec2, Pos2, Rect, lerp, remap` Example: `Shape::Circle { center, radius, fill, stroke }` -Depends on `emath`, [`ab_glyph`](https://crates.io/crates/ab_glyph), [`atomic_refcell`](https://crates.io/crates/atomic_refcell), [`ahash`](https://crates.io/crates/ahash). +Depends on `emath`. ### `egui_extras` This adds additional features on top of `egui`. diff --git a/CHANGELOG.md b/CHANGELOG.md index f85192891..08a352abb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,11 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w ### Fixed 🐛 * Fixed ComboBoxes always being rendered left-aligned ([#1304](https://github.com/emilk/egui/pull/1304)). -* Fixed ui code that could lead to a deadlock ([#1380](https://github.com/emilk/egui/pull/1380)) +* Fixed ui code that could lead to a deadlock ([#1380](https://github.com/emilk/egui/pull/1380)). + +### Removed 🔥 +* Removed the `single_threaded/multi_threaded` flags - egui is now always thread-safe ([#1390](https://github.com/emilk/egui/pull/1390)). + ## 0.17.0 - 2022-02-22 - Improved font selection and image handling diff --git a/Cargo.lock b/Cargo.lock index 30384431c..7c1e66bf3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -203,12 +203,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "atomic_refcell" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b5e5f48b927f04e952dedc932f31995a65a0bf65ec971c74436e51bf6e970d" - [[package]] name = "atty" version = "0.2.14" @@ -1206,7 +1200,6 @@ version = "0.17.0" dependencies = [ "ab_glyph", "ahash 0.7.6", - "atomic_refcell", "bytemuck", "cint", "criterion", diff --git a/README.md b/README.md index 2b058698f..f8c4ef3a5 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ On Fedora Rawhide you need to run: * Extensible: [easy to write your own widgets for egui](https://github.com/emilk/egui/blob/master/egui_demo_lib/src/apps/demo/toggle_switch.rs) * Modular: You should be able to use small parts of egui and combine them in new ways * Safe: there is no `unsafe` code in egui -* Minimal dependencies: [`ab_glyph`](https://crates.io/crates/ab_glyph) [`ahash`](https://crates.io/crates/ahash) [`atomic_refcell`](https://crates.io/crates/atomic_refcell), [`nohash-hasher`](https://crates.io/crates/nohash-hasher) +* Minimal dependencies: [`ab_glyph`](https://crates.io/crates/ab_glyph) [`ahash`](https://crates.io/crates/ahash) [`nohash-hasher`](https://crates.io/crates/nohash-hasher) [`parking_lot`](https://crates.io/crates/parking_lot) egui is *not* a framework. egui is a library you call into, not an environment you program for. diff --git a/egui-winit/Cargo.toml b/egui-winit/Cargo.toml index a7cde96f9..c27d9cca8 100644 --- a/egui-winit/Cargo.toml +++ b/egui-winit/Cargo.toml @@ -45,7 +45,6 @@ serialize = ["egui/serialize", "serde"] [dependencies] egui = { version = "0.17.0", path = "../egui", default-features = false, features = [ - "single_threaded", "tracing", ] } instant = { version = "0.1", features = ["wasm-bindgen"] } diff --git a/egui/Cargo.toml b/egui/Cargo.toml index b99087a0b..b51c8e700 100644 --- a/egui/Cargo.toml +++ b/egui/Cargo.toml @@ -20,7 +20,7 @@ all-features = true [features] -default = ["default_fonts", "single_threaded"] +default = ["default_fonts"] # add compatibility with https://crates.io/crates/cint cint = ["epaint/cint"] @@ -46,11 +46,6 @@ persistence = ["serde", "epaint/serialize", "ron"] # implement serde on most types. serialize = ["serde", "epaint/serialize"] -# multi_threaded is only needed if you plan to use the same egui::Context -# from multiple threads. It comes with a minor performance impact. -single_threaded = ["epaint/single_threaded"] -multi_threaded = ["epaint/multi_threaded"] - [dependencies] epaint = { version = "0.17.0", path = "../epaint", default-features = false } diff --git a/egui_extras/Cargo.toml b/egui_extras/Cargo.toml index 63c31f965..09ea707f3 100644 --- a/egui_extras/Cargo.toml +++ b/egui_extras/Cargo.toml @@ -27,9 +27,7 @@ svg = ["resvg", "tiny-skia", "usvg"] [dependencies] -egui = { version = "0.17.0", path = "../egui", default-features = false, features = [ - "single_threaded", -] } +egui = { version = "0.17.0", path = "../egui", default-features = false } parking_lot = "0.12" # Optional dependencies: @@ -37,7 +35,7 @@ parking_lot = "0.12" # Add support for loading images with the `image` crate. # You also need to ALSO opt-in to the image formats you want to support, like so: # image = { version = "0.24", features = ["jpeg", "png"] } -image = { version = "0.24", optional = true, default-features = false, features = [] } +image = { version = "0.24", optional = true, default-features = false } # svg feature resvg = { version = "0.22", optional = true } diff --git a/egui_glium/Cargo.toml b/egui_glium/Cargo.toml index b23a7a610..8ef557917 100644 --- a/egui_glium/Cargo.toml +++ b/egui_glium/Cargo.toml @@ -47,7 +47,6 @@ screen_reader = ["egui-winit/screen_reader"] [dependencies] egui = { version = "0.17.0", path = "../egui", default-features = false, features = [ "convert_bytemuck", - "single_threaded", ] } egui-winit = { version = "0.17.0", path = "../egui-winit", default-features = false } diff --git a/egui_glow/Cargo.toml b/egui_glow/Cargo.toml index 90dfa4862..fbf520261 100644 --- a/egui_glow/Cargo.toml +++ b/egui_glow/Cargo.toml @@ -57,7 +57,6 @@ winit = ["egui-winit", "glutin"] [dependencies] egui = { version = "0.17.0", path = "../egui", default-features = false, features = [ "convert_bytemuck", - "single_threaded", ] } epi = { version = "0.17.0", path = "../epi", optional = true } diff --git a/egui_web/Cargo.toml b/egui_web/Cargo.toml index 5994f9d1a..7f9b09fc9 100644 --- a/egui_web/Cargo.toml +++ b/egui_web/Cargo.toml @@ -43,7 +43,6 @@ screen_reader = ["tts"] [dependencies] egui = { version = "0.17.0", path = "../egui", default-features = false, features = [ "convert_bytemuck", - "single_threaded", "tracing", ] } egui_glow = { version = "0.17.0", path = "../egui_glow", default-features = false } diff --git a/epaint/CHANGELOG.md b/epaint/CHANGELOG.md index 9a3007048..ec709f696 100644 --- a/epaint/CHANGELOG.md +++ b/epaint/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to the epaint crate will be documented in this file. ## Unreleased * Add `Shape::Callback` for backend-specific painting ([#1351](https://github.com/emilk/egui/pull/1351)). +* Removed the `single_threaded/multi_threaded` flags - epaint is now always thread-safe ([#1390](https://github.com/emilk/egui/pull/1390)). ## 0.17.0 - 2022-02-22 diff --git a/epaint/Cargo.toml b/epaint/Cargo.toml index 4c6ef6a21..3b42c7882 100644 --- a/epaint/Cargo.toml +++ b/epaint/Cargo.toml @@ -27,7 +27,7 @@ all-features = true [features] -default = ["default_fonts", "multi_threaded"] +default = ["default_fonts"] # implement bytemuck on most types. convert_bytemuck = ["bytemuck", "emath/bytemuck"] @@ -47,23 +47,16 @@ mint = ["emath/mint"] # implement serde on most types. serialize = ["serde", "ahash/serde", "emath/serde"] -single_threaded = ["atomic_refcell"] - -# Only needed if you plan to use the same fonts from multiple threads. -# It comes with a minor performance impact. -multi_threaded = ["parking_lot"] - [dependencies] emath = { version = "0.17.0", path = "../emath" } ab_glyph = "0.2.11" ahash = { version = "0.7", default-features = false, features = ["std"] } -atomic_refcell = { version = "0.1", optional = true } # Used instead of parking_lot when you are always using epaint in a single thread. About as fast as parking_lot. Panics on multi-threaded use. bytemuck = { version = "1.7.2", optional = true, features = ["derive"] } cint = { version = "^0.2.2", optional = true } nohash-hasher = "0.2" -parking_lot = { version = "0.12", optional = true } # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios. +parking_lot = "0.12" # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios. serde = { version = "1", optional = true, features = ["derive", "rc"] } [dev-dependencies] diff --git a/epaint/src/mutex.rs b/epaint/src/mutex.rs index fa0e939e1..1d73ff7d7 100644 --- a/epaint/src/mutex.rs +++ b/epaint/src/mutex.rs @@ -1,16 +1,10 @@ //! Helper module that wraps some Mutex types with different implementations. -//! -//! When the `single_threaded` feature is on the mutexes will panic when locked from different threads. - -#[cfg(not(any(feature = "single_threaded", feature = "multi_threaded")))] -compile_error!("Either feature \"single_threaded\" or \"multi_threaded\" must be enabled."); // ---------------------------------------------------------------------------- -#[cfg(feature = "multi_threaded")] #[cfg(not(debug_assertions))] mod mutex_impl { - /// Provides interior mutability. Only thread-safe if the `multi_threaded` feature is enabled. + /// Provides interior mutability. #[derive(Default)] pub struct Mutex(parking_lot::Mutex); @@ -30,10 +24,9 @@ mod mutex_impl { } } -#[cfg(feature = "multi_threaded")] #[cfg(debug_assertions)] mod mutex_impl { - /// Provides interior mutability. Only thread-safe if the `multi_threaded` feature is enabled. + /// Provides interior mutability. #[derive(Default)] pub struct Mutex(parking_lot::Mutex); @@ -111,7 +104,6 @@ mod mutex_impl { } } -#[cfg(feature = "multi_threaded")] mod rw_lock_impl { /// The lock you get from [`RwLock::read`]. pub use parking_lot::MappedRwLockReadGuard as RwLockReadGuard; @@ -119,7 +111,7 @@ mod rw_lock_impl { /// The lock you get from [`RwLock::write`]. pub use parking_lot::MappedRwLockWriteGuard as RwLockWriteGuard; - /// Provides interior mutability. Only thread-safe if the `multi_threaded` feature is enabled. + /// Provides interior mutability. #[derive(Default)] pub struct RwLock(parking_lot::RwLock); @@ -141,79 +133,12 @@ mod rw_lock_impl { } } -#[cfg(feature = "multi_threaded")] mod arc_impl { pub use std::sync::Arc; } // ---------------------------------------------------------------------------- -#[cfg(not(feature = "multi_threaded"))] -mod mutex_impl { - // `atomic_refcell` will panic if multiple threads try to access the same value - - /// Provides interior mutability. Only thread-safe if the `multi_threaded` feature is enabled. - #[derive(Default)] - pub struct Mutex(atomic_refcell::AtomicRefCell); - - /// The lock you get from [`Mutex`]. - pub use atomic_refcell::AtomicRefMut as MutexGuard; - - impl Mutex { - #[inline(always)] - pub fn new(val: T) -> Self { - Self(atomic_refcell::AtomicRefCell::new(val)) - } - - /// Panics if already locked. - #[inline(always)] - pub fn lock(&self) -> MutexGuard<'_, T> { - self.0.borrow_mut() - } - } -} - -#[cfg(not(feature = "multi_threaded"))] -mod rw_lock_impl { - // `atomic_refcell` will panic if multiple threads try to access the same value - - /// The lock you get from [`RwLock::read`]. - pub use atomic_refcell::AtomicRef as RwLockReadGuard; - - /// The lock you get from [`RwLock::write`]. - pub use atomic_refcell::AtomicRefMut as RwLockWriteGuard; - - /// Provides interior mutability. Only thread-safe if the `multi_threaded` feature is enabled. - #[derive(Default)] - pub struct RwLock(atomic_refcell::AtomicRefCell); - - impl RwLock { - #[inline(always)] - pub fn new(val: T) -> Self { - Self(atomic_refcell::AtomicRefCell::new(val)) - } - - #[inline(always)] - pub fn read(&self) -> RwLockReadGuard<'_, T> { - self.0.borrow() - } - - /// Panics if already locked. - #[inline(always)] - pub fn write(&self) -> RwLockWriteGuard<'_, T> { - self.0.borrow_mut() - } - } -} - -#[cfg(not(feature = "multi_threaded"))] -mod arc_impl { - // pub use std::rc::Rc as Arc; // TODO(emilk): optimize single threaded code by using `Rc` instead of `Arc`. - pub use std::sync::Arc; -} - -// ---------------------------------------------------------------------------- - pub use arc_impl::Arc; pub use mutex_impl::{Mutex, MutexGuard}; pub use rw_lock_impl::{RwLock, RwLockReadGuard, RwLockWriteGuard}; diff --git a/epi/Cargo.toml b/epi/Cargo.toml index 4e894a621..a62005eb0 100644 --- a/epi/Cargo.toml +++ b/epi/Cargo.toml @@ -28,9 +28,7 @@ persistence = ["ron", "serde", "egui/persistence"] [dependencies] -egui = { version = "0.17.0", path = "../egui", default-features = false, features = [ - "single_threaded", -] } +egui = { version = "0.17.0", path = "../egui", default-features = false } glow = "0.11" tracing = "0.1" diff --git a/sh/check.sh b/sh/check.sh index e2d8bd35b..2a319cf41 100755 --- a/sh/check.sh +++ b/sh/check.sh @@ -20,17 +20,15 @@ cargo doc -p egui_web --target wasm32-unknown-unknown --lib --no-deps --all-feat cargo doc --document-private-items --no-deps --all-features (cd emath && cargo check --no-default-features) -(cd epaint && cargo check --no-default-features --features "single_threaded") -(cd epaint && cargo check --no-default-features --features "multi_threaded") -(cd epaint && cargo check --no-default-features --features "single_threaded" --release) -(cd epaint && cargo check --no-default-features --features "multi_threaded" --release) -(cd egui && cargo check --no-default-features --features "multi_threaded,serialize") +(cd epaint && cargo check --no-default-features) +(cd epaint && cargo check --no-default-features --release) +(cd egui && cargo check --no-default-features --features "serialize") (cd eframe && cargo check --no-default-features) (cd epi && cargo check --no-default-features) (cd egui_demo_lib && cargo check --no-default-features) (cd egui_extras && cargo check --no-default-features) (cd egui_web && cargo check --no-default-features) -# (cd egui-winit && cargo check --no-default-features) # we don't pick singlethreaded or multithreaded +(cd egui-winit && cargo check --no-default-features) (cd egui_glium && cargo check --no-default-features) (cd egui_glow && cargo check --no-default-features) From 805539b50d1afdbc05518ef05e828c7638fdfacc Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 21 Mar 2022 22:20:58 +0100 Subject: [PATCH 21/32] Add example of custom window frame for native window using eframe (#1396) --- eframe/examples/custom_window_frame.rs | 117 +++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 eframe/examples/custom_window_frame.rs diff --git a/eframe/examples/custom_window_frame.rs b/eframe/examples/custom_window_frame.rs new file mode 100644 index 000000000..9f5dd2229 --- /dev/null +++ b/eframe/examples/custom_window_frame.rs @@ -0,0 +1,117 @@ +//! Show a custom window frame instead of the default OS window chrome decorations. + +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release + +use eframe::egui; + +fn main() { + let options = eframe::NativeOptions { + // Hide the OS-specific "chrome" around the window: + decorated: false, + // To have rounded corners we need transparency: + transparent: true, + min_window_size: Some(egui::vec2(320.0, 100.0)), + ..Default::default() + }; + eframe::run_native( + "Custom window frame", // unused title + options, + Box::new(|_cc| Box::new(MyApp::default())), + ); +} + +#[derive(Default)] +struct MyApp {} + +impl eframe::App for MyApp { + fn clear_color(&self) -> egui::Rgba { + egui::Rgba::TRANSPARENT // Make sure we don't paint anything behind the rounded corners + } + + fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) { + custon_window_frame(ctx, frame, "egui with custom frame", |ui| { + ui.label("This is just the contents of the window"); + ui.horizontal(|ui| { + ui.label("egui theme:"); + egui::widgets::global_dark_light_mode_buttons(ui); + }); + }); + } +} + +fn custon_window_frame( + ctx: &egui::Context, + frame: &eframe::Frame, + title: &str, + add_contents: impl FnOnce(&mut egui::Ui), +) { + use egui::*; + let text_color = ctx.style().visuals.text_color(); + + // Height of the title bar + let height = 28.0; + + CentralPanel::default() + .frame(Frame::none()) + .show(ctx, |ui| { + let rect = ui.max_rect(); + let painter = ui.painter(); + + // Paint the frame: + painter.rect( + rect.shrink(1.0), + 10.0, + ctx.style().visuals.window_fill(), + Stroke::new(1.0, text_color), + ); + + // Paint the title: + painter.text( + rect.center_top() + vec2(0.0, height / 2.0), + Align2::CENTER_CENTER, + title, + FontId::proportional(height - 2.0), + text_color, + ); + + // Paint the line under the title: + painter.line_segment( + [ + rect.left_top() + vec2(2.0, height), + rect.right_top() + vec2(-2.0, height), + ], + Stroke::new(1.0, text_color), + ); + + // Add the close button: + let close_response = ui.put( + Rect::from_min_size(rect.left_top(), Vec2::splat(height)), + Button::new(RichText::new("❌").size(height - 4.0)).frame(false), + ); + if close_response.clicked() { + frame.quit(); + } + + // Interact with the title bar (drag to move window): + let title_bar_rect = { + let mut rect = rect; + rect.max.y = rect.min.y + height; + rect + }; + let title_bar_response = + ui.interact(title_bar_rect, Id::new("title_bar"), Sense::drag()); + if title_bar_response.drag_started() { + frame.drag_window(); + } + + // Add the contents: + let content_rect = { + let mut rect = rect; + rect.min.y = title_bar_rect.max.y; + rect + } + .shrink(4.0); + let mut content_ui = ui.child_ui(content_rect, *ui.layout()); + add_contents(&mut content_ui); + }); +} From 0a400a5bcc460aa325e5617813b1710e69e75d47 Mon Sep 17 00:00:00 2001 From: Hunter522 Date: Tue, 22 Mar 2022 02:44:23 -0500 Subject: [PATCH 22/32] Add Image::rotate and Mesh::rotate (#1371) Co-authored-by: Hunter Morgan --- eframe/examples/image.rs | 11 ++++++++++- egui/src/widgets/image.rs | 20 +++++++++++++++++++- epaint/src/mesh.rs | 9 +++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/eframe/examples/image.rs b/eframe/examples/image.rs index 5b890220c..0edcf6050 100644 --- a/eframe/examples/image.rs +++ b/eframe/examples/image.rs @@ -4,7 +4,10 @@ use eframe::egui; use egui_extras::RetainedImage; fn main() { - let options = eframe::NativeOptions::default(); + let options = eframe::NativeOptions { + initial_window_size: Some(egui::vec2(500.0, 900.0)), + ..Default::default() + }; eframe::run_native( "Show an image with eframe/egui", options, @@ -34,6 +37,12 @@ impl eframe::App for MyApp { ui.heading("This is an image:"); self.image.show(ui); + ui.heading("This is a rotated image:"); + ui.add( + egui::Image::new(self.image.texture_id(ctx), self.image.size_vec2()) + .rotate(45.0_f32.to_radians(), egui::Vec2::splat(0.5)), + ); + ui.heading("This is an image you can click:"); ui.add(egui::ImageButton::new( self.image.texture_id(ctx), diff --git a/egui/src/widgets/image.rs b/egui/src/widgets/image.rs index 61ea0d9a7..889fbdc96 100644 --- a/egui/src/widgets/image.rs +++ b/egui/src/widgets/image.rs @@ -1,4 +1,5 @@ use crate::*; +use emath::Rot2; /// An widget to show an image of a given size. /// @@ -36,6 +37,7 @@ pub struct Image { bg_fill: Color32, tint: Color32, sense: Sense, + rotation: Option<(Rot2, Vec2)>, } impl Image { @@ -47,6 +49,7 @@ impl Image { bg_fill: Default::default(), tint: Color32::WHITE, sense: Sense::hover(), + rotation: None, } } @@ -75,6 +78,17 @@ impl Image { self.sense = sense; self } + + /// Rotate the image about an origin by some angle + /// + /// Positive angle is clockwise. + /// Origin is a vector in normalized UV space ((0,0) in top-left, (1,1) bottom right). + /// + /// To rotate about the center you can pass `Vec2::splat(0.5)` as the origin. + pub fn rotate(mut self, angle: f32, origin: Vec2) -> Self { + self.rotation = Some((Rot2::from_angle(angle), origin)); + self + } } impl Image { @@ -88,10 +102,11 @@ impl Image { let Self { texture_id, uv, - size: _, + size, bg_fill, tint, sense: _, + rotation, } = self; if *bg_fill != Default::default() { @@ -104,6 +119,9 @@ impl Image { // TODO: builder pattern for Mesh let mut mesh = Mesh::with_texture(*texture_id); mesh.add_rect_with_uv(rect, *uv, *tint); + if let Some((rot, origin)) = rotation { + mesh.rotate(*rot, rect.min + *origin * *size); + } ui.painter().add(Shape::mesh(mesh)); } } diff --git a/epaint/src/mesh.rs b/epaint/src/mesh.rs index 7a5e0dcdb..c14f41fa1 100644 --- a/epaint/src/mesh.rs +++ b/epaint/src/mesh.rs @@ -254,6 +254,15 @@ impl Mesh { v.pos += delta; } } + + /// Rotate by some angle about an origin, in-place. + /// + /// Origin is a position in screen space. + pub fn rotate(&mut self, rot: Rot2, origin: Pos2) { + for v in &mut self.vertices { + v.pos = origin + rot * (v.pos - origin); + } + } } // ---------------------------------------------------------------------------- From e5aeb1618f71581a051257130012ad1251ee7559 Mon Sep 17 00:00:00 2001 From: Edgeworth <140149+Edgeworth@users.noreply.github.com> Date: Tue, 22 Mar 2022 16:59:13 +0900 Subject: [PATCH 23/32] Export the PlotBounds type. (#1392) --- egui/src/widgets/plot/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/egui/src/widgets/plot/mod.rs b/egui/src/widgets/plot/mod.rs index 3885fc117..f4b7e343b 100644 --- a/egui/src/widgets/plot/mod.rs +++ b/egui/src/widgets/plot/mod.rs @@ -8,13 +8,14 @@ use epaint::color::Hsva; use epaint::util::FloatOrd; use items::PlotItem; use legend::LegendWidget; -use transform::{PlotBounds, ScreenTransform}; +use transform::ScreenTransform; pub use items::{ Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, HLine, Line, LineStyle, MarkerShape, Orientation, PlotImage, Points, Polygon, Text, VLine, Value, Values, }; pub use legend::{Corner, Legend}; +pub use transform::PlotBounds; mod items; mod legend; From 41b178b6ec5919e83a44d0bde7dffe74f95924e3 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 22 Mar 2022 15:34:21 +0100 Subject: [PATCH 24/32] Use atomic_refcell instead of parking_lot for wasm32 targets (#1404) Closes https://github.com/emilk/egui/issues/1401 --- Cargo.lock | 10 +++-- eframe/Cargo.toml | 1 - eframe/examples/custom_3d.rs | 2 +- egui_extras/Cargo.toml | 1 - egui_extras/src/image.rs | 2 +- egui_glow/Cargo.toml | 1 - egui_glow/src/epi_backend.rs | 2 +- epaint/Cargo.toml | 10 ++++- epaint/src/mutex.rs | 80 ++++++++++++++++++++++++++++++++++++ 9 files changed, 99 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7c1e66bf3..a7f5b1e13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -203,6 +203,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "atomic_refcell" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b5e5f48b927f04e952dedc932f31995a65a0bf65ec971c74436e51bf6e970d" + [[package]] name = "atty" version = "0.2.14" @@ -987,7 +993,6 @@ dependencies = [ "epi", "glow", "image", - "parking_lot 0.12.0", "poll-promise", "rfd", ] @@ -1057,7 +1062,6 @@ version = "0.17.0" dependencies = [ "egui", "image", - "parking_lot 0.12.0", "resvg", "tiny-skia", "usvg", @@ -1086,7 +1090,6 @@ dependencies = [ "glow", "glutin", "memoffset", - "parking_lot 0.12.0", "tracing", "wasm-bindgen", "web-sys", @@ -1200,6 +1203,7 @@ version = "0.17.0" dependencies = [ "ab_glyph", "ahash 0.7.6", + "atomic_refcell", "bytemuck", "cint", "criterion", diff --git a/eframe/Cargo.toml b/eframe/Cargo.toml index a502ce465..0c1e0b63e 100644 --- a/eframe/Cargo.toml +++ b/eframe/Cargo.toml @@ -71,6 +71,5 @@ image = { version = "0.24", default-features = false, features = [ "jpeg", "png", ] } -parking_lot = "0.12" poll-promise = "0.1" rfd = "0.8" diff --git a/eframe/examples/custom_3d.rs b/eframe/examples/custom_3d.rs index 6c4b5e6f0..f86d444e2 100644 --- a/eframe/examples/custom_3d.rs +++ b/eframe/examples/custom_3d.rs @@ -11,7 +11,7 @@ use eframe::egui; -use parking_lot::Mutex; +use egui::mutex::Mutex; use std::sync::Arc; fn main() { diff --git a/egui_extras/Cargo.toml b/egui_extras/Cargo.toml index 09ea707f3..7988b3408 100644 --- a/egui_extras/Cargo.toml +++ b/egui_extras/Cargo.toml @@ -28,7 +28,6 @@ svg = ["resvg", "tiny-skia", "usvg"] [dependencies] egui = { version = "0.17.0", path = "../egui", default-features = false } -parking_lot = "0.12" # Optional dependencies: diff --git a/egui_extras/src/image.rs b/egui_extras/src/image.rs index 4015acd30..b9001f201 100644 --- a/egui_extras/src/image.rs +++ b/egui_extras/src/image.rs @@ -1,4 +1,4 @@ -use parking_lot::Mutex; +use egui::mutex::Mutex; /// An image to be shown in egui. /// diff --git a/egui_glow/Cargo.toml b/egui_glow/Cargo.toml index fbf520261..717d8d7bc 100644 --- a/egui_glow/Cargo.toml +++ b/egui_glow/Cargo.toml @@ -63,7 +63,6 @@ epi = { version = "0.17.0", path = "../epi", optional = true } bytemuck = "1.7" glow = "0.11" memoffset = "0.6" -parking_lot = "0.12" tracing = "0.1" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/egui_glow/src/epi_backend.rs b/egui_glow/src/epi_backend.rs index 726116732..2e16e7ae7 100644 --- a/egui_glow/src/epi_backend.rs +++ b/egui_glow/src/epi_backend.rs @@ -54,7 +54,7 @@ pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi ); { - let event_loop_proxy = parking_lot::Mutex::new(event_loop.create_proxy()); + let event_loop_proxy = egui::mutex::Mutex::new(event_loop.create_proxy()); integration.egui_ctx.set_request_repaint_callback(move || { event_loop_proxy.lock().send_event(RequestRepaintEvent).ok(); }); diff --git a/epaint/Cargo.toml b/epaint/Cargo.toml index 3b42c7882..5316397e0 100644 --- a/epaint/Cargo.toml +++ b/epaint/Cargo.toml @@ -56,9 +56,17 @@ ahash = { version = "0.7", default-features = false, features = ["std"] } bytemuck = { version = "1.7.2", optional = true, features = ["derive"] } cint = { version = "^0.2.2", optional = true } nohash-hasher = "0.2" -parking_lot = "0.12" # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios. serde = { version = "1", optional = true, features = ["derive", "rc"] } +# native: +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +parking_lot = "0.12" # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios. + +# web: +[target.'cfg(target_arch = "wasm32")'.dependencies] +atomic_refcell = "0.1" # Used instead of parking_lot on on wasm. See https://github.com/emilk/egui/issues/1401 + + [dev-dependencies] criterion = { version = "0.3", default-features = false } diff --git a/epaint/src/mutex.rs b/epaint/src/mutex.rs index 1d73ff7d7..b3639d199 100644 --- a/epaint/src/mutex.rs +++ b/epaint/src/mutex.rs @@ -2,9 +2,12 @@ // ---------------------------------------------------------------------------- +#[cfg(not(target_arch = "wasm32"))] #[cfg(not(debug_assertions))] mod mutex_impl { /// Provides interior mutability. + /// + /// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets. #[derive(Default)] pub struct Mutex(parking_lot::Mutex); @@ -24,9 +27,12 @@ mod mutex_impl { } } +#[cfg(not(target_arch = "wasm32"))] #[cfg(debug_assertions)] mod mutex_impl { /// Provides interior mutability. + /// + /// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets. #[derive(Default)] pub struct Mutex(parking_lot::Mutex); @@ -104,6 +110,7 @@ mod mutex_impl { } } +#[cfg(not(target_arch = "wasm32"))] mod rw_lock_impl { /// The lock you get from [`RwLock::read`]. pub use parking_lot::MappedRwLockReadGuard as RwLockReadGuard; @@ -112,6 +119,8 @@ mod rw_lock_impl { pub use parking_lot::MappedRwLockWriteGuard as RwLockWriteGuard; /// Provides interior mutability. + /// + /// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets. #[derive(Default)] pub struct RwLock(parking_lot::RwLock); @@ -133,12 +142,83 @@ mod rw_lock_impl { } } +#[cfg(not(target_arch = "wasm32"))] mod arc_impl { pub use std::sync::Arc; } // ---------------------------------------------------------------------------- +#[cfg(target_arch = "wasm32")] +mod mutex_impl { + // `atomic_refcell` will panic if multiple threads try to access the same value + + /// Provides interior mutability. + /// + /// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets. + #[derive(Default)] + pub struct Mutex(atomic_refcell::AtomicRefCell); + + /// The lock you get from [`Mutex`]. + pub use atomic_refcell::AtomicRefMut as MutexGuard; + + impl Mutex { + #[inline(always)] + pub fn new(val: T) -> Self { + Self(atomic_refcell::AtomicRefCell::new(val)) + } + + /// Panics if already locked. + #[inline(always)] + pub fn lock(&self) -> MutexGuard<'_, T> { + self.0.borrow_mut() + } + } +} + +#[cfg(target_arch = "wasm32")] +mod rw_lock_impl { + // `atomic_refcell` will panic if multiple threads try to access the same value + + /// The lock you get from [`RwLock::read`]. + pub use atomic_refcell::AtomicRef as RwLockReadGuard; + + /// The lock you get from [`RwLock::write`]. + pub use atomic_refcell::AtomicRefMut as RwLockWriteGuard; + + /// Provides interior mutability. + /// + /// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets. + #[derive(Default)] + pub struct RwLock(atomic_refcell::AtomicRefCell); + + impl RwLock { + #[inline(always)] + pub fn new(val: T) -> Self { + Self(atomic_refcell::AtomicRefCell::new(val)) + } + + #[inline(always)] + pub fn read(&self) -> RwLockReadGuard<'_, T> { + self.0.borrow() + } + + /// Panics if already locked. + #[inline(always)] + pub fn write(&self) -> RwLockWriteGuard<'_, T> { + self.0.borrow_mut() + } + } +} + +#[cfg(target_arch = "wasm32")] +mod arc_impl { + // pub use std::rc::Rc as Arc; // TODO(emilk): optimize single threaded code by using `Rc` instead of `Arc`. + pub use std::sync::Arc; +} + +// ---------------------------------------------------------------------------- + pub use arc_impl::Arc; pub use mutex_impl::{Mutex, MutexGuard}; pub use rw_lock_impl::{RwLock, RwLockReadGuard, RwLockWriteGuard}; From 6f10e2e7255d60e8948a1617e91a15fb309347a2 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 22 Mar 2022 16:04:06 +0100 Subject: [PATCH 25/32] Improve glow error reporting (#1403) * Improve glow error reporting * Add more check_for_gl_error calls * Remove clippy lint list from egui_glow lib.rs - Forgotten in https://github.com/emilk/egui/pull/1394 * egui_glow: move vao code to own file * Cleanup: `use glow::HasContext as _;` Co-authored-by: Zachary Kohnen --- egui_glow/src/lib.rs | 140 +++++++++++------------- egui_glow/src/misc_util.rs | 116 +------------------- egui_glow/src/painter.rs | 45 +++++--- egui_glow/src/post_process.rs | 21 ++-- egui_glow/src/shader_version.rs | 2 +- egui_glow/src/vao.rs | 185 ++++++++++++++++++++++++++++++++ egui_glow/src/vao_emulate.rs | 57 ---------- 7 files changed, 288 insertions(+), 278 deletions(-) create mode 100644 egui_glow/src/vao.rs delete mode 100644 egui_glow/src/vao_emulate.rs diff --git a/egui_glow/src/lib.rs b/egui_glow/src/lib.rs index 227b65276..29a6cf2bf 100644 --- a/egui_glow/src/lib.rs +++ b/egui_glow/src/lib.rs @@ -5,85 +5,6 @@ //! This library is an [`epi`] backend. //! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead. -// Forbid warnings in release builds: -#![cfg_attr(not(debug_assertions), deny(warnings))] -#![deny(unsafe_code)] -#![warn( - clippy::all, - clippy::await_holding_lock, - clippy::char_lit_as_u8, - clippy::checked_conversions, - clippy::dbg_macro, - clippy::debug_assert_with_mut_call, - clippy::disallowed_method, - clippy::doc_markdown, - clippy::empty_enum, - clippy::enum_glob_use, - clippy::exit, - clippy::expl_impl_clone_on_copy, - clippy::explicit_deref_methods, - clippy::explicit_into_iter_loop, - clippy::fallible_impl_from, - clippy::filter_map_next, - clippy::flat_map_option, - clippy::float_cmp_const, - clippy::fn_params_excessive_bools, - clippy::from_iter_instead_of_collect, - clippy::if_let_mutex, - clippy::implicit_clone, - clippy::imprecise_flops, - clippy::inefficient_to_string, - clippy::invalid_upcast_comparisons, - clippy::large_digit_groups, - clippy::large_stack_arrays, - clippy::large_types_passed_by_value, - clippy::let_unit_value, - clippy::linkedlist, - clippy::lossy_float_literal, - clippy::macro_use_imports, - clippy::manual_ok_or, - clippy::map_err_ignore, - clippy::map_flatten, - clippy::map_unwrap_or, - clippy::match_on_vec_items, - clippy::match_same_arms, - clippy::match_wild_err_arm, - clippy::match_wildcard_for_single_variants, - clippy::mem_forget, - clippy::mismatched_target_os, - clippy::missing_errors_doc, - clippy::missing_safety_doc, - clippy::mut_mut, - clippy::mutex_integer, - clippy::needless_borrow, - clippy::needless_continue, - clippy::needless_for_each, - clippy::needless_pass_by_value, - clippy::option_option, - clippy::path_buf_push_overwrite, - clippy::ptr_as_ptr, - clippy::ref_option_ref, - clippy::rest_pat_in_fully_bound_structs, - clippy::same_functions_in_if_condition, - clippy::semicolon_if_nothing_returned, - clippy::single_match_else, - clippy::string_add_assign, - clippy::string_add, - clippy::string_lit_as_bytes, - clippy::string_to_string, - clippy::todo, - clippy::trait_duplication_in_bounds, - clippy::unimplemented, - clippy::unnested_or_patterns, - clippy::unused_self, - clippy::useless_transmute, - clippy::verbose_file_reads, - clippy::zero_sized_map_values, - future_incompatible, - nonstandard_style, - rust_2018_idioms, - rustdoc::missing_crate_level_docs -)] #![allow(clippy::float_cmp)] #![allow(clippy::manual_range_contains)] @@ -93,7 +14,7 @@ pub use painter::Painter; mod misc_util; mod post_process; mod shader_version; -mod vao_emulate; +mod vao; #[cfg(all(not(target_arch = "wasm32"), feature = "winit"))] pub mod winit; @@ -105,3 +26,62 @@ mod epi_backend; #[cfg(all(not(target_arch = "wasm32"), feature = "winit"))] pub use epi_backend::{run, NativeOptions}; + +/// Check for OpenGL error and report it using `tracing::error`. +/// +/// ``` no_run +/// # let glow_context = todo!(); +/// use egui_glow::check_for_gl_error; +/// check_for_gl_error!(glow_context); +/// check_for_gl_error!(glow_context, "during painting"); +/// ``` +#[macro_export] +macro_rules! check_for_gl_error { + ($gl: expr) => {{ + $crate::check_for_gl_error_impl($gl, file!(), line!(), "") + }}; + ($gl: expr, $context: literal) => {{ + $crate::check_for_gl_error_impl($gl, file!(), line!(), $context) + }}; +} + +#[doc(hidden)] +pub fn check_for_gl_error_impl(gl: &glow::Context, file: &str, line: u32, context: &str) { + use glow::HasContext as _; + #[allow(unsafe_code)] + let error_code = unsafe { gl.get_error() }; + if error_code != glow::NO_ERROR { + let error_str = match error_code { + glow::INVALID_ENUM => "GL_INVALID_ENUM", + glow::INVALID_VALUE => "GL_INVALID_VALUE", + glow::INVALID_OPERATION => "GL_INVALID_OPERATION", + glow::STACK_OVERFLOW => "GL_STACK_OVERFLOW", + glow::STACK_UNDERFLOW => "GL_STACK_UNDERFLOW", + glow::OUT_OF_MEMORY => "GL_OUT_OF_MEMORY", + glow::INVALID_FRAMEBUFFER_OPERATION => "GL_INVALID_FRAMEBUFFER_OPERATION", + glow::CONTEXT_LOST => "GL_CONTEXT_LOST", + 0x8031 => "GL_TABLE_TOO_LARGE1", + 0x9242 => "CONTEXT_LOST_WEBGL", + _ => "", + }; + + if context.is_empty() { + tracing::error!( + "GL error, at {}:{}: {} (0x{:X}). Please file a bug at https://github.com/emilk/egui/issues", + file, + line, + error_str, + error_code, + ); + } else { + tracing::error!( + "GL error, at {}:{} ({}): {} (0x{:X}). Please file a bug at https://github.com/emilk/egui/issues", + file, + line, + context, + error_str, + error_code, + ); + } + } +} diff --git a/egui_glow/src/misc_util.rs b/egui_glow/src/misc_util.rs index 4547c2618..ee22f58bd 100644 --- a/egui_glow/src/misc_util.rs +++ b/egui_glow/src/misc_util.rs @@ -1,18 +1,6 @@ #![allow(unsafe_code)] -use glow::HasContext; -use std::option::Option::Some; -pub fn check_for_gl_error(gl: &glow::Context, context: &str) { - let error_code = unsafe { gl.get_error() }; - if error_code != glow::NO_ERROR { - tracing::error!( - "GL error, at: '{}', code: {} (0x{:X})", - context, - error_code, - error_code - ); - } -} +use glow::HasContext as _; pub(crate) unsafe fn compile_shader( gl: &glow::Context, @@ -50,105 +38,3 @@ pub(crate) unsafe fn link_program<'a, T: IntoIterator>( Err(gl.get_program_info_log(program)) } } -///Wrapper around Emulated VAO and GL's VAO -pub(crate) enum VAO { - Emulated(crate::vao_emulate::EmulatedVao), - Native(crate::glow::VertexArray), -} - -impl VAO { - pub(crate) unsafe fn native(gl: &glow::Context) -> Self { - Self::Native(gl.create_vertex_array().unwrap()) - } - - pub(crate) unsafe fn emulated() -> Self { - Self::Emulated(crate::vao_emulate::EmulatedVao::new()) - } - - pub(crate) unsafe fn bind_vertex_array(&self, gl: &glow::Context) { - match self { - VAO::Emulated(vao) => vao.bind_vertex_array(gl), - VAO::Native(vao) => gl.bind_vertex_array(Some(*vao)), - } - } - - pub(crate) unsafe fn bind_buffer(&mut self, gl: &glow::Context, buffer: &glow::Buffer) { - match self { - VAO::Emulated(vao) => vao.bind_buffer(buffer), - VAO::Native(_) => gl.bind_buffer(glow::ARRAY_BUFFER, Some(*buffer)), - } - } - - pub(crate) unsafe fn add_new_attribute( - &mut self, - gl: &glow::Context, - buffer_info: crate::vao_emulate::BufferInfo, - ) { - match self { - VAO::Emulated(vao) => vao.add_new_attribute(buffer_info), - VAO::Native(_) => { - gl.vertex_attrib_pointer_f32( - buffer_info.location, - buffer_info.vector_size, - buffer_info.data_type, - buffer_info.normalized, - buffer_info.stride, - buffer_info.offset, - ); - gl.enable_vertex_attrib_array(buffer_info.location); - } - } - } - - pub(crate) unsafe fn unbind_vertex_array(&self, gl: &glow::Context) { - match self { - VAO::Emulated(vao) => vao.unbind_vertex_array(gl), - VAO::Native(_) => { - gl.bind_vertex_array(None); - } - } - } -} - -/// If returned true no need to emulate vao -pub(crate) fn supports_vao(gl: &glow::Context) -> bool { - const WEBGL_PREFIX: &str = "WebGL "; - const OPENGL_ES_PREFIX: &str = "OpenGL ES "; - - let version_string = unsafe { gl.get_parameter_string(glow::VERSION) }; - tracing::debug!("GL version: {:?}.", version_string); - - // Examples: - // * "WebGL 2.0 (OpenGL ES 3.0 Chromium)" - // * "WebGL 2.0" - - if let Some(pos) = version_string.rfind(WEBGL_PREFIX) { - let version_str = &version_string[pos + WEBGL_PREFIX.len()..]; - if version_str.contains("1.0") { - // need to test OES_vertex_array_object . - gl.supported_extensions() - .contains("OES_vertex_array_object") - } else { - true - } - } else if version_string.contains(OPENGL_ES_PREFIX) { - // glow targets es2.0+ so we don't concern about OpenGL ES-CM,OpenGL ES-CL - if version_string.contains("2.0") { - // need to test OES_vertex_array_object . - gl.supported_extensions() - .contains("OES_vertex_array_object") - } else { - true - } - } else { - // from OpenGL 3 vao into core - if version_string.starts_with('2') { - // I found APPLE_vertex_array_object , GL_ATI_vertex_array_object ,ARB_vertex_array_object - // but APPLE's and ATI's very old extension. - gl.supported_extensions() - .contains("ARB_vertex_array_object") - } else { - true - } - } -} diff --git a/egui_glow/src/painter.rs b/egui_glow/src/painter.rs index 804bc19ed..69d5123df 100644 --- a/egui_glow/src/painter.rs +++ b/egui_glow/src/painter.rs @@ -6,13 +6,14 @@ use egui::{ emath::Rect, epaint::{Color32, Mesh, Primitive, Vertex}, }; -use glow::HasContext; +use glow::HasContext as _; use memoffset::offset_of; -use crate::misc_util::{check_for_gl_error, compile_shader, link_program}; +use crate::check_for_gl_error; +use crate::misc_util::{compile_shader, link_program}; use crate::post_process::PostProcess; use crate::shader_version::ShaderVersion; -use crate::vao_emulate; +use crate::vao; pub use glow::Context; @@ -36,7 +37,7 @@ pub struct Painter { u_sampler: glow::UniformLocation, is_webgl_1: bool, is_embedded: bool, - vertex_array: crate::misc_util::VAO, + vertex_array: crate::vao::VAO, srgb_support: bool, /// The filter used for subsequent textures. texture_filter: TextureFilter, @@ -95,11 +96,15 @@ impl Painter { pp_fb_extent: Option<[i32; 2]>, shader_prefix: &str, ) -> Result { - check_for_gl_error(&gl, "before Painter::new"); + check_for_gl_error!(&gl, "before Painter::new"); let max_texture_side = unsafe { gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE) } as usize; - let support_vao = crate::misc_util::supports_vao(&gl); + let support_vao = crate::vao::supports_vao(&gl); + if !support_vao { + tracing::debug!("VAO not supported"); + } + let shader_version = ShaderVersion::get(&gl); let is_webgl_1 = shader_version == ShaderVersion::Es100; let header = shader_version.version(); @@ -175,14 +180,14 @@ impl Painter { let a_tc_loc = gl.get_attrib_location(program, "a_tc").unwrap(); let a_srgba_loc = gl.get_attrib_location(program, "a_srgba").unwrap(); let mut vertex_array = if support_vao { - crate::misc_util::VAO::native(&gl) + crate::vao::VAO::native(&gl) } else { - crate::misc_util::VAO::emulated() + crate::vao::VAO::emulated() }; vertex_array.bind_vertex_array(&gl); vertex_array.bind_buffer(&gl, &vertex_buffer); let stride = std::mem::size_of::() as i32; - let position_buffer_info = vao_emulate::BufferInfo { + let position_buffer_info = vao::BufferInfo { location: a_pos_loc, vector_size: 2, data_type: glow::FLOAT, @@ -190,7 +195,7 @@ impl Painter { stride, offset: offset_of!(Vertex, pos) as i32, }; - let tex_coord_buffer_info = vao_emulate::BufferInfo { + let tex_coord_buffer_info = vao::BufferInfo { location: a_tc_loc, vector_size: 2, data_type: glow::FLOAT, @@ -198,7 +203,7 @@ impl Painter { stride, offset: offset_of!(Vertex, uv) as i32, }; - let color_buffer_info = vao_emulate::BufferInfo { + let color_buffer_info = vao::BufferInfo { location: a_srgba_loc, vector_size: 4, data_type: glow::UNSIGNED_BYTE, @@ -209,7 +214,7 @@ impl Painter { vertex_array.add_new_attribute(&gl, position_buffer_info); vertex_array.add_new_attribute(&gl, tex_coord_buffer_info); vertex_array.add_new_attribute(&gl, color_buffer_info); - check_for_gl_error(&gl, "after Painter::new"); + check_for_gl_error!(&gl, "after Painter::new"); Ok(Painter { gl, @@ -266,7 +271,7 @@ impl Painter { if !cfg!(target_arch = "wasm32") { self.gl.enable(glow::FRAMEBUFFER_SRGB); - check_for_gl_error(&self.gl, "FRAMEBUFFER_SRGB"); + check_for_gl_error!(&self.gl, "FRAMEBUFFER_SRGB"); } let width_in_points = width_in_pixels as f32 / pixels_per_point; @@ -285,6 +290,8 @@ impl Painter { self.gl .bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_array_buffer)); + check_for_gl_error!(&self.gl, "prepare_painting"); + (width_in_pixels, height_in_pixels) } @@ -375,6 +382,8 @@ impl Painter { callback.call(self); + check_for_gl_error!(&self.gl, "callback"); + // Restore state: unsafe { if let Some(ref mut post_process) = self.post_process { @@ -396,7 +405,7 @@ impl Painter { self.gl.disable(glow::SCISSOR_TEST); - check_for_gl_error(&self.gl, "painting"); + check_for_gl_error!(&self.gl, "painting"); } } @@ -432,6 +441,8 @@ impl Painter { 0, ); } + + check_for_gl_error!(&self.gl, "paint_mesh"); } } @@ -526,7 +537,7 @@ impl Painter { glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32, ); - check_for_gl_error(&self.gl, "tex_parameter"); + check_for_gl_error!(&self.gl, "tex_parameter"); let (internal_format, src_format) = if self.is_webgl_1 { let format = if self.srgb_support { @@ -554,7 +565,7 @@ impl Painter { glow::UNSIGNED_BYTE, glow::PixelUnpackData::Slice(data), ); - check_for_gl_error(&self.gl, "tex_sub_image_2d"); + check_for_gl_error!(&self.gl, "tex_sub_image_2d"); } else { let border = 0; self.gl.tex_image_2d( @@ -568,7 +579,7 @@ impl Painter { glow::UNSIGNED_BYTE, Some(data), ); - check_for_gl_error(&self.gl, "tex_image_2d"); + check_for_gl_error!(&self.gl, "tex_image_2d"); } } } diff --git a/egui_glow/src/post_process.rs b/egui_glow/src/post_process.rs index 0706b2a60..97b2e0c97 100644 --- a/egui_glow/src/post_process.rs +++ b/egui_glow/src/post_process.rs @@ -1,7 +1,8 @@ #![allow(unsafe_code)] -use crate::misc_util::{check_for_gl_error, compile_shader, link_program}; -use crate::vao_emulate::BufferInfo; -use glow::HasContext; +use crate::check_for_gl_error; +use crate::misc_util::{compile_shader, link_program}; +use crate::vao::BufferInfo; +use glow::HasContext as _; /// Uses a framebuffer to render everything in linear color space and convert it back to `sRGB` /// in a separate "post processing" step @@ -9,7 +10,7 @@ pub(crate) struct PostProcess { gl: std::rc::Rc, pos_buffer: glow::Buffer, index_buffer: glow::Buffer, - vertex_array: crate::misc_util::VAO, + vertex_array: crate::vao::VAO, is_webgl_1: bool, texture: glow::Texture, texture_size: (i32, i32), @@ -77,7 +78,7 @@ impl PostProcess { glow::UNSIGNED_BYTE, None, ); - check_for_gl_error(&gl, "post process texture initialization"); + check_for_gl_error!(&gl, "post process texture initialization"); gl.framebuffer_texture_2d( glow::FRAMEBUFFER, @@ -125,9 +126,9 @@ impl PostProcess { .get_attrib_location(program, "a_pos") .ok_or_else(|| "failed to get location of a_pos".to_string())?; let mut vertex_array = if need_to_emulate_vao { - crate::misc_util::VAO::emulated() + crate::vao::VAO::emulated() } else { - crate::misc_util::VAO::native(&gl) + crate::vao::VAO::native(&gl) }; vertex_array.bind_vertex_array(&gl); vertex_array.bind_buffer(&gl, &pos_buffer); @@ -146,7 +147,7 @@ impl PostProcess { gl.buffer_data_u8_slice(glow::ELEMENT_ARRAY_BUFFER, &indices, glow::STATIC_DRAW); gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); - check_for_gl_error(&gl, "post process initialization"); + check_for_gl_error!(&gl, "post process initialization"); Ok(PostProcess { gl, @@ -190,6 +191,8 @@ impl PostProcess { self.gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.fbo)); self.gl.clear_color(0.0, 0.0, 0.0, 0.0); self.gl.clear(glow::COLOR_BUFFER_BIT); + + check_for_gl_error!(&self.gl, "PostProcess::begin"); } pub(crate) unsafe fn bind(&self) { @@ -219,6 +222,8 @@ impl PostProcess { self.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); self.gl.bind_texture(glow::TEXTURE_2D, None); self.gl.use_program(None); + + check_for_gl_error!(&self.gl, "PostProcess::end"); } pub(crate) unsafe fn destroy(&self) { diff --git a/egui_glow/src/shader_version.rs b/egui_glow/src/shader_version.rs index ae4af65ed..84c2434f2 100644 --- a/egui_glow/src/shader_version.rs +++ b/egui_glow/src/shader_version.rs @@ -1,6 +1,5 @@ #![allow(unsafe_code)] -use glow::HasContext; use std::convert::TryInto; #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -14,6 +13,7 @@ pub(crate) enum ShaderVersion { impl ShaderVersion { pub(crate) fn get(gl: &glow::Context) -> Self { + use glow::HasContext as _; let shading_lang_string = unsafe { gl.get_parameter_string(glow::SHADING_LANGUAGE_VERSION) }; let shader_version = Self::parse(&shading_lang_string); diff --git a/egui_glow/src/vao.rs b/egui_glow/src/vao.rs new file mode 100644 index 000000000..d3a5d8968 --- /dev/null +++ b/egui_glow/src/vao.rs @@ -0,0 +1,185 @@ +#![allow(unsafe_code)] + +use glow::HasContext as _; + +use crate::check_for_gl_error; + +// ---------------------------------------------------------------------------- + +#[derive(Debug)] +pub(crate) struct BufferInfo { + pub location: u32, // + pub vector_size: i32, + pub data_type: u32, //GL_FLOAT,GL_UNSIGNED_BYTE + pub normalized: bool, + pub stride: i32, + pub offset: i32, +} + +// ---------------------------------------------------------------------------- + +pub struct EmulatedVao { + buffer: Option, + buffer_infos: Vec, +} + +impl EmulatedVao { + pub(crate) fn new() -> Self { + Self { + buffer: None, + buffer_infos: vec![], + } + } + + pub(crate) fn bind_buffer(&mut self, buffer: &glow::Buffer) { + let _old = self.buffer.replace(*buffer); + } + + pub(crate) fn add_new_attribute(&mut self, buffer_info: BufferInfo) { + self.buffer_infos.push(buffer_info); + } + + pub(crate) fn bind_vertex_array(&self, gl: &glow::Context) { + unsafe { + gl.bind_buffer(glow::ARRAY_BUFFER, self.buffer); + check_for_gl_error!(gl, "bind_buffer"); + } + for attribute in &self.buffer_infos { + dbg!(attribute); + unsafe { + gl.vertex_attrib_pointer_f32( + attribute.location, + attribute.vector_size, + attribute.data_type, + attribute.normalized, + attribute.stride, + attribute.offset, + ); + check_for_gl_error!(gl, "vertex_attrib_pointer_f32"); + gl.enable_vertex_attrib_array(attribute.location); + check_for_gl_error!(gl, "enable_vertex_attrib_array"); + } + } + } + + pub(crate) fn unbind_vertex_array(&self, gl: &glow::Context) { + for attribute in &self.buffer_infos { + unsafe { + gl.disable_vertex_attrib_array(attribute.location); + } + } + unsafe { + gl.bind_buffer(glow::ARRAY_BUFFER, None); + } + } +} + +// ---------------------------------------------------------------------------- + +/// Wrapper around either Emulated VAO and GL's VAO +pub(crate) enum VAO { + Emulated(crate::vao::EmulatedVao), + Native(crate::glow::VertexArray), +} + +impl VAO { + pub(crate) unsafe fn native(gl: &glow::Context) -> Self { + Self::Native(gl.create_vertex_array().unwrap()) + } + + pub(crate) unsafe fn emulated() -> Self { + Self::Emulated(crate::vao::EmulatedVao::new()) + } + + pub(crate) unsafe fn bind_vertex_array(&self, gl: &glow::Context) { + match self { + VAO::Emulated(emulated_vao) => emulated_vao.bind_vertex_array(gl), + VAO::Native(vao) => { + gl.bind_vertex_array(Some(*vao)); + check_for_gl_error!(gl, "bind_vertex_array"); + } + } + } + + pub(crate) unsafe fn bind_buffer(&mut self, gl: &glow::Context, buffer: &glow::Buffer) { + match self { + VAO::Emulated(emulated_vao) => emulated_vao.bind_buffer(buffer), + VAO::Native(_) => gl.bind_buffer(glow::ARRAY_BUFFER, Some(*buffer)), + } + } + + pub(crate) unsafe fn add_new_attribute( + &mut self, + gl: &glow::Context, + buffer_info: crate::vao::BufferInfo, + ) { + match self { + VAO::Emulated(emulated_vao) => emulated_vao.add_new_attribute(buffer_info), + VAO::Native(_) => { + gl.vertex_attrib_pointer_f32( + buffer_info.location, + buffer_info.vector_size, + buffer_info.data_type, + buffer_info.normalized, + buffer_info.stride, + buffer_info.offset, + ); + gl.enable_vertex_attrib_array(buffer_info.location); + } + } + } + + pub(crate) unsafe fn unbind_vertex_array(&self, gl: &glow::Context) { + match self { + VAO::Emulated(emulated_vao) => emulated_vao.unbind_vertex_array(gl), + VAO::Native(_) => { + gl.bind_vertex_array(None); + } + } + } +} + +// ---------------------------------------------------------------------------- + +/// If returned true no need to emulate vao +pub(crate) fn supports_vao(gl: &glow::Context) -> bool { + const WEBGL_PREFIX: &str = "WebGL "; + const OPENGL_ES_PREFIX: &str = "OpenGL ES "; + + let version_string = unsafe { gl.get_parameter_string(glow::VERSION) }; + tracing::debug!("GL version: {:?}.", version_string); + + // Examples: + // * "WebGL 2.0 (OpenGL ES 3.0 Chromium)" + // * "WebGL 2.0" + + if let Some(pos) = version_string.rfind(WEBGL_PREFIX) { + let version_str = &version_string[pos + WEBGL_PREFIX.len()..]; + if version_str.contains("1.0") { + // need to test OES_vertex_array_object . + gl.supported_extensions() + .contains("OES_vertex_array_object") + } else { + true + } + } else if version_string.contains(OPENGL_ES_PREFIX) { + // glow targets es2.0+ so we don't concern about OpenGL ES-CM,OpenGL ES-CL + if version_string.contains("2.0") { + // need to test OES_vertex_array_object . + gl.supported_extensions() + .contains("OES_vertex_array_object") + } else { + true + } + } else { + // from OpenGL 3 vao into core + if version_string.starts_with('2') { + // I found APPLE_vertex_array_object , GL_ATI_vertex_array_object ,ARB_vertex_array_object + // but APPLE's and ATI's very old extension. + gl.supported_extensions() + .contains("ARB_vertex_array_object") + } else { + true + } + } +} diff --git a/egui_glow/src/vao_emulate.rs b/egui_glow/src/vao_emulate.rs deleted file mode 100644 index 590134945..000000000 --- a/egui_glow/src/vao_emulate.rs +++ /dev/null @@ -1,57 +0,0 @@ -#![allow(unsafe_code)] -use glow::HasContext; - -pub(crate) struct BufferInfo { - pub location: u32, // - pub vector_size: i32, - pub data_type: u32, //GL_FLOAT,GL_UNSIGNED_BYTE - pub normalized: bool, - pub stride: i32, - pub offset: i32, -} -pub struct EmulatedVao { - buffer: Option, - buffer_infos: Vec, -} -impl EmulatedVao { - pub(crate) fn new() -> Self { - Self { - buffer: None, - buffer_infos: vec![], - } - } - pub(crate) fn bind_buffer(&mut self, buffer: &glow::Buffer) { - let _old = self.buffer.replace(*buffer); - } - pub(crate) fn add_new_attribute(&mut self, buffer_info: BufferInfo) { - self.buffer_infos.push(buffer_info); - } - pub(crate) fn bind_vertex_array(&self, gl: &glow::Context) { - unsafe { - gl.bind_buffer(glow::ARRAY_BUFFER, self.buffer); - } - for attribute in self.buffer_infos.iter() { - unsafe { - gl.vertex_attrib_pointer_f32( - attribute.location, - attribute.vector_size, - attribute.data_type, - attribute.normalized, - attribute.stride, - attribute.offset, - ); - gl.enable_vertex_attrib_array(attribute.location); - } - } - } - pub(crate) fn unbind_vertex_array(&self, gl: &glow::Context) { - for attribute in self.buffer_infos.iter() { - unsafe { - gl.disable_vertex_attrib_array(attribute.location); - } - } - unsafe { - gl.bind_buffer(glow::ARRAY_BUFFER, None); - } - } -} From ea9393aa9b7cd3b6d08aa54bc42e126bf7a1fcdd Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 22 Mar 2022 23:11:27 +0100 Subject: [PATCH 26/32] glow painter improvements (#1406) * Add viewport info to PaintCallback * glow: be more explicit with more state * glow: Refactor VAO --- eframe/examples/custom_3d.rs | 2 +- egui/src/lib.rs | 4 +- egui_glow/src/painter.rs | 109 ++++++++++++------------ egui_glow/src/post_process.rs | 37 ++++---- egui_glow/src/vao.rs | 154 +++++++++++++--------------------- epaint/src/lib.rs | 5 +- epaint/src/shape.rs | 57 +++++++++++-- 7 files changed, 190 insertions(+), 178 deletions(-) diff --git a/eframe/examples/custom_3d.rs b/eframe/examples/custom_3d.rs index f86d444e2..45dedabbe 100644 --- a/eframe/examples/custom_3d.rs +++ b/eframe/examples/custom_3d.rs @@ -75,7 +75,7 @@ impl MyApp { let callback = egui::PaintCallback { rect, - callback: std::sync::Arc::new(move |render_ctx| { + callback: std::sync::Arc::new(move |_info, render_ctx| { if let Some(painter) = render_ctx.downcast_ref::() { rotating_triangle.lock().paint(painter.gl(), angle); } else { diff --git a/egui/src/lib.rs b/egui/src/lib.rs index 129afaa17..2a0978496 100644 --- a/egui/src/lib.rs +++ b/egui/src/lib.rs @@ -307,8 +307,8 @@ pub use epaint::{ color, mutex, text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak}, textures::TexturesDelta, - AlphaImage, ClippedPrimitive, Color32, ColorImage, ImageData, Mesh, PaintCallback, Rgba, - Rounding, Shape, Stroke, TextureHandle, TextureId, + AlphaImage, ClippedPrimitive, Color32, ColorImage, ImageData, Mesh, PaintCallback, + PaintCallbackInfo, Rgba, Rounding, Shape, Stroke, TextureHandle, TextureId, }; pub mod text { diff --git a/egui_glow/src/painter.rs b/egui_glow/src/painter.rs index 69d5123df..ebbf94ce9 100644 --- a/egui_glow/src/painter.rs +++ b/egui_glow/src/painter.rs @@ -37,12 +37,12 @@ pub struct Painter { u_sampler: glow::UniformLocation, is_webgl_1: bool, is_embedded: bool, - vertex_array: crate::vao::VAO, + vao: crate::vao::VertexArrayObject, srgb_support: bool, /// The filter used for subsequent textures. texture_filter: TextureFilter, post_process: Option, - vertex_buffer: glow::Buffer, + vbo: glow::Buffer, element_array_buffer: glow::Buffer, textures: HashMap, @@ -100,11 +100,6 @@ impl Painter { let max_texture_side = unsafe { gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE) } as usize; - let support_vao = crate::vao::supports_vao(&gl); - if !support_vao { - tracing::debug!("VAO not supported"); - } - let shader_version = ShaderVersion::get(&gl); let is_webgl_1 = shader_version == ShaderVersion::Es100; let header = shader_version.version(); @@ -122,7 +117,6 @@ impl Painter { Some(PostProcess::new( gl.clone(), shader_prefix, - support_vao, is_webgl_1, width, height, @@ -173,47 +167,44 @@ impl Painter { gl.delete_shader(frag); let u_screen_size = gl.get_uniform_location(program, "u_screen_size").unwrap(); let u_sampler = gl.get_uniform_location(program, "u_sampler").unwrap(); - let vertex_buffer = gl.create_buffer()?; - let element_array_buffer = gl.create_buffer()?; - gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer)); + + let vbo = gl.create_buffer()?; + let a_pos_loc = gl.get_attrib_location(program, "a_pos").unwrap(); let a_tc_loc = gl.get_attrib_location(program, "a_tc").unwrap(); let a_srgba_loc = gl.get_attrib_location(program, "a_srgba").unwrap(); - let mut vertex_array = if support_vao { - crate::vao::VAO::native(&gl) - } else { - crate::vao::VAO::emulated() - }; - vertex_array.bind_vertex_array(&gl); - vertex_array.bind_buffer(&gl, &vertex_buffer); + let stride = std::mem::size_of::() as i32; - let position_buffer_info = vao::BufferInfo { - location: a_pos_loc, - vector_size: 2, - data_type: glow::FLOAT, - normalized: false, - stride, - offset: offset_of!(Vertex, pos) as i32, - }; - let tex_coord_buffer_info = vao::BufferInfo { - location: a_tc_loc, - vector_size: 2, - data_type: glow::FLOAT, - normalized: false, - stride, - offset: offset_of!(Vertex, uv) as i32, - }; - let color_buffer_info = vao::BufferInfo { - location: a_srgba_loc, - vector_size: 4, - data_type: glow::UNSIGNED_BYTE, - normalized: false, - stride, - offset: offset_of!(Vertex, color) as i32, - }; - vertex_array.add_new_attribute(&gl, position_buffer_info); - vertex_array.add_new_attribute(&gl, tex_coord_buffer_info); - vertex_array.add_new_attribute(&gl, color_buffer_info); + let buffer_infos = vec![ + vao::BufferInfo { + location: a_pos_loc, + vector_size: 2, + data_type: glow::FLOAT, + normalized: false, + stride, + offset: offset_of!(Vertex, pos) as i32, + }, + vao::BufferInfo { + location: a_tc_loc, + vector_size: 2, + data_type: glow::FLOAT, + normalized: false, + stride, + offset: offset_of!(Vertex, uv) as i32, + }, + vao::BufferInfo { + location: a_srgba_loc, + vector_size: 4, + data_type: glow::UNSIGNED_BYTE, + normalized: false, + stride, + offset: offset_of!(Vertex, color) as i32, + }, + ]; + let vao = crate::vao::VertexArrayObject::new(&gl, vbo, buffer_infos); + + let element_array_buffer = gl.create_buffer()?; + check_for_gl_error!(&gl, "after Painter::new"); Ok(Painter { @@ -224,11 +215,11 @@ impl Painter { u_sampler, is_webgl_1, is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300), - vertex_array, + vao, srgb_support, texture_filter: Default::default(), post_process, - vertex_buffer, + vbo, element_array_buffer, textures: Default::default(), #[cfg(feature = "epi")] @@ -256,9 +247,13 @@ impl Painter { self.gl.enable(glow::SCISSOR_TEST); // egui outputs mesh in both winding orders self.gl.disable(glow::CULL_FACE); + self.gl.disable(glow::DEPTH_TEST); + + self.gl.color_mask(true, true, true, true); self.gl.enable(glow::BLEND); - self.gl.blend_equation(glow::FUNC_ADD); + self.gl + .blend_equation_separate(glow::FUNC_ADD, glow::FUNC_ADD); self.gl.blend_func_separate( // egui outputs colors with premultiplied alpha: glow::ONE, @@ -285,8 +280,8 @@ impl Painter { .uniform_2_f32(Some(&self.u_screen_size), width_in_points, height_in_points); self.gl.uniform_1_i32(Some(&self.u_sampler), 0); self.gl.active_texture(glow::TEXTURE0); - self.vertex_array.bind_vertex_array(&self.gl); + self.vao.bind(&self.gl); self.gl .bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_array_buffer)); @@ -380,7 +375,13 @@ impl Painter { ); } - callback.call(self); + let info = egui::PaintCallbackInfo { + rect: callback.rect, + pixels_per_point, + screen_size_px: inner_size, + }; + + callback.call(&info, self); check_for_gl_error!(&self.gl, "callback"); @@ -395,8 +396,9 @@ impl Painter { } } } + unsafe { - self.vertex_array.unbind_vertex_array(&self.gl); + self.vao.unbind(&self.gl); self.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); if let Some(ref post_process) = self.post_process { @@ -414,8 +416,7 @@ impl Painter { debug_assert!(mesh.is_valid()); if let Some(texture) = self.get_texture(mesh.texture_id) { unsafe { - self.gl - .bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer)); + self.gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vbo)); self.gl.buffer_data_u8_slice( glow::ARRAY_BUFFER, bytemuck::cast_slice(&mesh.vertices), @@ -600,7 +601,7 @@ impl Painter { for tex in self.textures.values() { self.gl.delete_texture(*tex); } - self.gl.delete_buffer(self.vertex_buffer); + self.gl.delete_buffer(self.vbo); self.gl.delete_buffer(self.element_array_buffer); for t in &self.textures_to_destroy { self.gl.delete_texture(*t); diff --git a/egui_glow/src/post_process.rs b/egui_glow/src/post_process.rs index 97b2e0c97..e35028a33 100644 --- a/egui_glow/src/post_process.rs +++ b/egui_glow/src/post_process.rs @@ -10,7 +10,7 @@ pub(crate) struct PostProcess { gl: std::rc::Rc, pos_buffer: glow::Buffer, index_buffer: glow::Buffer, - vertex_array: crate::vao::VAO, + vao: crate::vao::VertexArrayObject, is_webgl_1: bool, texture: glow::Texture, texture_size: (i32, i32), @@ -22,7 +22,6 @@ impl PostProcess { pub(crate) unsafe fn new( gl: std::rc::Rc, shader_prefix: &str, - need_to_emulate_vao: bool, is_webgl_1: bool, width: i32, height: i32, @@ -125,22 +124,18 @@ impl PostProcess { let a_pos_loc = gl .get_attrib_location(program, "a_pos") .ok_or_else(|| "failed to get location of a_pos".to_string())?; - let mut vertex_array = if need_to_emulate_vao { - crate::vao::VAO::emulated() - } else { - crate::vao::VAO::native(&gl) - }; - vertex_array.bind_vertex_array(&gl); - vertex_array.bind_buffer(&gl, &pos_buffer); - let buffer_info_a_pos = BufferInfo { - location: a_pos_loc, - vector_size: 2, - data_type: glow::FLOAT, - normalized: false, - stride: 0, - offset: 0, - }; - vertex_array.add_new_attribute(&gl, buffer_info_a_pos); + let vao = crate::vao::VertexArrayObject::new( + &gl, + pos_buffer, + vec![BufferInfo { + location: a_pos_loc, + vector_size: 2, + data_type: glow::FLOAT, + normalized: false, + stride: 0, + offset: 0, + }], + ); let index_buffer = gl.create_buffer()?; gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_buffer)); @@ -153,7 +148,7 @@ impl PostProcess { gl, pos_buffer, index_buffer, - vertex_array, + vao, is_webgl_1, texture, texture_size: (width, height), @@ -212,13 +207,13 @@ impl PostProcess { .get_uniform_location(self.program, "u_sampler") .unwrap(); self.gl.uniform_1_i32(Some(&u_sampler_loc), 0); - self.vertex_array.bind_vertex_array(&self.gl); + self.vao.bind(&self.gl); self.gl .bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.index_buffer)); self.gl .draw_elements(glow::TRIANGLES, 6, glow::UNSIGNED_BYTE, 0); - self.vertex_array.unbind_vertex_array(&self.gl); + self.vao.unbind(&self.gl); self.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); self.gl.bind_texture(glow::TEXTURE_2D, None); self.gl.use_program(None); diff --git a/egui_glow/src/vao.rs b/egui_glow/src/vao.rs index d3a5d8968..b88aefeb7 100644 --- a/egui_glow/src/vao.rs +++ b/egui_glow/src/vao.rs @@ -18,35 +18,67 @@ pub(crate) struct BufferInfo { // ---------------------------------------------------------------------------- -pub struct EmulatedVao { - buffer: Option, +/// Wrapper around either Emulated VAO or GL's VAO. +pub(crate) struct VertexArrayObject { + // If `None`, we emulate VAO:s. + vao: Option, + vbo: glow::Buffer, buffer_infos: Vec, } -impl EmulatedVao { - pub(crate) fn new() -> Self { +impl VertexArrayObject { + #[allow(clippy::needless_pass_by_value)] // false positive + pub(crate) unsafe fn new( + gl: &glow::Context, + vbo: glow::Buffer, + buffer_infos: Vec, + ) -> Self { + let vao = if supports_vao(gl) { + let vao = gl.create_vertex_array().unwrap(); + check_for_gl_error!(gl, "create_vertex_array"); + + // Store state in the VAO: + gl.bind_vertex_array(Some(vao)); + gl.bind_buffer(glow::ARRAY_BUFFER, Some(vbo)); + + for attribute in &buffer_infos { + gl.vertex_attrib_pointer_f32( + attribute.location, + attribute.vector_size, + attribute.data_type, + attribute.normalized, + attribute.stride, + attribute.offset, + ); + check_for_gl_error!(gl, "vertex_attrib_pointer_f32"); + gl.enable_vertex_attrib_array(attribute.location); + check_for_gl_error!(gl, "enable_vertex_attrib_array"); + } + + gl.bind_vertex_array(None); + + Some(vao) + } else { + tracing::debug!("VAO not supported"); + None + }; + Self { - buffer: None, - buffer_infos: vec![], + vao, + vbo, + buffer_infos, } } - pub(crate) fn bind_buffer(&mut self, buffer: &glow::Buffer) { - let _old = self.buffer.replace(*buffer); - } - - pub(crate) fn add_new_attribute(&mut self, buffer_info: BufferInfo) { - self.buffer_infos.push(buffer_info); - } - - pub(crate) fn bind_vertex_array(&self, gl: &glow::Context) { - unsafe { - gl.bind_buffer(glow::ARRAY_BUFFER, self.buffer); + pub(crate) unsafe fn bind(&self, gl: &glow::Context) { + if let Some(vao) = self.vao { + gl.bind_vertex_array(Some(vao)); + check_for_gl_error!(gl, "bind_vertex_array"); + } else { + gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vbo)); check_for_gl_error!(gl, "bind_buffer"); - } - for attribute in &self.buffer_infos { - dbg!(attribute); - unsafe { + + for attribute in &self.buffer_infos { gl.vertex_attrib_pointer_f32( attribute.location, attribute.vector_size, @@ -62,87 +94,21 @@ impl EmulatedVao { } } - pub(crate) fn unbind_vertex_array(&self, gl: &glow::Context) { - for attribute in &self.buffer_infos { - unsafe { + pub(crate) unsafe fn unbind(&self, gl: &glow::Context) { + if self.vao.is_some() { + gl.bind_vertex_array(None); + } else { + gl.bind_buffer(glow::ARRAY_BUFFER, None); + for attribute in &self.buffer_infos { gl.disable_vertex_attrib_array(attribute.location); } } - unsafe { - gl.bind_buffer(glow::ARRAY_BUFFER, None); - } } } // ---------------------------------------------------------------------------- -/// Wrapper around either Emulated VAO and GL's VAO -pub(crate) enum VAO { - Emulated(crate::vao::EmulatedVao), - Native(crate::glow::VertexArray), -} - -impl VAO { - pub(crate) unsafe fn native(gl: &glow::Context) -> Self { - Self::Native(gl.create_vertex_array().unwrap()) - } - - pub(crate) unsafe fn emulated() -> Self { - Self::Emulated(crate::vao::EmulatedVao::new()) - } - - pub(crate) unsafe fn bind_vertex_array(&self, gl: &glow::Context) { - match self { - VAO::Emulated(emulated_vao) => emulated_vao.bind_vertex_array(gl), - VAO::Native(vao) => { - gl.bind_vertex_array(Some(*vao)); - check_for_gl_error!(gl, "bind_vertex_array"); - } - } - } - - pub(crate) unsafe fn bind_buffer(&mut self, gl: &glow::Context, buffer: &glow::Buffer) { - match self { - VAO::Emulated(emulated_vao) => emulated_vao.bind_buffer(buffer), - VAO::Native(_) => gl.bind_buffer(glow::ARRAY_BUFFER, Some(*buffer)), - } - } - - pub(crate) unsafe fn add_new_attribute( - &mut self, - gl: &glow::Context, - buffer_info: crate::vao::BufferInfo, - ) { - match self { - VAO::Emulated(emulated_vao) => emulated_vao.add_new_attribute(buffer_info), - VAO::Native(_) => { - gl.vertex_attrib_pointer_f32( - buffer_info.location, - buffer_info.vector_size, - buffer_info.data_type, - buffer_info.normalized, - buffer_info.stride, - buffer_info.offset, - ); - gl.enable_vertex_attrib_array(buffer_info.location); - } - } - } - - pub(crate) unsafe fn unbind_vertex_array(&self, gl: &glow::Context) { - match self { - VAO::Emulated(emulated_vao) => emulated_vao.unbind_vertex_array(gl), - VAO::Native(_) => { - gl.bind_vertex_array(None); - } - } - } -} - -// ---------------------------------------------------------------------------- - -/// If returned true no need to emulate vao -pub(crate) fn supports_vao(gl: &glow::Context) -> bool { +fn supports_vao(gl: &glow::Context) -> bool { const WEBGL_PREFIX: &str = "WebGL "; const OPENGL_ES_PREFIX: &str = "OpenGL ES "; diff --git a/epaint/src/lib.rs b/epaint/src/lib.rs index 1e27b55f1..ab559f896 100644 --- a/epaint/src/lib.rs +++ b/epaint/src/lib.rs @@ -31,7 +31,10 @@ pub use { image::{AlphaImage, ColorImage, ImageData, ImageDelta}, mesh::{Mesh, Mesh16, Vertex}, shadow::Shadow, - shape::{CircleShape, PaintCallback, PathShape, RectShape, Rounding, Shape, TextShape}, + shape::{ + CircleShape, PaintCallback, PaintCallbackInfo, PathShape, RectShape, Rounding, Shape, + TextShape, + }, stats::PaintStats, stroke::Stroke, tessellator::{tessellate_shapes, TessellationOptions, Tessellator}, diff --git a/epaint/src/shape.rs b/epaint/src/shape.rs index 57d3cef1a..4fbd84573 100644 --- a/epaint/src/shape.rs +++ b/epaint/src/shape.rs @@ -642,6 +642,52 @@ fn dashes_from_line( // ---------------------------------------------------------------------------- +/// Information passed along with [`PaintCallback`] ([`Shape::Callback`]). +pub struct PaintCallbackInfo { + /// Viewport in points. + pub rect: Rect, + + /// Pixels per point. + pub pixels_per_point: f32, + + /// Full size of the screen, in pixels. + pub screen_size_px: [u32; 2], +} + +impl PaintCallbackInfo { + /// Physical pixel offset for left side of the viewport. + #[inline] + pub fn viewport_left_px(&self) -> f32 { + self.rect.min.x * self.pixels_per_point + } + + /// Physical pixel offset for top side of the viewport. + #[inline] + pub fn viewport_top_px(&self) -> f32 { + self.rect.min.y * self.pixels_per_point + } + + /// Physical pixel offset for bottom side of the viewport. + /// + /// This is what `glViewport` etc expects for the y axis. + #[inline] + pub fn viewport_from_bottom_px(&self) -> f32 { + self.screen_size_px[1] as f32 - self.rect.max.y * self.pixels_per_point + } + + /// Viewport width in physical pixels. + #[inline] + pub fn viewport_width_px(&self) -> f32 { + self.rect.width() * self.pixels_per_point + } + + /// Viewport width in physical pixels. + #[inline] + pub fn viewport_height_px(&self) -> f32 { + self.rect.height() * self.pixels_per_point + } +} + /// If you want to paint some 3D shapes inside an egui region, you can use this. /// /// This is advanced usage, and is backend specific. @@ -650,21 +696,22 @@ pub struct PaintCallback { /// Where to paint. pub rect: Rect, - /// Paint something custom using. + /// Paint something custom (e.g. 3D stuff). /// /// The argument is the render context, and what it contains depends on the backend. /// In `eframe` it will be `egui_glow::Painter`. /// /// The rendering backend is responsible for first setting the active viewport to [`Self::rect`]. - /// The rendering backend is also responsible for restoring any state it needs, + /// + /// The rendering backend is also responsible for restoring any state, /// such as the bound shader program and vertex array. - pub callback: std::sync::Arc, + pub callback: std::sync::Arc, } impl PaintCallback { #[inline] - pub fn call(&self, render_ctx: &dyn std::any::Any) { - (self.callback)(render_ctx); + pub fn call(&self, info: &PaintCallbackInfo, render_ctx: &dyn std::any::Any) { + (self.callback)(info, render_ctx); } } From 85e3ec502785b1371180b727946c732ce24f497d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 23 Mar 2022 10:10:12 +0100 Subject: [PATCH 27/32] Log supported OpenGL extensions if VAO is in doubt. --- egui_glow/src/vao.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/egui_glow/src/vao.rs b/egui_glow/src/vao.rs index b88aefeb7..0a6e53d30 100644 --- a/egui_glow/src/vao.rs +++ b/egui_glow/src/vao.rs @@ -123,8 +123,9 @@ fn supports_vao(gl: &glow::Context) -> bool { let version_str = &version_string[pos + WEBGL_PREFIX.len()..]; if version_str.contains("1.0") { // need to test OES_vertex_array_object . - gl.supported_extensions() - .contains("OES_vertex_array_object") + let supported_extensions = gl.supported_extensions(); + tracing::debug!("Supported OpenGL extensions: {:?}", supported_extensions); + supported_extensions.contains("OES_vertex_array_object") } else { true } @@ -132,8 +133,9 @@ fn supports_vao(gl: &glow::Context) -> bool { // glow targets es2.0+ so we don't concern about OpenGL ES-CM,OpenGL ES-CL if version_string.contains("2.0") { // need to test OES_vertex_array_object . - gl.supported_extensions() - .contains("OES_vertex_array_object") + let supported_extensions = gl.supported_extensions(); + tracing::debug!("Supported OpenGL extensions: {:?}", supported_extensions); + supported_extensions.contains("OES_vertex_array_object") } else { true } @@ -142,8 +144,9 @@ fn supports_vao(gl: &glow::Context) -> bool { if version_string.starts_with('2') { // I found APPLE_vertex_array_object , GL_ATI_vertex_array_object ,ARB_vertex_array_object // but APPLE's and ATI's very old extension. - gl.supported_extensions() - .contains("ARB_vertex_array_object") + let supported_extensions = gl.supported_extensions(); + tracing::debug!("Supported OpenGL extensions: {:?}", supported_extensions); + supported_extensions.contains("ARB_vertex_array_object") } else { true } From c63bdeab67846db71ce74512d236427c45ef6286 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 23 Mar 2022 11:06:33 +0100 Subject: [PATCH 28/32] Add an example of showing 3D using three-d (#1407) --- Cargo.lock | 96 +++++++++++ README.md | 18 +- eframe/Cargo.toml | 1 + .../{custom_3d.rs => custom_3d_glow.rs} | 7 +- eframe/examples/custom_3d_three-d.rs | 156 ++++++++++++++++++ 5 files changed, 274 insertions(+), 4 deletions(-) rename eframe/examples/{custom_3d.rs => custom_3d_glow.rs} (96%) create mode 100644 eframe/examples/custom_3d_three-d.rs diff --git a/Cargo.lock b/Cargo.lock index a7f5b1e13..2d4bb8479 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,6 +81,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "approx" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278" +dependencies = [ + "num-traits", +] + [[package]] name = "arrayref" version = "0.3.6" @@ -442,6 +451,16 @@ dependencies = [ "libc", ] +[[package]] +name = "cgmath" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a98d30140e3296250832bbaaff83b27dcd6fa3cc70fb6f1f3e5c9c0023b5317" +dependencies = [ + "approx", + "num-traits", +] + [[package]] name = "chrono" version = "0.4.19" @@ -995,6 +1014,7 @@ dependencies = [ "image", "poll-promise", "rfd", + "three-d", ] [[package]] @@ -1486,6 +1506,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "gloo-timers" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d12a7f4e95cfe710f1d624fb1210b7d961a5fb05c4fd942f4feab06e61f590e" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "glow" version = "0.11.2" @@ -1604,6 +1634,11 @@ name = "half" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +dependencies = [ + "num-traits", + "serde", + "zerocopy", +] [[package]] name = "hashbrown" @@ -1807,6 +1842,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "libm" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db" + [[package]] name = "line-wrap" version = "0.1.1" @@ -2122,6 +2163,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -3038,6 +3080,18 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + [[package]] name = "syntect" version = "4.6.0" @@ -3126,6 +3180,25 @@ dependencies = [ "once_cell", ] +[[package]] +name = "three-d" +version = "0.11.0" +source = "git+https://github.com/asny/three-d?rev=fa475673e284e05b2f4e068769dce3ec5bcabc8d#fa475673e284e05b2f4e068769dce3ec5bcabc8d" +dependencies = [ + "cgmath", + "gl_generator", + "gloo-timers", + "glow", + "half", + "js-sys", + "log", + "serde", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "time" version = "0.1.43" @@ -3476,6 +3549,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" dependencies = [ "cfg-if 1.0.0", + "serde", + "serde_json", "wasm-bindgen-macro", ] @@ -3950,6 +4025,27 @@ dependencies = [ "zvariant", ] +[[package]] +name = "zerocopy" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332f188cc1bcf1fe1064b8c58d150f497e697f49774aa846f2dc949d9a25f236" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0fbc82b82efe24da867ee52e015e58178684bd9dd64c34e66bdf21da2582a9f" +dependencies = [ + "proc-macro2", + "syn", + "synstructure", +] + [[package]] name = "zvariant" version = "3.1.2" diff --git a/README.md b/README.md index f8c4ef3a5..be9e86631 100644 --- a/README.md +++ b/README.md @@ -338,9 +338,23 @@ On Linux and Mac, Firefox will copy the WebGL render target from GPU, to CPU and To alleviate the above mentioned performance issues the default max-width of an egui web app is 1024 points. You can change this by overriding the `fn max_size_points` of [`epi::App`](https://docs.rs/epi/latest/epi/trait.App.html). ### How do I render 3D stuff in an egui area? -egui can't do 3D graphics itself, but if you use a 3D library (e.g. [`glium`](https://github.com/glium/glium) using [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium), or [`miniquad`](https://github.com/not-fl3/miniquad) using [`egui-miniquad`](https://github.com/not-fl3/egui-miniquad)) you can render your 3D content to a texture, then display it using [`ui.image(…)`](https://docs.rs/egui/latest/egui/struct.Ui.html#method.image). You first need to convert the native texture to an [`egui::TextureId`](https://docs.rs/egui/latest/egui/enum.TextureId.html), and how to do this depends on the integration you use (e.g. [`register_glium_texture`](https://docs.rs/epi/latest/epi/trait.NativeTexture.html#tymethod.register_native_texture)). +There are multiple ways to combine egui with 3D. The simplest way is to use a 3D library and have egui sit on top of the 3D view. See for instance [`bevy_egui`](https://github.com/mvlabat/bevy_egui) or [`three-d`](https://github.com/asny/three-d). -There is an example for showing a native glium texture in an egui window at . +If you want to embed 3D into an egui view there are two options. + +#### `Shape::Callback` +Examples: +* +* + +`Shape::Callback` will call your code when egui gets painted, to show anything using whatever the background rendering context is. When using [`eframe`](https://github.com/emilk/egui/tree/master/eframe) this will be [`glow`](https://github.com/grovesNL/glow). Other integrations will give you other rendering contexts, if they support `Shape::Callback` at all. + +#### Render-to-texture +You can also render your 3D scene to a texture and display it using [`ui.image(…)`](https://docs.rs/egui/latest/egui/struct.Ui.html#method.image). You first need to convert the native texture to an [`egui::TextureId`](https://docs.rs/egui/latest/egui/enum.TextureId.html), and how to do this depends on the integration you use. + +Examples: +* Using [`egui-miniquad`]( https://github.com/not-fl3/egui-miniquad): https://github.com/not-fl3/egui-miniquad/blob/master/examples/render_to_egui_image.rs +* Using [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium): . ## Other diff --git a/eframe/Cargo.toml b/eframe/Cargo.toml index 0c1e0b63e..5b2f4b40f 100644 --- a/eframe/Cargo.toml +++ b/eframe/Cargo.toml @@ -73,3 +73,4 @@ image = { version = "0.24", default-features = false, features = [ ] } poll-promise = "0.1" rfd = "0.8" +three-d = { git = "https://github.com/asny/three-d", rev = "fa475673e284e05b2f4e068769dce3ec5bcabc8d", default-features = false } # 2022-03-22 diff --git a/eframe/examples/custom_3d.rs b/eframe/examples/custom_3d_glow.rs similarity index 96% rename from eframe/examples/custom_3d.rs rename to eframe/examples/custom_3d_glow.rs index 45dedabbe..f352367f3 100644 --- a/eframe/examples/custom_3d.rs +++ b/eframe/examples/custom_3d_glow.rs @@ -2,7 +2,10 @@ //! //! This is very advanced usage, and you need to be careful. //! -//! If you want an easier way to show 3D graphics with egui, take a look at: +//! If you want an easier way to show 3D graphics with egui, take a look at the `custom_3d_three-d.rs` example. +//! +//! If you are content of having egui sit on top of a 3D background, take a look at: +//! //! * [`bevy_egui`](https://github.com/mvlabat/bevy_egui) //! * [`three-d`](https://github.com/asny/three-d) @@ -49,7 +52,7 @@ impl eframe::App for MyApp { }); egui::ScrollArea::both().show(ui, |ui| { - egui::Frame::dark_canvas(ui.style()).show(ui, |ui| { + egui::Frame::canvas(ui.style()).show(ui, |ui| { self.custom_painting(ui); }); ui.label("Drag to rotate!"); diff --git a/eframe/examples/custom_3d_three-d.rs b/eframe/examples/custom_3d_three-d.rs new file mode 100644 index 000000000..606560ce5 --- /dev/null +++ b/eframe/examples/custom_3d_three-d.rs @@ -0,0 +1,156 @@ +//! This demo shows how to embed 3D rendering using [`three-d`](https://github.com/asny/three-d) in `eframe`. +//! +//! Any 3D library built on top of [`glow`](https://github.com/grovesNL/glow) can be used in `eframe`. +//! +//! Alternatively you can render 3D stuff to a texture and display it using [`egui::Ui::image`]. +//! +//! If you are content of having egui sit on top of a 3D background, take a look at: +//! +//! * [`bevy_egui`](https://github.com/mvlabat/bevy_egui) +//! * [`three-d`](https://github.com/asny/three-d) + +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release + +use eframe::egui; + +fn main() { + let options = eframe::NativeOptions { + initial_window_size: Some(egui::vec2(550.0, 610.0)), + ..Default::default() + }; + eframe::run_native( + "Custom 3D painting in eframe!", + options, + Box::new(|cc| Box::new(MyApp::new(cc))), + ); +} + +struct MyApp { + angle: f32, +} + +impl MyApp { + fn new(_cc: &eframe::CreationContext<'_>) -> Self { + Self { angle: 0.0 } + } +} + +impl eframe::App for MyApp { + fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) { + egui::CentralPanel::default().show(ctx, |ui| { + egui::widgets::global_dark_light_mode_buttons(ui); + + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 0.0; + ui.label("The triangle is being painted using "); + ui.hyperlink_to("three-d", "https://github.com/asny/three-d"); + ui.label("."); + }); + + egui::ScrollArea::both().show(ui, |ui| { + egui::Frame::canvas(ui.style()).show(ui, |ui| { + self.custom_painting(ui); + }); + ui.label("Drag to rotate!"); + }); + }); + } +} + +impl MyApp { + fn custom_painting(&mut self, ui: &mut egui::Ui) { + let (rect, response) = + ui.allocate_exact_size(egui::Vec2::splat(512.0), egui::Sense::drag()); + + self.angle += response.drag_delta().x * 0.01; + + // Clone locals so we can move them into the paint callback: + let angle = self.angle; + + let callback = egui::PaintCallback { + rect, + callback: std::sync::Arc::new(move |info, render_ctx| { + if let Some(painter) = render_ctx.downcast_ref::() { + with_three_d_context(painter.gl(), |three_d| { + paint_with_three_d(three_d, info, angle); + }); + } else { + eprintln!("Can't do custom painting because we are not using a glow context"); + } + }), + }; + ui.painter().add(callback); + } +} + +/// We get a [`glow::Context`] from `eframe`, but we want a [`three_d::Context`]. +/// +/// Sadly we can't just create a [`three_d::Context`] in [`MyApp::new`] and pass it +/// to the [`egui::PaintCallback`] because [`three_d::Context`] isn't `Send+Sync`, which +/// [`egui::PaintCallback`] is. +fn with_three_d_context( + gl: &std::rc::Rc, + f: impl FnOnce(&three_d::Context) -> R, +) -> R { + use std::cell::RefCell; + thread_local! { + pub static THREE_D: RefCell> = RefCell::new(None); + } + + THREE_D.with(|three_d| { + let mut three_d = three_d.borrow_mut(); + let three_d = + three_d.get_or_insert_with(|| three_d::Context::from_gl_context(gl.clone()).unwrap()); + f(three_d) + }) +} + +fn paint_with_three_d(three_d: &three_d::Context, info: &egui::PaintCallbackInfo, angle: f32) { + // Based on https://github.com/asny/three-d/blob/master/examples/triangle/src/main.rs + use three_d::*; + + let viewport = Viewport { + x: info.viewport_left_px().round() as _, + y: info.viewport_from_bottom_px().round() as _, + width: info.viewport_width_px().round() as _, + height: info.viewport_height_px().round() as _, + }; + + let camera = Camera::new_perspective( + three_d, + viewport, + vec3(0.0, 0.0, 2.0), + vec3(0.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + degrees(45.0), + 0.1, + 10.0, + ) + .unwrap(); + + // Create a CPU-side mesh consisting of a single colored triangle + let positions = vec![ + vec3(0.5, -0.5, 0.0), // bottom right + vec3(-0.5, -0.5, 0.0), // bottom left + vec3(0.0, 0.5, 0.0), // top + ]; + let colors = vec![ + Color::new(255, 0, 0, 255), // bottom right + Color::new(0, 255, 0, 255), // bottom left + Color::new(0, 0, 255, 255), // top + ]; + let cpu_mesh = CpuMesh { + positions: Positions::F32(positions), + colors: Some(colors), + ..Default::default() + }; + + // Construct a model, with a default color material, thereby transferring the mesh data to the GPU + let mut model = Model::new(three_d, &cpu_mesh).unwrap(); + + // Set the current transformation of the triangle + model.set_transformation(Mat4::from_angle_y(radians(angle))); + + // Render the triangle with the color material which uses the per vertex colors defined at construction + model.render(&camera, &[]).unwrap(); +} From a9fd03709ea47ca97e803937be6160fca8318f77 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 23 Mar 2022 11:13:57 +0100 Subject: [PATCH 29/32] Add new NativeOptions: vsync multisampling depth_buffer stencil_buffer These are useful when embedding 3D into eframe. --- CHANGELOG.md | 2 +- eframe/CHANGELOG.md | 1 + eframe/examples/custom_3d_glow.rs | 18 +++++++++-------- eframe/examples/custom_3d_three-d.rs | 3 ++- egui-winit/src/epi.rs | 4 ++++ egui_glow/CHANGELOG.md | 2 ++ egui_glow/src/epi_backend.rs | 12 ++++++----- epi/src/lib.rs | 30 ++++++++++++++++++++++++++++ 8 files changed, 57 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08a352abb..9dbbcd0ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w ## Unreleased ### Added ⭐ -* Added `Shape::Callback` for backend-specific painting ([#1351](https://github.com/emilk/egui/pull/1351)). +* Added `Shape::Callback` for backend-specific painting, [with an example](https://github.com/emilk/egui/blob/master/eframe/examples/custom_3d_three-d.rs) ([#1351](https://github.com/emilk/egui/pull/1351)). * Added `Frame::canvas` ([#1362](https://github.com/emilk/egui/pull/1362)). * `Context::request_repaint` will wake up UI thread, if integrations has called `Context::set_request_repaint_callback` ([#1366](https://github.com/emilk/egui/pull/1366)). * Added `Ui::push_id` ([#1374](https://github.com/emilk/egui/pull/1374)). diff --git a/eframe/CHANGELOG.md b/eframe/CHANGELOG.md index a03be5deb..580fc090a 100644 --- a/eframe/CHANGELOG.md +++ b/eframe/CHANGELOG.md @@ -9,6 +9,7 @@ NOTE: [`egui_web`](../egui_web/CHANGELOG.md), [`egui-winit`](../egui-winit/CHANG * Remove the `egui_glium` feature. `eframe` will now always use `egui_glow` as the native backend ([#1357](https://github.com/emilk/egui/pull/1357)). * Removed `Frame::request_repaint` - just call `egui::Context::request_repaint` for the same effect ([#1366](https://github.com/emilk/egui/pull/1366)). * Use full browser width by default ([#1378](https://github.com/emilk/egui/pull/1378)). +* Add new `NativeOptions`: `vsync`, `multisampling`, `depth_buffer`, `stencil_buffer`. ## 0.17.0 - 2022-02-22 diff --git a/eframe/examples/custom_3d_glow.rs b/eframe/examples/custom_3d_glow.rs index f352367f3..20b3d3cd8 100644 --- a/eframe/examples/custom_3d_glow.rs +++ b/eframe/examples/custom_3d_glow.rs @@ -18,9 +18,13 @@ use egui::mutex::Mutex; use std::sync::Arc; fn main() { - let options = eframe::NativeOptions::default(); + let options = eframe::NativeOptions { + initial_window_size: Some(egui::vec2(350.0, 380.0)), + multisampling: 8, + ..Default::default() + }; eframe::run_native( - "Custom 3D painting in eframe", + "Custom 3D painting in eframe using glow", options, Box::new(|cc| Box::new(MyApp::new(cc))), ); @@ -51,12 +55,10 @@ impl eframe::App for MyApp { ui.label(" (OpenGL)."); }); - egui::ScrollArea::both().show(ui, |ui| { - egui::Frame::canvas(ui.style()).show(ui, |ui| { - self.custom_painting(ui); - }); - ui.label("Drag to rotate!"); + egui::Frame::canvas(ui.style()).show(ui, |ui| { + self.custom_painting(ui); }); + ui.label("Drag to rotate!"); }); } @@ -68,7 +70,7 @@ impl eframe::App for MyApp { impl MyApp { fn custom_painting(&mut self, ui: &mut egui::Ui) { let (rect, response) = - ui.allocate_exact_size(egui::Vec2::splat(256.0), egui::Sense::drag()); + ui.allocate_exact_size(egui::Vec2::splat(300.0), egui::Sense::drag()); self.angle += response.drag_delta().x * 0.01; diff --git a/eframe/examples/custom_3d_three-d.rs b/eframe/examples/custom_3d_three-d.rs index 606560ce5..5be84631c 100644 --- a/eframe/examples/custom_3d_three-d.rs +++ b/eframe/examples/custom_3d_three-d.rs @@ -16,6 +16,7 @@ use eframe::egui; fn main() { let options = eframe::NativeOptions { initial_window_size: Some(egui::vec2(550.0, 610.0)), + multisampling: 8, ..Default::default() }; eframe::run_native( @@ -31,7 +32,7 @@ struct MyApp { impl MyApp { fn new(_cc: &eframe::CreationContext<'_>) -> Self { - Self { angle: 0.0 } + Self { angle: 0.2 } } } diff --git a/egui-winit/src/epi.rs b/egui-winit/src/epi.rs index 2674db33d..1a82de178 100644 --- a/egui-winit/src/epi.rs +++ b/egui-winit/src/epi.rs @@ -21,6 +21,10 @@ pub fn window_builder( max_window_size, resizable, transparent, + vsync: _, // used in `fn create_display` + multisampling: _, // used in `fn create_display` + depth_buffer: _, // used in `fn create_display` + stencil_buffer: _, // used in `fn create_display` } = native_options; let window_icon = icon_data.clone().and_then(load_icon); diff --git a/egui_glow/CHANGELOG.md b/egui_glow/CHANGELOG.md index 0c470c644..60aa349e9 100644 --- a/egui_glow/CHANGELOG.md +++ b/egui_glow/CHANGELOG.md @@ -3,6 +3,8 @@ All notable changes to the `egui_glow` integration will be noted in this file. ## Unreleased +* Improved logging on rendering failures. +* Add new `NativeOptions`: `vsync`, `multisampling`, `depth_buffer`, `stencil_buffer`. ## 0.17.0 - 2022-02-22 diff --git a/egui_glow/src/epi_backend.rs b/egui_glow/src/epi_backend.rs index 2e16e7ae7..3d74a9560 100644 --- a/egui_glow/src/epi_backend.rs +++ b/egui_glow/src/epi_backend.rs @@ -5,6 +5,7 @@ struct RequestRepaintEvent; #[allow(unsafe_code)] fn create_display( + native_options: &NativeOptions, window_builder: winit::window::WindowBuilder, event_loop: &winit::event_loop::EventLoop, ) -> ( @@ -13,10 +14,11 @@ fn create_display( ) { let gl_window = unsafe { glutin::ContextBuilder::new() - .with_depth_buffer(0) - .with_srgb(true) - .with_stencil_buffer(0) - .with_vsync(true) + .with_depth_buffer(native_options.depth_buffer) + .with_multisampling(native_options.multisampling) + .with_srgb(native_options.vsync) + .with_stencil_buffer(native_options.stencil_buffer) + .with_vsync(native_options.vsync) .build_windowed(window_builder, event_loop) .unwrap() .make_current() @@ -40,7 +42,7 @@ pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi let window_builder = egui_winit::epi::window_builder(native_options, &window_settings).with_title(app_name); let event_loop = winit::event_loop::EventLoop::with_user_event(); - let (gl_window, gl) = create_display(window_builder, &event_loop); + let (gl_window, gl) = create_display(native_options, window_builder, &event_loop); let gl = std::rc::Rc::new(gl); let mut painter = crate::Painter::new(gl.clone(), None, "") diff --git a/epi/src/lib.rs b/epi/src/lib.rs index 9359cccaf..dac193017 100644 --- a/epi/src/lib.rs +++ b/epi/src/lib.rs @@ -183,6 +183,32 @@ pub struct NativeOptions { /// You control the transparency with [`App::clear_color()`]. /// You should avoid having a [`egui::CentralPanel`], or make sure its frame is also transparent. pub transparent: bool, + + /// Turn on vertical syncing, limiting the FPS to the display refresh rate. + /// + /// The default is `true`. + pub vsync: bool, + + /// Set the level of the multisampling anti-aliasing (MSAA). + /// + /// Must be a power-of-two. Higher = more smooth 3D. + /// + /// A value of `0` turns it off (default). + /// + /// `egui` already performs anti-aliasing via "feathering" + /// (controlled by [`egui::epaint::TessellationOptions`]), + /// but if you are embedding 3D in egui you may want to turn on multisampling. + pub multisampling: u16, + + /// Sets the number of bits in the depth buffer. + /// + /// `egui` doesn't need the depth buffer, so the default value is 0. + pub depth_buffer: u8, + + /// Sets the number of bits in the stencil buffer. + /// + /// `egui` doesn't need the stencil buffer, so the default value is 0. + pub stencil_buffer: u8, } impl Default for NativeOptions { @@ -199,6 +225,10 @@ impl Default for NativeOptions { max_window_size: None, resizable: true, transparent: false, + vsync: true, + multisampling: 0, + depth_buffer: 0, + stencil_buffer: 0, } } } From 1387d6e9d6a978862623fef030f3b00304a7013c Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 23 Mar 2022 11:41:38 +0100 Subject: [PATCH 30/32] Refactor TessellationOptions to expose slider for feathering size (#1408) The epaint tessellator uses "feathering" to accomplish anti-aliasing. This PS allows you to control the feathering size, i.e. how blurry the edges of epaint shapes are. This changes the interface of Tessellator slightly, and renames some options in TessellationOptions. --- egui/src/context.rs | 12 +- egui/src/introspection.rs | 14 ++- egui_demo_lib/benches/benchmark.rs | 2 +- epaint/CHANGELOG.md | 2 + epaint/src/shadow.rs | 14 ++- epaint/src/tessellator.rs | 172 ++++++++++++++--------------- epaint/src/text/text_layout.rs | 6 +- 7 files changed, 115 insertions(+), 107 deletions(-) diff --git a/egui/src/context.rs b/egui/src/context.rs index 536461de9..6666ba33c 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -807,14 +807,16 @@ impl Context { // shapes are the same, but just comparing the shapes takes about 50% of the time // it takes to tessellate them, so it is not a worth optimization. - let mut tessellation_options = *self.tessellation_options(); - tessellation_options.pixels_per_point = self.pixels_per_point(); - tessellation_options.aa_size = 1.0 / self.pixels_per_point(); + let pixels_per_point = self.pixels_per_point(); + let tessellation_options = *self.tessellation_options(); + let font_image_size = self.fonts().font_image_size(); + let paint_stats = PaintStats::from_shapes(&shapes); let clipped_primitives = tessellator::tessellate_shapes( - shapes, + pixels_per_point, tessellation_options, - self.fonts().font_image_size(), + shapes, + font_image_size, ); self.write().paint_stats = paint_stats.with_clipped_primitives(&clipped_primitives); clipped_primitives diff --git a/egui/src/introspection.rs b/egui/src/introspection.rs index 7e286d894..4956918c3 100644 --- a/egui/src/introspection.rs +++ b/egui/src/introspection.rs @@ -139,9 +139,8 @@ impl Widget for &mut epaint::TessellationOptions { fn ui(self, ui: &mut Ui) -> Response { ui.vertical(|ui| { let epaint::TessellationOptions { - pixels_per_point: _, - aa_size: _, - anti_alias, + feathering, + feathering_size_in_pixels, coarse_tessellation_culling, round_text_to_pixels, debug_paint_clip_rects, @@ -150,8 +149,15 @@ impl Widget for &mut epaint::TessellationOptions { bezier_tolerance, epsilon: _, } = self; - ui.checkbox(anti_alias, "Antialias") + + ui.checkbox(feathering, "Feathering (antialias)") .on_hover_text("Apply feathering to smooth out the edges of shapes. Turn off for small performance gain."); + let feathering_slider = crate::Slider::new(feathering_size_in_pixels, 0.0..=10.0) + .smallest_positive(0.1) + .logarithmic(true) + .text("Feathering size in pixels"); + ui.add_enabled(*feathering, feathering_slider); + ui.add( crate::widgets::Slider::new(bezier_tolerance, 0.0001..=10.0) .logarithmic(true) diff --git a/egui_demo_lib/benches/benchmark.rs b/egui_demo_lib/benches/benchmark.rs index 9038187f6..b3e86bbc0 100644 --- a/egui_demo_lib/benches/benchmark.rs +++ b/egui_demo_lib/benches/benchmark.rs @@ -123,7 +123,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { }); let galley = fonts.layout(LOREM_IPSUM_LONG.to_owned(), font_id, color, wrap_width); - let mut tessellator = egui::epaint::Tessellator::from_options(Default::default()); + let mut tessellator = egui::epaint::Tessellator::new(1.0, Default::default()); let mut mesh = egui::epaint::Mesh::default(); let text_shape = TextShape::new(egui::Pos2::ZERO, galley); let font_image_size = fonts.font_image_size(); diff --git a/epaint/CHANGELOG.md b/epaint/CHANGELOG.md index ec709f696..de93bc15a 100644 --- a/epaint/CHANGELOG.md +++ b/epaint/CHANGELOG.md @@ -5,6 +5,8 @@ All notable changes to the epaint crate will be documented in this file. ## Unreleased * Add `Shape::Callback` for backend-specific painting ([#1351](https://github.com/emilk/egui/pull/1351)). * Removed the `single_threaded/multi_threaded` flags - epaint is now always thread-safe ([#1390](https://github.com/emilk/egui/pull/1390)). +* `Tessellator::from_options` is now `Tessellator::new` ([#1408](https://github.com/emilk/egui/pull/1408)). +* Renamed `TessellationOptions::anti_alias` to `feathering` ([#1408](https://github.com/emilk/egui/pull/1408)). ## 0.17.0 - 2022-02-22 diff --git a/epaint/src/shadow.rs b/epaint/src/shadow.rs index d8895fee3..35ce41cd0 100644 --- a/epaint/src/shadow.rs +++ b/epaint/src/shadow.rs @@ -63,11 +63,15 @@ impl Shadow { use crate::tessellator::*; let rect = RectShape::filled(rect.expand(half_ext), ext_rounding, color); - let mut tessellator = Tessellator::from_options(TessellationOptions { - aa_size: extrusion, - anti_alias: true, - ..Default::default() - }); + let pixels_per_point = 1.0; // doesn't matter here + let mut tessellator = Tessellator::new( + pixels_per_point, + TessellationOptions { + feathering: true, + feathering_size_in_pixels: extrusion * pixels_per_point, + ..Default::default() + }, + ); let mut mesh = Mesh::default(); tessellator.tessellate_rect(&rect, &mut mesh); mesh diff --git a/epaint/src/tessellator.rs b/epaint/src/tessellator.rs index 440f356e6..1f9fe1a05 100644 --- a/epaint/src/tessellator.rs +++ b/epaint/src/tessellator.rs @@ -163,23 +163,17 @@ impl Path { } /// Open-ended. - pub fn stroke_open(&self, stroke: Stroke, options: &TessellationOptions, out: &mut Mesh) { - stroke_path(&self.0, PathType::Open, stroke, options, out); + pub fn stroke_open(&self, feathering: f32, stroke: Stroke, out: &mut Mesh) { + stroke_path(feathering, &self.0, PathType::Open, stroke, out); } /// A closed path (returning to the first point). - pub fn stroke_closed(&self, stroke: Stroke, options: &TessellationOptions, out: &mut Mesh) { - stroke_path(&self.0, PathType::Closed, stroke, options, out); + pub fn stroke_closed(&self, feathering: f32, stroke: Stroke, out: &mut Mesh) { + stroke_path(feathering, &self.0, PathType::Closed, stroke, out); } - pub fn stroke( - &self, - path_type: PathType, - stroke: Stroke, - options: &TessellationOptions, - out: &mut Mesh, - ) { - stroke_path(&self.0, path_type, stroke, options, out); + pub fn stroke(&self, feathering: f32, path_type: PathType, stroke: Stroke, out: &mut Mesh) { + stroke_path(feathering, &self.0, path_type, stroke, out); } /// The path is taken to be closed (i.e. returning to the start again). @@ -187,8 +181,8 @@ impl Path { /// Calling this may reverse the vertices in the path if they are wrong winding order. /// /// The preferred winding order is clockwise. - pub fn fill(&mut self, color: Color32, options: &TessellationOptions, out: &mut Mesh) { - fill_closed_path(&mut self.0, color, options, out); + pub fn fill(&mut self, feathering: f32, color: Color32, out: &mut Mesh) { + fill_closed_path(feathering, &mut self.0, color, out); } } @@ -286,18 +280,23 @@ pub enum PathType { #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] pub struct TessellationOptions { - /// Size of a point in pixels (DPI scaling), e.g. 2.0. Used to snap text to pixel boundaries. - pub pixels_per_point: f32, - - /// The size of a pixel (in points), used for anti-aliasing (smoothing of edges). - /// This is normally the inverse of [`Self::pixels_per_point`], - /// but you can make it larger if you want more blurry edges. - pub aa_size: f32, - - /// Anti-aliasing makes shapes appear smoother, but requires more triangles and is therefore slower. + /// Use "feathering" to smooth out the edges of shapes as a form of anti-aliasing. + /// + /// Feathering works by making each edge into a thin gradient into transparency. + /// The size of this edge is controlled by [`Self::feathering_size_in_pixels`]. + /// + /// This makes shapes appear smoother, but requires more triangles and is therefore slower. + /// /// This setting does not affect text. + /// /// Default: `true`. - pub anti_alias: bool, + pub feathering: bool, + + /// The size of the the feathering, in physical pixels. + /// + /// The default, and suggested, value for this is `1.0`. + /// If you use a larger value, edges will appear blurry. + pub feathering_size_in_pixels: f32, /// If `true` (default) cull certain primitives before tessellating them. /// This likely makes @@ -326,9 +325,8 @@ pub struct TessellationOptions { impl Default for TessellationOptions { fn default() -> Self { Self { - pixels_per_point: 1.0, - aa_size: 1.0, - anti_alias: true, + feathering: true, + feathering_size_in_pixels: 1.0, coarse_tessellation_culling: true, round_text_to_pixels: true, debug_paint_text_rects: false, @@ -340,27 +338,6 @@ impl Default for TessellationOptions { } } -impl TessellationOptions { - pub fn from_pixels_per_point(pixels_per_point: f32) -> Self { - Self { - pixels_per_point, - aa_size: 1.0 / pixels_per_point, - ..Default::default() - } - } -} - -impl TessellationOptions { - #[inline(always)] - pub fn round_to_pixel(&self, point: f32) -> f32 { - if self.round_text_to_pixels { - (point * self.pixels_per_point).round() / self.pixels_per_point - } else { - point - } - } -} - fn cw_signed_area(path: &[PathPoint]) -> f64 { if let Some(last) = path.last() { let mut previous = last.pos; @@ -380,18 +357,13 @@ fn cw_signed_area(path: &[PathPoint]) -> f64 { /// Calling this may reverse the vertices in the path if they are wrong winding order. /// /// The preferred winding order is clockwise. -fn fill_closed_path( - path: &mut [PathPoint], - color: Color32, - options: &TessellationOptions, - out: &mut Mesh, -) { +fn fill_closed_path(feathering: f32, path: &mut [PathPoint], color: Color32, out: &mut Mesh) { if color == Color32::TRANSPARENT { return; } let n = path.len() as u32; - if options.anti_alias { + if feathering > 0.0 { if cw_signed_area(path) < 0.0 { // Wrong winding order - fix: path.reverse(); @@ -415,7 +387,7 @@ fn fill_closed_path( let mut i0 = n - 1; for i1 in 0..n { let p1 = &path[i1 as usize]; - let dm = 0.5 * options.aa_size * p1.normal; + let dm = 0.5 * feathering * p1.normal; out.colored_vertex(p1.pos - dm, color); out.colored_vertex(p1.pos + dm, color_outer); out.add_triangle(idx_inner + i1 * 2, idx_inner + i0 * 2, idx_outer + 2 * i0); @@ -438,10 +410,10 @@ fn fill_closed_path( /// Tessellate the given path as a stroke with thickness. fn stroke_path( + feathering: f32, path: &[PathPoint], path_type: PathType, stroke: Stroke, - options: &TessellationOptions, out: &mut Mesh, ) { let n = path.len() as u32; @@ -452,21 +424,21 @@ fn stroke_path( let idx = out.vertices.len() as u32; - if options.anti_alias { + if feathering > 0.0 { let color_inner = stroke.color; let color_outer = Color32::TRANSPARENT; - let thin_line = stroke.width <= options.aa_size; + let thin_line = stroke.width <= feathering; if thin_line { /* We paint the line using three edges: outer, inner, outer. . o i o outer, inner, outer - . |---| aa_size (pixel width) + . |---| feathering (pixel width) */ // Fade out as it gets thinner: - let color_inner = mul_color(color_inner, stroke.width / options.aa_size); + let color_inner = mul_color(color_inner, stroke.width / feathering); if color_inner == Color32::TRANSPARENT { return; } @@ -480,9 +452,9 @@ fn stroke_path( let p1 = &path[i1 as usize]; let p = p1.pos; let n = p1.normal; - out.colored_vertex(p + n * options.aa_size, color_outer); + out.colored_vertex(p + n * feathering, color_outer); out.colored_vertex(p, color_inner); - out.colored_vertex(p - n * options.aa_size, color_outer); + out.colored_vertex(p - n * feathering, color_outer); if connect_with_previous { out.add_triangle(idx + 3 * i0 + 0, idx + 3 * i0 + 1, idx + 3 * i1 + 0); @@ -500,14 +472,14 @@ fn stroke_path( We paint the line using four edges: outer, inner, inner, outer . o i p i o outer, inner, point, inner, outer - . |---| aa_size (pixel width) + . |---| feathering (pixel width) . |--------------| width . |---------| outer_rad . |-----| inner_rad */ - let inner_rad = 0.5 * (stroke.width - options.aa_size); - let outer_rad = 0.5 * (stroke.width + options.aa_size); + let inner_rad = 0.5 * (stroke.width - feathering); + let outer_rad = 0.5 * (stroke.width + feathering); match path_type { PathType::Closed => { @@ -542,7 +514,7 @@ fn stroke_path( // | aa | | aa | // _________________ ___ - // | \ added / | aa_size + // | \ added / | feathering // | \ ___p___ / | ___ // | | | | // | | opa | | @@ -558,7 +530,7 @@ fn stroke_path( let end = &path[0]; let p = end.pos; let n = end.normal; - let back_extrude = n.rot90() * options.aa_size; + let back_extrude = n.rot90() * feathering; out.colored_vertex(p + n * outer_rad + back_extrude, color_outer); out.colored_vertex(p + n * inner_rad, color_inner); out.colored_vertex(p - n * inner_rad, color_inner); @@ -595,7 +567,7 @@ fn stroke_path( let end = &path[i1 as usize]; let p = end.pos; let n = end.normal; - let back_extrude = -n.rot90() * options.aa_size; + let back_extrude = -n.rot90() * feathering; out.colored_vertex(p + n * outer_rad + back_extrude, color_outer); out.colored_vertex(p + n * inner_rad, color_inner); out.colored_vertex(p - n * inner_rad, color_inner); @@ -640,11 +612,11 @@ fn stroke_path( ); } - let thin_line = stroke.width <= options.aa_size; + let thin_line = stroke.width <= feathering; if thin_line { // Fade out thin lines rather than making them thinner - let radius = options.aa_size / 2.0; - let color = mul_color(stroke.color, stroke.width / options.aa_size); + let radius = feathering / 2.0; + let color = mul_color(stroke.color, stroke.width / feathering); if color == Color32::TRANSPARENT { return; } @@ -677,7 +649,10 @@ fn mul_color(color: Color32, factor: f32) -> Color32 { /// /// Se also [`tessellate_shapes`], a convenient wrapper around [`Tessellator`]. pub struct Tessellator { + pixels_per_point: f32, options: TessellationOptions, + /// size of feathering in points. normally the size of a physical pixel. 0.0 if disabled + feathering: f32, /// Only used for culling clip_rect: Rect, scratchpad_points: Vec, @@ -686,9 +661,17 @@ pub struct Tessellator { impl Tessellator { /// Create a new [`Tessellator`]. - pub fn from_options(options: TessellationOptions) -> Self { + pub fn new(pixels_per_point: f32, options: TessellationOptions) -> Self { + let feathering = if options.feathering { + let pixel_size = 1.0 / pixels_per_point; + options.feathering_size_in_pixels * pixel_size + } else { + 0.0 + }; Self { + pixels_per_point, options, + feathering, clip_rect: Rect::EVERYTHING, scratchpad_points: Default::default(), scratchpad_path: Default::default(), @@ -700,6 +683,15 @@ impl Tessellator { self.clip_rect = clip_rect; } + #[inline(always)] + pub fn round_to_pixel(&self, point: f32) -> f32 { + if self.options.round_text_to_pixels { + (point * self.pixels_per_point).round() / self.pixels_per_point + } else { + point + } + } + /// Tessellate a single [`Shape`] into a [`Mesh`]. /// /// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles). @@ -783,9 +775,9 @@ impl Tessellator { self.scratchpad_path.clear(); self.scratchpad_path.add_circle(center, radius); - self.scratchpad_path.fill(fill, &self.options, out); + self.scratchpad_path.fill(self.feathering, fill, out); self.scratchpad_path - .stroke_closed(stroke, &self.options, out); + .stroke_closed(self.feathering, stroke, out); } /// Tessellate a single [`Mesh`] into a [`Mesh`]. @@ -826,7 +818,8 @@ impl Tessellator { self.scratchpad_path.clear(); self.scratchpad_path.add_line_segment(points); - self.scratchpad_path.stroke_open(stroke, &self.options, out); + self.scratchpad_path + .stroke_open(self.feathering, stroke, out); } /// Tessellate a single [`PathShape`] into a [`Mesh`]. @@ -863,7 +856,7 @@ impl Tessellator { closed, "You asked to fill a path that is not closed. That makes no sense." ); - self.scratchpad_path.fill(*fill, &self.options, out); + self.scratchpad_path.fill(self.feathering, *fill, out); } let typ = if *closed { PathType::Closed @@ -871,7 +864,7 @@ impl Tessellator { PathType::Open }; self.scratchpad_path - .stroke(typ, *stroke, &self.options, out); + .stroke(self.feathering, typ, *stroke, out); } /// Tessellate a single [`Rect`] into a [`Mesh`]. @@ -904,8 +897,8 @@ impl Tessellator { path.clear(); path::rounded_rectangle(&mut self.scratchpad_points, rect, rounding); path.add_line_loop(&self.scratchpad_points); - path.fill(fill, &self.options, out); - path.stroke_closed(stroke, &self.options, out); + path.fill(self.feathering, fill, out); + path.stroke_closed(self.feathering, stroke, out); } /// Tessellate a single [`TextShape`] into a [`Mesh`]. @@ -937,8 +930,8 @@ impl Tessellator { // The contents of the galley is already snapped to pixel coordinates, // but we need to make sure the galley ends up on the start of a physical pixel: let galley_pos = pos2( - self.options.round_to_pixel(galley_pos.x), - self.options.round_to_pixel(galley_pos.y), + self.round_to_pixel(galley_pos.x), + self.round_to_pixel(galley_pos.y), ); let uv_normalizer = vec2(1.0 / tex_size[0] as f32, 1.0 / tex_size[1] as f32); @@ -1006,7 +999,7 @@ impl Tessellator { self.scratchpad_path .add_line_segment([row_rect.left_bottom(), row_rect.right_bottom()]); self.scratchpad_path - .stroke_open(*underline, &self.options, out); + .stroke_open(self.feathering, *underline, out); } } } @@ -1086,14 +1079,15 @@ impl Tessellator { closed, "You asked to fill a path that is not closed. That makes no sense." ); - self.scratchpad_path.fill(fill, &self.options, out); + self.scratchpad_path.fill(self.feathering, fill, out); } let typ = if closed { PathType::Closed } else { PathType::Open }; - self.scratchpad_path.stroke(typ, stroke, &self.options, out); + self.scratchpad_path + .stroke(self.feathering, typ, stroke, out); } } @@ -1102,8 +1096,9 @@ impl Tessellator { /// The given shapes will tessellated in the same order as they are given. /// They will be batched together by clip rectangle. /// -/// * `shapes`: what to tessellate +/// * `pixels_per_point`: number of physical pixels to each logical point /// * `options`: tessellation quality +/// * `shapes`: what to tessellate /// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles) /// /// The implementation uses a [`Tessellator`]. @@ -1111,11 +1106,12 @@ impl Tessellator { /// ## Returns /// A list of clip rectangles with matching [`Mesh`]. pub fn tessellate_shapes( - shapes: Vec, + pixels_per_point: f32, options: TessellationOptions, + shapes: Vec, tex_size: [usize; 2], ) -> Vec { - let mut tessellator = Tessellator::from_options(options); + let mut tessellator = Tessellator::new(pixels_per_point, options); let mut clipped_primitives: Vec = Vec::default(); diff --git a/epaint/src/text/text_layout.rs b/epaint/src/text/text_layout.rs index a70127c76..bac97e174 100644 --- a/epaint/src/text/text_layout.rs +++ b/epaint/src/text/text_layout.rs @@ -599,10 +599,8 @@ fn add_hline(point_scale: PointScale, [start, stop]: [Pos2; 2], stroke: Stroke, if antialiased { let mut path = crate::tessellator::Path::default(); // TODO: reuse this to avoid re-allocations. path.add_line_segment([start, stop]); - let options = crate::tessellator::TessellationOptions::from_pixels_per_point( - point_scale.pixels_per_point(), - ); - path.stroke_open(stroke, &options, mesh); + let feathering = 1.0 / point_scale.pixels_per_point(); + path.stroke_open(feathering, stroke, mesh); } else { // Thin lines often lost, so this is a bad idea From 3e41da718783616e6ae159f3641ce0c3b7b53d04 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 23 Mar 2022 13:04:12 +0100 Subject: [PATCH 31/32] Revert accidentally setting srgb option on glutin window based on vsync Introduced in a9fd03709ea47ca97e803937be6160fca8318f77 --- egui_glow/src/epi_backend.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/egui_glow/src/epi_backend.rs b/egui_glow/src/epi_backend.rs index 3d74a9560..e9b1cf741 100644 --- a/egui_glow/src/epi_backend.rs +++ b/egui_glow/src/epi_backend.rs @@ -16,7 +16,7 @@ fn create_display( glutin::ContextBuilder::new() .with_depth_buffer(native_options.depth_buffer) .with_multisampling(native_options.multisampling) - .with_srgb(native_options.vsync) + .with_srgb(true) .with_stencil_buffer(native_options.stencil_buffer) .with_vsync(native_options.vsync) .build_windowed(window_builder, event_loop) From bcddafb50544ad42ac0b4a26795e8620cae94e64 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 11 Mar 2022 10:45:55 +0100 Subject: [PATCH 32/32] Add a some fine lines to the color test to test anti-aliasing --- egui_demo_lib/src/apps/color_test.rs | 82 ++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 4 deletions(-) diff --git a/egui_demo_lib/src/apps/color_test.rs b/egui_demo_lib/src/apps/color_test.rs index cf002269f..4126026d2 100644 --- a/egui_demo_lib/src/apps/color_test.rs +++ b/egui_demo_lib/src/apps/color_test.rs @@ -128,9 +128,9 @@ impl ColorTest { ui.separator(); - // TODO: another ground truth where we do the alpha-blending against the background also. - // TODO: exactly the same thing, but with vertex colors (no textures) - self.show_gradients(ui, WHITE, (TRANSPARENT, BLACK)); + self.show_gradients(ui, BLACK, (BLACK, WHITE)); + ui.separator(); + self.show_gradients(ui, WHITE, (BLACK, TRANSPARENT)); ui.separator(); self.show_gradients(ui, BLACK, (TRANSPARENT, WHITE)); ui.separator(); @@ -145,6 +145,10 @@ impl ColorTest { ui.separator(); pixel_test(ui); + + ui.separator(); + + fine_line_test(ui); } fn show_gradients(&mut self, ui: &mut Ui, bg_fill: Color32, (left, right): (Color32, Color32)) { @@ -353,6 +357,12 @@ impl TextureManager { fn pixel_test(ui: &mut Ui) { ui.label("Each subsequent square should be one physical pixel larger than the previous. They should be exactly one physical pixel apart. They should be perfectly aligned to the pixel grid."); + let color = if ui.style().visuals.dark_mode { + egui::Color32::WHITE + } else { + egui::Color32::BLACK + }; + let pixels_per_point = ui.ctx().pixels_per_point(); let num_squares: u32 = 8; let size_pixels = Vec2::new( @@ -375,7 +385,71 @@ fn pixel_test(ui: &mut Ui) { ), Vec2::splat(size as f32) / pixels_per_point, ); - painter.rect_filled(rect_points, 0.0, egui::Color32::WHITE); + painter.rect_filled(rect_points, 0.0, color); cursor_pixel.x += (1 + size) as f32; } } + +fn fine_line_test(ui: &mut Ui) { + ui.label("Some fine lines for testing anti-aliasing and blending:"); + + let size = Vec2::new(256.0, 512.0); + let (response, painter) = ui.allocate_painter(size, Sense::hover()); + let rect = response.rect; + + let mut top_half = rect; + top_half.set_bottom(top_half.center().y); + painter.rect_filled(top_half, 0.0, Color32::BLACK); + paint_fine_lines(&painter, top_half, Color32::WHITE); + + let mut bottom_half = rect; + bottom_half.set_top(bottom_half.center().y); + painter.rect_filled(bottom_half, 0.0, Color32::WHITE); + paint_fine_lines(&painter, bottom_half, Color32::BLACK); +} + +fn paint_fine_lines(painter: &egui::Painter, mut rect: Rect, color: Color32) { + rect = rect.shrink(12.0); + for width in [0.5, 1.0, 2.0] { + painter.text( + rect.left_top(), + Align2::CENTER_CENTER, + width.to_string(), + FontId::monospace(14.0), + color, + ); + + painter.add(egui::epaint::CubicBezierShape::from_points_stroke( + [ + rect.left_top() + Vec2::new(16.0, 0.0), + rect.right_top(), + rect.right_center(), + rect.right_bottom(), + ], + false, + Color32::TRANSPARENT, + Stroke::new(width, color), + )); + + rect.min.y += 32.0; + rect.max.x -= 32.0; + } + + rect.min.y += 16.0; + painter.text( + rect.left_top(), + Align2::LEFT_CENTER, + "transparent --> opaque", + FontId::monospace(11.0), + color, + ); + rect.min.y += 12.0; + let mut mesh = Mesh::default(); + mesh.colored_vertex(rect.left_bottom(), Color32::TRANSPARENT); + mesh.colored_vertex(rect.left_top(), Color32::TRANSPARENT); + mesh.colored_vertex(rect.right_bottom(), color); + mesh.colored_vertex(rect.right_top(), color); + mesh.add_triangle(0, 1, 2); + mesh.add_triangle(1, 2, 3); + painter.add(mesh); +}