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 610143f1..ccc18f40 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 55dee5c1..30384431 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 90cb6126..b7fb979d 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 dede08c4..ba754b75 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 d257ca21..e09af05e 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 fef366f6..75057f2c 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 11ff6764..a41f3059 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 c8215b42..63ae9f01 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 924210a1..90dfa486 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 57c3f46e..bef12fad 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 50313303..b85d1a08 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 033a4a78..c618c0d8 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 f1e8a359..1e9035fa 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 548918f7..8b14f130 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 0192d32c..dc74fe0d 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 ba754b75..f7958762 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 f4b2f5a9..66d0f3fc 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 77f1479b..d5a9576f 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 97575c23..c91622f3 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 b7d362f9..a4e71bf7 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 fcb55cec..ffa4f568 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 e09af05e..2674db33 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 07e283e4..e3a60415 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 487933f2..033aaa15 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 1ad429b9..30425599 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 ca25fd17..cf002269 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 5544a51a..c01aa8b9 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 943a0139..51a03f4c 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 63ae9f01..a2c9097c 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 dc066e38..30350e84 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 0a119597..d8d701d3 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 a5197ccf..8d65c528 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 52808d7b..4fe900f7 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 9e6760e5..1ecfda86 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 870f4121..4b832878 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 bef12fad..32705bf2 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 b85d1a08..62424350 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 c618c0d8..ddc1add0 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 1e9035fa..8bc9bea3 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 8b14f130..54f9bf8e 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 dc74fe0d..b1408b00 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 f7958762..9cbb51d1 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 66d0f3fc..1cec45a4 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 d5a9576f..93156c73 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 c91622f3..5b890220 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 a4e71bf7..fd3f32f7 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 ffa4f568..6c7e7f8d 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 033aaa15..8d799335 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 30425599..43d70103 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 ddc1add0..005b132c 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 c0254c12..3885fc11 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 f46af30d..95fde7f8 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 8e68836c..db24433b 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 ccc18f40..6678da2c 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 eb442593..ae4a9601 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 76f53ca8..2b058698 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 b7fb979d..a03be5de 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 6c7e7f8d..732d60a1 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 e54b97d1..0daa3a45 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 d8d701d3..e80240e4 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 91bee2c7..ecffcb7f 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 062c9ac0..8bddd04a 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 005b132c..d1bda8d0 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 6678da2c..3612db70 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 46146821..1906150d 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 23480abe..c24a5fb9 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 4fdeb730..9b988b3f 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 800e6120..7a5e0dcd 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 95fde7f8..440f356e 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 1d846fb2..57d3cef1 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 75057f2c..536461de 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 4b832878..06abc5c2 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 32705bf2..72611673 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 db24433b..804bc19e 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 e5b9c7ea..7e286d89 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 2316d905..47f3e905 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 e3a60415..c6ab4b0e 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 b294115b..e62dd17b 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 00000000..b809c283 --- /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 732d60a1..68b060b0 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 a4a36220..731724cf 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 c6ab4b0e..129afaa1 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 8d799335..a4444edf 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 43d70103..c99e2688 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 ca15d348..87e9b2d9 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 c51f54a8..c7111442 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 1ecfda86..f5c85e68 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 62424350..0ea9c8c0 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 c589aad1..a66cf5d4 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 8bddd04a..c0bc1905 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 11c36efd..e3ac02b1 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 ef2af8d8..c7387354 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 97fb41ed..4f87d9f6 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 e75d3026..1e27b55f 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 d1bda8d0..9359ccca 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 3612db70..f8519289 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 7eabe383..8d2430ea 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 c8cbdb67..fa00f3cf 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 4e1eb70d..b059d432 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 9024a002..d7ffbabb 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 6c60c6a3..41bd9d4f 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 765b70b9..e1aca332 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 54f9bf8e..6c4b5e6f 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 9cbb51d1..71ac412a 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 a4444edf..e81aba48 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 c99e2688..6d747f0f 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 9b988b3f..9038187f 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 8d65c528..abaafd46 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 06abc5c2..173654b8 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 5381d322..397d7fd3 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 f8519289..08a352ab 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 30384431..7c1e66bf 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 2b058698..f8c4ef3a 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 a7cde96f..c27d9cca 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 b99087a0..b51c8e70 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 63c31f96..09ea707f 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 b23a7a61..8ef55791 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 90dfa486..fbf52026 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 5994f9d1..7f9b09fc 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 9a300704..ec709f69 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 4c6ef6a2..3b42c788 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 fa0e939e..1d73ff7d 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 4e894a62..a62005eb 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 e2d8bd35..2a319cf4 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 00000000..9f5dd222 --- /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 5b890220..0edcf605 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 61ea0d9a..889fbdc9 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 7a5e0dcd..c14f41fa 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 3885fc11..f4b7e343 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 7c1e66bf..a7f5b1e1 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 a502ce46..0c1e0b63 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 6c4b5e6f..f86d444e 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 09ea707f..7988b340 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 4015acd3..b9001f20 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 fbf52026..717d8d7b 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 72611673..2e16e7ae 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 3b42c788..5316397e 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 1d73ff7d..b3639d19 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 227b6527..29a6cf2b 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 4547c261..ee22f58b 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 804bc19e..69d5123d 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 0706b2a6..97b2e0c9 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 ae4af65e..84c2434f 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 00000000..d3a5d896 --- /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 59013494..00000000 --- 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 f86d444e..45dedabb 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 129afaa1..2a097849 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 69d5123d..ebbf94ce 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 97b2e0c9..e35028a3 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 d3a5d896..b88aefeb 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 1e27b55f..ab559f89 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 57d3cef1..4fbd8457 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 b88aefeb..0a6e53d3 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 a7f5b1e1..2d4bb847 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 f8c4ef3a..be9e8663 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 0c1e0b63..5b2f4b40 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 45dedabb..f352367f 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 00000000..606560ce --- /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 08a352ab..9dbbcd0c 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 a03be5de..580fc090 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 f352367f..20b3d3cd 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 606560ce..5be84631 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 2674db33..1a82de17 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 0c470c64..60aa349e 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 2e16e7ae..3d74a956 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 9359ccca..dac19301 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 536461de..6666ba33 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 7e286d89..4956918c 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 9038187f..b3e86bbc 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 ec709f69..de93bc15 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 d8895fee..35ce41cd 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 440f356e..1f9fe1a0 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 a70127c7..bac97e17 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 3d74a956..e9b1cf74 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 cf002269..4126026d 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); +}