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/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 610143f1..9dbbcd0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,14 +7,22 @@ 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, [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)). +* 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)). +* 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 2b008075..6112e122 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" @@ -993,9 +1012,9 @@ dependencies = [ "epi", "glow", "image", - "parking_lot 0.12.0", "poll-promise", "rfd", + "three-d", ] [[package]] @@ -1053,6 +1072,7 @@ dependencies = [ "poll-promise", "serde", "syntect", + "tracing", "unicode_names2", ] @@ -1063,7 +1083,6 @@ dependencies = [ "chrono", "egui", "image", - "parking_lot 0.12.0", "resvg", "serde", "tiny-skia", @@ -1489,6 +1508,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" @@ -1607,6 +1636,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" @@ -1810,6 +1844,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" @@ -2125,6 +2165,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -3041,6 +3082,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" @@ -3129,6 +3182,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" @@ -3473,6 +3545,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", ] @@ -3947,6 +4021,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 76f53ca8..be9e8663 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) 👈 @@ -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. @@ -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/CHANGELOG.md b/eframe/CHANGELOG.md index 90cb6126..580fc090 100644 --- a/eframe/CHANGELOG.md +++ b/eframe/CHANGELOG.md @@ -5,8 +5,11 @@ 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)). +* Add new `NativeOptions`: `vsync`, `multisampling`, `depth_buffer`, `stencil_buffer`. ## 0.17.0 - 2022-02-22 diff --git a/eframe/Cargo.toml b/eframe/Cargo.toml index a502ce46..5b2f4b40 100644 --- a/eframe/Cargo.toml +++ b/eframe/Cargo.toml @@ -71,6 +71,6 @@ image = { version = "0.24", default-features = false, features = [ "jpeg", "png", ] } -parking_lot = "0.12" 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/confirm_exit.rs b/eframe/examples/confirm_exit.rs index f1e8a359..8bc9bea3 100644 --- a/eframe/examples/confirm_exit.rs +++ b/eframe/examples/confirm_exit.rs @@ -1,6 +1,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::default(); + eframe::run_native( + "Confirm exit", + options, + Box::new(|_cc| Box::new(MyApp::default())), + ); +} #[derive(Default)] struct MyApp { @@ -8,17 +17,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 +47,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_glow.rs similarity index 72% rename from eframe/examples/custom_3d.rs rename to eframe/examples/custom_3d_glow.rs index 548918f7..20b3d3cd 100644 --- a/eframe/examples/custom_3d.rs +++ b/eframe/examples/custom_3d_glow.rs @@ -2,68 +2,87 @@ //! //! 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) #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(unsafe_code)] -use eframe::{egui, epi}; +use eframe::egui; -use parking_lot::Mutex; +use egui::mutex::Mutex; use std::sync::Arc; -#[derive(Default)] +fn main() { + 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 using glow", + options, + Box::new(|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:"); - - egui::ScrollArea::both().show(ui, |ui| { - egui::Frame::dark_canvas(ui.style()).show(ui, |ui| { - self.custom_painting(ui); - }); - ui.label("Drag to rotate!"); + 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)."); }); - }); - 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| { + egui::Frame::canvas(ui.style()).show(ui, |ui| { self.custom_painting(ui); }); + ui.label("Drag to rotate!"); + }); + } + + fn on_exit(&mut self, gl: &glow::Context) { + self.rotating_triangle.lock().destroy(gl); } } 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; + // 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| { + callback: std::sync::Arc::new(move |_info, 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 +182,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 +203,3 @@ impl RotatingTriangle { } } } - -fn main() { - let options = eframe::NativeOptions::default(); - eframe::run_native(Box::new(MyApp::default()), options); -} diff --git a/eframe/examples/custom_3d_three-d.rs b/eframe/examples/custom_3d_three-d.rs new file mode 100644 index 00000000..5be84631 --- /dev/null +++ b/eframe/examples/custom_3d_three-d.rs @@ -0,0 +1,157 @@ +//! 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)), + multisampling: 8, + ..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.2 } + } +} + +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(); +} diff --git a/eframe/examples/custom_font.rs b/eframe/examples/custom_font.rs index 0192d32c..b1408b00 100644 --- a/eframe/examples/custom_font.rs +++ b/eframe/examples/custom_font.rs @@ -1,66 +1,63 @@ -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, + Box::new(|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/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); + }); +} diff --git a/eframe/examples/download_image.rs b/eframe/examples/download_image.rs index dede08c4..71ac412a 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, + Box::new(|_cc| Box::new(MyApp::default())), + ); } #[derive(Default)] @@ -15,23 +19,19 @@ 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. // 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 }); @@ -50,6 +50,7 @@ impl epi::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/eframe/examples/file_dialog.rs b/eframe/examples/file_dialog.rs index f4b2f5a9..1cec45a4 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, + Box::new(|_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..93156c73 100644 --- a/eframe/examples/hello_world.rs +++ b/eframe/examples/hello_world.rs @@ -1,6 +1,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::default(); + eframe::run_native( + "My egui App", + options, + Box::new(|_cc| Box::new(MyApp::default())), + ); +} struct MyApp { name: String, @@ -16,12 +25,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 +44,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..0edcf605 100644 --- a/eframe/examples/image.rs +++ b/eframe/examples/image.rs @@ -1,8 +1,20 @@ #![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 { + initial_window_size: Some(egui::vec2(500.0, 900.0)), + ..Default::default() + }; + eframe::run_native( + "Show an image with eframe/egui", + options, + Box::new(|_cc| Box::new(MyApp::default())), + ); +} + struct MyApp { image: RetainedImage, } @@ -19,16 +31,18 @@ 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); + 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), @@ -37,8 +51,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..fd3f32f7 100644 --- a/eframe/examples/svg.rs +++ b/eframe/examples/svg.rs @@ -4,7 +4,19 @@ #![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, + Box::new(|_cc| Box::new(MyApp::default())), + ); +} struct MyApp { svg_image: egui_extras::RetainedImage, @@ -22,12 +34,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 +47,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..68b060b0 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, Box::new(|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,26 +54,17 @@ //! #[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, Box::new(|cc| Box::new(MyApp::new(cc)))) //! } //! ``` -// 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: 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 @@ -79,20 +75,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`]. -/// -/// ### 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 +86,55 @@ 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, Box::new(|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, Box::new(|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) +#[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/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-winit/src/epi.rs b/egui-winit/src/epi.rs index d257ca21..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); @@ -216,12 +220,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,10 +235,7 @@ impl EpiIntegration { integration_name: &'static str, max_texture_side: usize, window: &winit::window::Window, - gl: &std::rc::Rc, - repaint_signal: std::sync::Arc, persistence: crate::epi::Persistence, - app: Box, ) -> Self { let egui_ctx = egui::Context::default(); @@ -252,7 +252,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) { @@ -261,41 +260,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(); @@ -306,11 +285,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, @@ -323,12 +302,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); @@ -338,7 +321,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); } @@ -358,15 +341,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-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/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/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/context.rs b/egui/src/context.rs index fef366f6..6666ba33 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. @@ -667,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) } @@ -778,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 e5b9c7ea..4956918c 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"); @@ -136,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, @@ -147,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/src/lib.rs b/egui/src/lib.rs index 07e283e4..2a097849 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)] @@ -386,14 +307,15 @@ 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, + PaintCallbackInfo, Rgba, Rounding, Shape, Stroke, TextureHandle, TextureId, }; 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/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/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/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()); 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/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/src/widgets/plot/mod.rs b/egui/src/widgets/plot/mod.rs index c0254c12..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; @@ -767,6 +768,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: 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), diff --git a/egui_demo_app/src/lib.rs b/egui_demo_app/src/lib.rs index 487933f2..e81aba48 100644 --- a/egui_demo_app/src/lib.rs +++ b/egui_demo_app/src/lib.rs @@ -1,7 +1,4 @@ -// Forbid warnings in release builds: -#![cfg_attr(not(debug_assertions), deny(warnings))] -#![forbid(unsafe_code)] -#![warn(clippy::all, rust_2018_idioms)] +//! Demo app for egui #[cfg(target_arch = "wasm32")] use eframe::wasm_bindgen::{self, prelude::*}; @@ -19,6 +16,8 @@ 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, + 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 1ad429b9..6d747f0f 100644 --- a/egui_demo_app/src/main.rs +++ b/egui_demo_app/src/main.rs @@ -1,21 +1,21 @@ -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +//! Demo app for egui -// Forbid warnings in release builds: -#![cfg_attr(not(debug_assertions), deny(warnings))] -#![forbid(unsafe_code)] -#![warn(clippy::all, rust_2018_idioms)] +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release // When compiling natively: 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, + Box::new(|cc| Box::new(egui_demo_lib::WrapApp::new(cc))), + ); } diff --git a/egui_demo_lib/Cargo.toml b/egui_demo_lib/Cargo.toml index e680b109..272e56db 100644 --- a/egui_demo_lib/Cargo.toml +++ b/egui_demo_lib/Cargo.toml @@ -45,6 +45,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/benches/benchmark.rs b/egui_demo_lib/benches/benchmark.rs index 4fdeb730..b3e86bbc 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,19 +119,19 @@ pub fn criterion_benchmark(c: &mut Criterion) { color, wrap_width, ) - }) + }); }); 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(); 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/egui_demo_lib/src/apps/color_test.rs b/egui_demo_lib/src/apps/color_test.rs index ca25fd17..4126026d 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() { @@ -132,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(); @@ -149,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)) { @@ -357,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( @@ -379,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); +} 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/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`. + } + } + }); } } 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 c8215b42..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); @@ -78,11 +74,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_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/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/lib.rs b/egui_demo_lib/src/lib.rs index 6b02b436..fcb16c2f 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_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); diff --git a/egui_demo_lib/src/wrap_app.rs b/egui_demo_lib/src/wrap_app.rs index 0a119597..e80240e4 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,33 +49,22 @@ 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); } - 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` } @@ -111,7 +112,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 +139,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_extras/Cargo.toml b/egui_extras/Cargo.toml index f06d1db4..172dbbdd 100644 --- a/egui_extras/Cargo.toml +++ b/egui_extras/Cargo.toml @@ -36,10 +36,7 @@ datepicker = ["chrono"] persistence = ["serde"] [dependencies] -egui = { version = "0.17.0", path = "../egui", default-features = false, features = [ - "single_threaded", -] } -parking_lot = "0.12" +egui = { version = "0.17.0", path = "../egui", default-features = false } # Optional dependencies: @@ -49,7 +46,7 @@ chrono = { version = "0.4", optional = true } # 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_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_extras/src/lib.rs b/egui_extras/src/lib.rs index c796531f..08060265 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/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_glium/examples/native_texture.rs b/egui_glium/examples/native_texture.rs index a5197ccf..abaafd46 100644 --- a/egui_glium/examples/native_texture.rs +++ b/egui_glium/examples/native_texture.rs @@ -1,44 +1,9 @@ -//! 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 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<'_, u8> { + // 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..f5c85e68 100644 --- a/egui_glium/src/lib.rs +++ b/egui_glium/src/lib.rs @@ -2,88 +2,8 @@ //! //! 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: -#![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_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/Cargo.toml b/egui_glow/Cargo.toml index 924210a1..717d8d7b 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_glow/examples/pure_glow.rs b/egui_glow/examples/pure_glow.rs index 870f4121..173654b8 100644 --- a/egui_glow/examples/pure_glow.rs +++ b/egui_glow/examples/pure_glow.rs @@ -1,42 +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 - -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) -} +#![allow(unsafe_code)] fn main() { let mut clear_color = [0.1, 0.1, 0.1]; @@ -116,3 +81,34 @@ 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)) }; + + (gl_window, gl) +} diff --git a/egui_glow/src/epi_backend.rs b/egui_glow/src/epi_backend.rs index 57c3f46e..e9b1cf74 100644 --- a/egui_glow/src/epi_backend.rs +++ b/egui_glow/src/epi_backend.rs @@ -3,16 +3,9 @@ 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( + native_options: &NativeOptions, window_builder: winit::window::WindowBuilder, event_loop: &winit::event_loop::EventLoop, ) -> ( @@ -21,10 +14,11 @@ fn create_display( ) { let gl_window = unsafe { glutin::ContextBuilder::new() - .with_depth_buffer(0) + .with_depth_buffer(native_options.depth_buffer) + .with_multisampling(native_options.multisampling) .with_srgb(true) - .with_stencil_buffer(0) - .with_vsync(true) + .with_stencil_buffer(native_options.stencil_buffer) + .with_vsync(native_options.vsync) .build_windowed(window_builder, event_loop) .unwrap() .make_current() @@ -33,11 +27,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) } @@ -47,31 +36,43 @@ 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_window, gl) = create_display(native_options, 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( "egui_glow", painter.max_texture_side(), gl_window.window(), - &gl, - repaint_signal, persistence, - app, ); + { + 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(); + }); + } + + 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| { @@ -90,7 +91,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); @@ -98,7 +99,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); @@ -126,7 +127,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 { @@ -145,7 +146,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; } @@ -153,7 +154,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_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 8e68836c..ebbf94ce 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,12 +37,12 @@ pub struct Painter { u_sampler: glow::UniformLocation, is_webgl_1: bool, is_embedded: bool, - vertex_array: crate::misc_util::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, @@ -95,11 +96,10 @@ 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 shader_version = ShaderVersion::get(&gl); let is_webgl_1 = shader_version == ShaderVersion::Es100; let header = shader_version.version(); @@ -117,7 +117,6 @@ impl Painter { Some(PostProcess::new( gl.clone(), shader_prefix, - support_vao, is_webgl_1, width, height, @@ -168,48 +167,45 @@ 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::misc_util::VAO::native(&gl) - } else { - crate::misc_util::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 { - 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_emulate::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_emulate::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); - check_for_gl_error(&gl, "after Painter::new"); + 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 { gl, @@ -219,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")] @@ -251,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, @@ -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; @@ -275,11 +280,13 @@ 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)); + check_for_gl_error!(&self.gl, "prepare_painting"); + (width_in_pixels, height_in_pixels) } @@ -368,7 +375,15 @@ 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"); // Restore state: unsafe { @@ -381,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 { @@ -391,7 +407,7 @@ impl Painter { self.gl.disable(glow::SCISSOR_TEST); - check_for_gl_error(&self.gl, "painting"); + check_for_gl_error!(&self.gl, "painting"); } } @@ -400,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), @@ -427,6 +442,8 @@ impl Painter { 0, ); } + + check_for_gl_error!(&self.gl, "paint_mesh"); } } @@ -485,7 +502,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, @@ -508,7 +538,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 { @@ -536,7 +566,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( @@ -550,7 +580,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"); } } } @@ -571,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 0706b2a6..e35028a3 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, + vao: crate::vao::VertexArrayObject, is_webgl_1: bool, texture: glow::Texture, texture_size: (i32, i32), @@ -21,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, @@ -77,7 +77,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, @@ -124,35 +124,31 @@ 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::misc_util::VAO::emulated() - } else { - crate::misc_util::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)); 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, pos_buffer, index_buffer, - vertex_array, + vao, is_webgl_1, texture, texture_size: (width, height), @@ -190,6 +186,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) { @@ -209,16 +207,18 @@ 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); + + 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..0a6e53d3 --- /dev/null +++ b/egui_glow/src/vao.rs @@ -0,0 +1,154 @@ +#![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, +} + +// ---------------------------------------------------------------------------- + +/// 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 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 { + vao, + vbo, + buffer_infos, + } + } + + 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 { + 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) 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); + } + } + } +} + +// ---------------------------------------------------------------------------- + +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 . + let supported_extensions = gl.supported_extensions(); + tracing::debug!("Supported OpenGL extensions: {:?}", supported_extensions); + 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 . + let supported_extensions = gl.supported_extensions(); + tracing::debug!("Supported OpenGL extensions: {:?}", supported_extensions); + 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. + let supported_extensions = gl.supported_extensions(); + tracing::debug!("Supported OpenGL extensions: {:?}", supported_extensions); + 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); - } - } -} 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/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/egui_web/src/backend.rs b/egui_web/src/backend.rs index 50313303..0ea9c8c0 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 { @@ -72,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 { @@ -145,13 +139,11 @@ 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(); - 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()); @@ -176,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, @@ -193,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) } @@ -326,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/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 062c9ac0..c0bc1905 100644 --- a/egui_web/src/lib.rs +++ b/egui_web/src/lib.rs @@ -3,16 +3,8 @@ //! 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))] -#![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; @@ -896,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/CHANGELOG.md b/epaint/CHANGELOG.md index 9a300704..de93bc15 100644 --- a/epaint/CHANGELOG.md +++ b/epaint/CHANGELOG.md @@ -4,6 +4,9 @@ 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/Cargo.toml b/epaint/Cargo.toml index 4c6ef6a2..5316397e 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,25 +47,26 @@ 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. 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/lib.rs b/epaint/src/lib.rs index e75d3026..ab559f89 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)] @@ -110,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/mesh.rs b/epaint/src/mesh.rs index 800e6120..c14f41fa 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)] @@ -242,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); + } + } } // ---------------------------------------------------------------------------- diff --git a/epaint/src/mutex.rs b/epaint/src/mutex.rs index fa0e939e..b3639d19 100644 --- a/epaint/src/mutex.rs +++ b/epaint/src/mutex.rs @@ -1,16 +1,13 @@ //! 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(target_arch = "wasm32"))] #[cfg(not(debug_assertions))] mod mutex_impl { - /// Provides interior mutability. Only thread-safe if the `multi_threaded` feature is enabled. + /// Provides interior mutability. + /// + /// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets. #[derive(Default)] pub struct Mutex(parking_lot::Mutex); @@ -30,10 +27,12 @@ mod mutex_impl { } } -#[cfg(feature = "multi_threaded")] +#[cfg(not(target_arch = "wasm32"))] #[cfg(debug_assertions)] mod mutex_impl { - /// Provides interior mutability. Only thread-safe if the `multi_threaded` feature is enabled. + /// Provides interior mutability. + /// + /// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets. #[derive(Default)] pub struct Mutex(parking_lot::Mutex); @@ -111,7 +110,7 @@ mod mutex_impl { } } -#[cfg(feature = "multi_threaded")] +#[cfg(not(target_arch = "wasm32"))] mod rw_lock_impl { /// The lock you get from [`RwLock::read`]. pub use parking_lot::MappedRwLockReadGuard as RwLockReadGuard; @@ -119,7 +118,9 @@ 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. + /// + /// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets. #[derive(Default)] pub struct RwLock(parking_lot::RwLock); @@ -141,18 +142,20 @@ mod rw_lock_impl { } } -#[cfg(feature = "multi_threaded")] +#[cfg(not(target_arch = "wasm32"))] mod arc_impl { pub use std::sync::Arc; } // ---------------------------------------------------------------------------- -#[cfg(not(feature = "multi_threaded"))] +#[cfg(target_arch = "wasm32")] 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. + /// Provides interior mutability. + /// + /// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets. #[derive(Default)] pub struct Mutex(atomic_refcell::AtomicRefCell); @@ -173,7 +176,7 @@ mod mutex_impl { } } -#[cfg(not(feature = "multi_threaded"))] +#[cfg(target_arch = "wasm32")] mod rw_lock_impl { // `atomic_refcell` will panic if multiple threads try to access the same value @@ -183,7 +186,9 @@ mod rw_lock_impl { /// 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. + /// Provides interior mutability. + /// + /// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets. #[derive(Default)] pub struct RwLock(atomic_refcell::AtomicRefCell); @@ -206,7 +211,7 @@ mod rw_lock_impl { } } -#[cfg(not(feature = "multi_threaded"))] +#[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; 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/shape.rs b/epaint/src/shape.rs index 1d846fb2..4fbd8457 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 { @@ -636,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. @@ -644,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); } } 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(), diff --git a/epaint/src/tessellator.rs b/epaint/src/tessellator.rs index f46af30d..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,24 +661,43 @@ 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(), } } + /// Set the `Rect` to use for culling. + pub fn set_clip_rect(&mut self, clip_rect: Rect) { + 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). /// * `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) => { @@ -711,26 +705,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() { @@ -738,44 +714,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); @@ -787,7 +748,267 @@ 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(self.feathering, fill, out); + self.scratchpad_path + .stroke_closed(self.feathering, stroke, 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(self.feathering, stroke, 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(self.feathering, *fill, out); + } + let typ = if *closed { + PathType::Closed + } else { + PathType::Open + }; + self.scratchpad_path + .stroke(self.feathering, typ, *stroke, 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(self.feathering, fill, out); + path.stroke_closed(self.feathering, stroke, 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.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); + + 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(self.feathering, *underline, 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, @@ -812,11 +1033,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 @@ -858,177 +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); - } - - 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); - } - } + self.scratchpad_path + .stroke(self.feathering, typ, stroke, out); } } @@ -1037,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`]. @@ -1046,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 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/epi/src/lib.rs b/epi/src/lib.rs index 033a4a78..dac19301 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. @@ -98,6 +17,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 = Box) -> 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 +52,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 +80,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) @@ -174,13 +95,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`. @@ -263,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 { @@ -279,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, } } } @@ -359,13 +309,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 +467,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 +474,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. 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)